🎨 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:
69
frontend/app/components/feature/ProjectCard.vue
Normal file
69
frontend/app/components/feature/ProjectCard.vue
Normal 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">📁</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>
|
||||
27
frontend/app/types/project.ts
Normal file
27
frontend/app/types/project.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user