🎨 Add ProjectCard component with hover effect (Story 2.1)

Reusable project card with NuxtImg lazy loading, hover overlay with
"Discover" CTA, responsive design, and full accessibility support
including prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 02:04:24 +01:00
parent 676d362b24
commit 4117a84809
6 changed files with 162 additions and 38 deletions

View File

@@ -0,0 +1,69 @@
<template>
<NuxtLink
:to="localePath(`/projets/${project.slug}`)"
class="project-card group block rounded-xl overflow-hidden bg-sky-dark/50 border border-sky-text/10 hover:border-sky-accent/30 transition-colors focus-visible:outline-2 focus-visible:outline-sky-accent focus-visible:outline-offset-2"
role="article"
>
<div class="relative overflow-hidden">
<!-- Image avec lazy loading -->
<NuxtImg
v-if="project.image"
:src="project.image"
:alt="project.title"
format="webp"
loading="lazy"
width="400"
height="225"
class="w-full h-44 md:h-48 object-cover transition-transform duration-300 group-hover:scale-105"
/>
<div
v-else
class="w-full h-44 md:h-48 bg-sky-text/5 flex items-center justify-center"
>
<span class="text-sky-text/30 text-4xl">&#x1f4c1;</span>
</div>
<!-- Overlay au hover -->
<div class="absolute inset-0 bg-sky-dark/70 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<span class="text-sky-accent font-ui font-semibold text-lg px-6 py-2 border border-sky-accent rounded-lg">
{{ $t('projects.discover') }}
</span>
</div>
</div>
<!-- Contenu texte -->
<div class="p-4">
<h3 class="font-ui text-lg text-sky-text font-semibold truncate">
{{ project.title }}
</h3>
<p v-if="project.short_description" class="font-narrative text-sm text-sky-text/60 line-clamp-2 mt-1">
{{ project.short_description }}
</p>
</div>
</NuxtLink>
</template>
<script setup lang="ts">
import type { Project } from '~/types/project'
defineProps<{
project: Project
}>()
const localePath = useLocalePath()
</script>
<style scoped>
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
.project-card,
.project-card * {
transition: none !important;
animation: none !important;
}
.project-card:hover img {
transform: none !important;
}
}
</style>

View File

@@ -0,0 +1,27 @@
export interface Project {
id: number
slug: string
title: string
description: string
short_description: string
image: string
url?: string
github_url?: string
date_completed?: string
is_featured: boolean
skills?: ProjectSkill[]
}
export interface ProjectSkill {
id: number
slug: string
name: string
description?: string
icon?: string
category: string
max_level: number
pivot?: {
level_before: number
level_after: number
}
}