You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
68 lines
1.5 KiB
68 lines
1.5 KiB
<template>
|
|
<div class="project-card">
|
|
<NuxtLink :to="`${projectsPath}/${project.id}`" class="project-link">
|
|
<div class="project-image">
|
|
<img
|
|
:src="imageSrc"
|
|
:alt="project.title"
|
|
loading="lazy"
|
|
@error="handleImageError"
|
|
class="project-img"
|
|
/>
|
|
</div>
|
|
<div class="project-content">
|
|
<h2>{{ project.title }}</h2>
|
|
<p>{{ project.description }}</p>
|
|
<div class="project-technologies">
|
|
<span v-for="tech in project.technologies" :key="tech" class="tech-tag">
|
|
{{ tech }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</NuxtLink>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Project } from '~/types/project';
|
|
import { DEFAULT_IMAGES } from '~/config/paths';
|
|
import { navigationItems } from '~/config/navigation';
|
|
|
|
const props = defineProps<{ project: Project }>();
|
|
|
|
// Optimisation: précalculer le chemin des projets
|
|
const projectsPath = navigationItems.find((item) => item.finder === 'projects')?.path || '/projects';
|
|
const imageSrc = ref(props.project.image || DEFAULT_IMAGES.PROJECT);
|
|
|
|
const handleImageError = () => {
|
|
imageSrc.value = DEFAULT_IMAGES.PROJECT;
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
@import '~/assets/css/pages/project-detail.css';
|
|
|
|
.project-img {
|
|
width: 100%;
|
|
height: auto;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.image-placeholder {
|
|
background-color: var(--surface-ground);
|
|
animation: pulse 1.5s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% {
|
|
opacity: 0.6;
|
|
}
|
|
50% {
|
|
opacity: 0.8;
|
|
}
|
|
100% {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
</style>
|