feat(gallery): ajout d'un modal de galerie et de nouvelles images
- Création du composant GalleryModal pour afficher les images en plein écran avec navigation. - Ajout de styles CSS pour le modal de galerie. - Intégration de la logique de gestion de la galerie dans le composable useGallery. - Ajout de nouvelles images WebP pour le projet FlowBoard. - Mise à jour des pages Home et ProjectDetail pour utiliser le nouveau composant de galerie.
This commit is contained in:
BIN
src/assets/images/flowboard/flowboard_1.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_1.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/images/flowboard/flowboard_2.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_2.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/images/flowboard/flowboard_3.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_3.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/images/flowboard/flowboard_4.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_4.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
99
src/components/GalleryModal.vue
Normal file
99
src/components/GalleryModal.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useAssets } from '@/composables/useAssets'
|
||||
import './styles/GalleryModal.css'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
currentImage: string
|
||||
currentIndex: number
|
||||
totalImages: number
|
||||
hasNext: boolean
|
||||
hasPrevious: boolean
|
||||
projectTitle: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
next: []
|
||||
previous: []
|
||||
goTo: [index: number]
|
||||
}>()
|
||||
|
||||
const { getImageUrl } = useAssets()
|
||||
|
||||
// Keyboard navigation
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (!props.isOpen) return
|
||||
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
emit('close')
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
if (props.hasPrevious) emit('previous')
|
||||
break
|
||||
case 'ArrowRight':
|
||||
if (props.hasNext) emit('next')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="gallery-modal" @click="emit('close')">
|
||||
<div class="gallery-modal-overlay"></div>
|
||||
|
||||
<!-- Close Button -->
|
||||
<button class="gallery-close" @click="emit('close')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
<button v-if="hasPrevious" class="gallery-nav gallery-nav-prev" @click.stop="emit('previous')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button v-if="hasNext" class="gallery-nav gallery-nav-next" @click.stop="emit('next')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Main Image -->
|
||||
<div class="gallery-content" @click.stop>
|
||||
<img :src="getImageUrl(currentImage)" :alt="`${projectTitle} - Image ${currentIndex + 1}`"
|
||||
class="gallery-image">
|
||||
|
||||
<!-- Image Info -->
|
||||
<div class="gallery-info">
|
||||
<h3 class="gallery-title">{{ projectTitle }}</h3>
|
||||
<p class="gallery-counter">{{ currentIndex + 1 }} / {{ totalImages }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Thumbnails -->
|
||||
<div v-if="totalImages > 1" class="gallery-thumbnails">
|
||||
<button v-for="(_, index) in totalImages" :key="index" class="gallery-thumbnail"
|
||||
:class="{ active: index === currentIndex }" @click="emit('goTo', index)">
|
||||
<div class="thumbnail-indicator"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
201
src/components/styles/GalleryModal.css
Normal file
201
src/components/styles/GalleryModal.css
Normal file
@@ -0,0 +1,201 @@
|
||||
/* GalleryModal Styles */
|
||||
|
||||
.gallery-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.gallery-modal-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.gallery-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 10001;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.gallery-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.gallery-close svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.gallery-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 10001;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.gallery-nav:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
.gallery-nav-prev {
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
.gallery-nav-next {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.gallery-nav svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.gallery-content {
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.gallery-info {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.gallery-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.gallery-counter {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.8;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gallery-thumbnails {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gallery-thumbnail {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.thumbnail-indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.gallery-thumbnail.active .thumbnail-indicator {
|
||||
background: white;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.gallery-thumbnail:hover .thumbnail-indicator {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.gallery-modal {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.gallery-close,
|
||||
.gallery-nav {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.gallery-close svg,
|
||||
.gallery-nav svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.gallery-nav-prev {
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.gallery-nav-next {
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.gallery-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.gallery-counter {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
59
src/composables/useGallery.ts
Normal file
59
src/composables/useGallery.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export function useGallery() {
|
||||
const isOpen = ref(false)
|
||||
const currentIndex = ref(0)
|
||||
const images = ref<string[]>([])
|
||||
|
||||
const currentImage = computed(() => images.value[currentIndex.value])
|
||||
const hasNext = computed(() => currentIndex.value < images.value.length - 1)
|
||||
const hasPrevious = computed(() => currentIndex.value > 0)
|
||||
|
||||
const openGallery = (galleryImages: string[], index: number = 0) => {
|
||||
images.value = galleryImages
|
||||
currentIndex.value = index
|
||||
isOpen.value = true
|
||||
// Prevent body scroll when modal is open
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
const closeGallery = () => {
|
||||
isOpen.value = false
|
||||
currentIndex.value = 0
|
||||
images.value = []
|
||||
// Restore body scroll
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
|
||||
const nextImage = () => {
|
||||
if (hasNext.value) {
|
||||
currentIndex.value++
|
||||
}
|
||||
}
|
||||
|
||||
const previousImage = () => {
|
||||
if (hasPrevious.value) {
|
||||
currentIndex.value--
|
||||
}
|
||||
}
|
||||
|
||||
const goToImage = (index: number) => {
|
||||
if (index >= 0 && index < images.value.length) {
|
||||
currentIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
currentIndex,
|
||||
currentImage,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
openGallery,
|
||||
closeGallery,
|
||||
nextImage,
|
||||
previousImage,
|
||||
goToImage,
|
||||
images: computed(() => images.value)
|
||||
}
|
||||
}
|
127
src/composables/useProjects.ts
Normal file
127
src/composables/useProjects.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { Project } from '@/types'
|
||||
|
||||
// Base project data without translations
|
||||
const baseProjects: Omit<Project, 'title' | 'description' | 'longDescription'>[] = [
|
||||
{
|
||||
id: 'virtual-tour',
|
||||
image: '@/assets/images/virtualtour.webp',
|
||||
technologies: ['Vue.js', 'Three.js', 'WebGL', 'Node.js'],
|
||||
category: 'Web Development',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Visit',
|
||||
link: 'https://www.lycee-chabanne16.fr/visites/BACSN/index.htm'
|
||||
}
|
||||
],
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'xinko',
|
||||
image: '@/assets/images/xinko.webp',
|
||||
technologies: ['Node.js', 'Discord.js', 'MongoDB', 'Express'],
|
||||
category: 'Bot Development',
|
||||
featured: true,
|
||||
buttons: [
|
||||
{
|
||||
title: 'Invite',
|
||||
link: 'https://discord.com/api/oauth2/authorize?client_id=1035571329866407976&permissions=292288982151&scope=applications.commands%20bot'
|
||||
}
|
||||
],
|
||||
date: '2023'
|
||||
},
|
||||
{
|
||||
id: 'image-manipulation',
|
||||
image: '@/assets/images/dig.webp',
|
||||
technologies: ['JavaScript', 'Node.js', 'Canvas', 'npm'],
|
||||
category: 'Open Source',
|
||||
featured: true,
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-image-generation'
|
||||
},
|
||||
{
|
||||
title: 'NPM Package',
|
||||
link: 'https://www.npmjs.com/package/discord-image-generation'
|
||||
}
|
||||
],
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'primate-web-admin',
|
||||
image: '@/assets/images/primate.webp',
|
||||
technologies: ['React', 'TypeScript', 'Node.js', 'Express'],
|
||||
category: 'Enterprise Software',
|
||||
date: '2023'
|
||||
},
|
||||
{
|
||||
id: 'instagram-bot',
|
||||
image: '@/assets/images/instagram.webp',
|
||||
technologies: ['JavaScript', 'Node.js', 'Instagram API', 'Canvas'],
|
||||
category: 'Social Media Bot',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/instagram-bot'
|
||||
}
|
||||
],
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'crowdin-status-bot',
|
||||
image: '@/assets/images/crowdin.webp',
|
||||
technologies: ['Node.js', 'Discord.js', 'Crowdin API', 'Cron'],
|
||||
category: 'Automation',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-crowdin-status'
|
||||
}
|
||||
],
|
||||
date: '2023'
|
||||
},
|
||||
{
|
||||
id: 'flowboard',
|
||||
image: '@/assets/images/flowboard/flowboard_1.webp',
|
||||
technologies: ['Vue.js', 'Node.js', 'TypeScript', 'MongoDB', 'Express'],
|
||||
category: 'Web Development',
|
||||
featured: true,
|
||||
features: [
|
||||
'Organize your tasks, projects and ideas by creating thematic boards adapted to your needs',
|
||||
'Add cards for each task, assign members, set due dates, and track progress at a glance',
|
||||
'Invite colleagues and teammates to join your boards to work together, share ideas, and coordinate your efforts',
|
||||
'Keep an overview of the progress of your projects thanks to a simple and intuitive interface',
|
||||
'Use labels, lists and tables to prioritize tasks, set priorities and keep the overview clear'
|
||||
],
|
||||
gallery: [
|
||||
'@/assets/images/flowboard/flowboard_1.webp',
|
||||
'@/assets/images/flowboard/flowboard_2.webp',
|
||||
'@/assets/images/flowboard/flowboard_3.webp',
|
||||
'@/assets/images/flowboard/flowboard_4.webp'
|
||||
],
|
||||
date: '2024'
|
||||
}
|
||||
]
|
||||
|
||||
export function useProjects() {
|
||||
const { t } = useI18n()
|
||||
|
||||
const projects = computed((): Project[] => {
|
||||
return baseProjects.map(project => ({
|
||||
...project,
|
||||
title: t(`projectData.${project.id}.title`),
|
||||
description: t(`projectData.${project.id}.description`),
|
||||
longDescription: t(`projectData.${project.id}.longDescription`),
|
||||
buttons: project.buttons?.map(button => ({
|
||||
...button,
|
||||
title: t(`projectData.${project.id}.buttons.${button.title.toLowerCase()}`, button.title)
|
||||
})) || []
|
||||
}))
|
||||
})
|
||||
|
||||
return {
|
||||
projects: projects
|
||||
}
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
import type { Project } from '@/types'
|
||||
|
||||
export const projects: Project[] = [
|
||||
{
|
||||
id: 'virtual-tour',
|
||||
title: 'Virtual Tour',
|
||||
image: '@/assets/images/virtualtour.webp',
|
||||
description: 'Développement d\'une plateforme de visite virtuelle interactive et immersive.',
|
||||
longDescription: 'Virtual Tour est une plateforme innovante permettant de créer des visites virtuelles interactives et immersives. Développée avec les dernières technologies web, elle offre une expérience utilisateur fluide et engageante pour explorer des espaces en 3D.',
|
||||
technologies: ['Vue.js', 'Three.js', 'WebGL', 'Node.js'],
|
||||
category: 'Web Development',
|
||||
featured: true,
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'xinko',
|
||||
title: 'Xinko',
|
||||
image: '@/assets/images/xinko.webp',
|
||||
description: 'Xinko is a multiplatform bot that can be used to create primary with ease and fun in it.',
|
||||
longDescription: 'Xinko est un bot multiplateforme innovant conçu pour simplifier la création de contenu primaire. Avec une interface intuitive et des fonctionnalités avancées, il permet aux utilisateurs de générer du contenu de qualité avec facilité et plaisir.',
|
||||
technologies: ['Node.js', 'Discord.js', 'MongoDB', 'Express'],
|
||||
category: 'Bot Development',
|
||||
featured: true,
|
||||
buttons: [
|
||||
{
|
||||
title: 'Website',
|
||||
link: 'https://xinko.bot'
|
||||
}
|
||||
],
|
||||
date: '2023'
|
||||
},
|
||||
{
|
||||
id: 'image-manipulation',
|
||||
title: 'Image Manipulation',
|
||||
image: '@/assets/images/dig.webp',
|
||||
description: 'Discord Image Generation: NPM package for code-based image manipulation. Originally an API, now open-source.',
|
||||
longDescription: 'Un package NPM complet pour la génération et la manipulation d\'images dans Discord. Ce projet open-source offre une API simple pour créer des memes, appliquer des filtres et générer des images dynamiques. Utilisé par de nombreux bots Discord avec plus de 100k téléchargements.',
|
||||
technologies: ['JavaScript', 'Node.js', 'Canvas', 'npm'],
|
||||
category: 'Open Source',
|
||||
featured: true,
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-image-generation'
|
||||
},
|
||||
{
|
||||
title: 'NPM Package',
|
||||
link: 'https://www.npmjs.com/package/discord-image-generation'
|
||||
}
|
||||
],
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'primate-web-admin',
|
||||
title: 'Primate Web Admin',
|
||||
image: '@/assets/images/primate.webp',
|
||||
description: 'Primate Web Admin is a Web interface to manage Primate that is a Munki-like deployment tool for Windows.',
|
||||
longDescription: 'Interface web moderne pour gérer Primate, un outil de déploiement pour Windows inspiré de Munki. Cette application web permet aux administrateurs système de déployer et gérer des logiciels sur un parc informatique Windows de manière centralisée.',
|
||||
technologies: ['React', 'TypeScript', 'Node.js', 'Express'],
|
||||
category: 'Enterprise Software',
|
||||
date: '2023'
|
||||
},
|
||||
{
|
||||
id: 'instagram-bot',
|
||||
title: 'Instagram Bot',
|
||||
image: '@/assets/images/instagram.webp',
|
||||
description: 'Fully functional Instagram bot using Insta.js by androz2091. It has many commands. Generate images with commands like: !stonk or !invert.',
|
||||
longDescription: 'Bot Instagram entièrement fonctionnel développé avec Insta.js. Il propose de nombreuses commandes pour générer des images personnalisées, des memes et des effets visuels. Parfait pour animer vos stories et posts Instagram avec du contenu original.',
|
||||
technologies: ['JavaScript', 'Node.js', 'Instagram API', 'Canvas'],
|
||||
category: 'Social Media Bot',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/instagram-bot'
|
||||
}
|
||||
],
|
||||
date: '2022'
|
||||
},
|
||||
{
|
||||
id: 'crowdin-status-bot',
|
||||
title: 'Crowdin Status Bot',
|
||||
image: '@/assets/images/crowdin.webp',
|
||||
description: 'A bot that fetches Crowdin translation status and updates Discord messages with the latest status. Stay informed on progress!',
|
||||
longDescription: 'Bot Discord automatisé qui récupère le statut des traductions Crowdin et met à jour les messages Discord avec les dernières informations. Idéal pour les équipes de traduction qui souhaitent rester informées du progrès de leurs projets en temps réel.',
|
||||
technologies: ['Node.js', 'Discord.js', 'Crowdin API', 'Cron'],
|
||||
category: 'Automation',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Repository',
|
||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-crowdin-status'
|
||||
}
|
||||
],
|
||||
date: '2023'
|
||||
}
|
||||
]
|
@@ -68,6 +68,21 @@ export default {
|
||||
npmpackage: 'NPM Package',
|
||||
viewProject: 'View Details'
|
||||
},
|
||||
projectDetail: {
|
||||
backToProjects: 'Back to Projects',
|
||||
viewDemo: 'View Demo',
|
||||
sourceCode: 'Source Code',
|
||||
share: 'Share',
|
||||
aboutProject: 'About the Project',
|
||||
keyFeatures: 'Key Features',
|
||||
technologiesUsed: 'Technologies Used',
|
||||
gallery: 'Gallery',
|
||||
projectInfo: 'Project Information',
|
||||
date: 'Date',
|
||||
category: 'Category',
|
||||
status: 'Status',
|
||||
relatedProjects: 'Related Projects'
|
||||
},
|
||||
noResults: {
|
||||
title: 'No projects found',
|
||||
description: 'Try modifying your search or filter criteria.'
|
||||
@@ -264,34 +279,57 @@ export default {
|
||||
// Project data
|
||||
projectData: {
|
||||
'virtual-tour': {
|
||||
title: 'Virtual Tour Platform - 3D Interactive Experience',
|
||||
description: 'Interactive virtual tour platform built with Vue.js and Three.js. Immersive 3D experiences for real estate, museums, and businesses.',
|
||||
longDescription: 'Advanced virtual tour platform featuring 360-degree panoramas, interactive hotspots, and smooth navigation. Built with Vue.js for the frontend, Three.js for 3D rendering, and Node.js backend. Optimized for performance with lazy loading and WebGL acceleration. Perfect for real estate showcases, virtual museums, and business tours.'
|
||||
title: 'Virtual Tour - Interactive 360° Experience',
|
||||
description: 'My high school teacher and me had an idea to create a Virtual tour with 360° videos to allow everyone to visit the school from the web.',
|
||||
longDescription: 'Collaborative project with my high school teacher to create an immersive virtual tour experience of our school. Uses 360° videos to provide interactive navigation and allow prospective students and parents to explore the school facilities remotely. Intuitive interface enabling exploration of different spaces: classrooms, laboratories, common areas, and sports facilities.',
|
||||
buttons: {
|
||||
visit: 'Visit'
|
||||
}
|
||||
},
|
||||
'xinko': {
|
||||
title: 'Xinko - Multi-Platform Bot Framework',
|
||||
description: 'Versatile bot framework supporting Discord, Telegram, and Slack. Built with Node.js and TypeScript for scalable bot development.',
|
||||
longDescription: 'Xinko is a powerful multi-platform bot framework designed for developers. Features include unified API across platforms, plugin system, database abstraction, and comprehensive documentation. Built with Node.js, TypeScript, and modern JavaScript practices. Supports Discord.js, Telegram Bot API, and Slack SDK with a single codebase.'
|
||||
title: 'Xinko - Multipurpose Discord Bot',
|
||||
description: 'Xinko is a multipurpose bot that can help you create and manage your discord servers with ease and fun. It has many commands and features.',
|
||||
longDescription: 'Comprehensive Discord bot designed to simplify server management. Xinko offers a wide range of commands for moderation, entertainment, utility, and community management. User-friendly interface with advanced permission system, modern slash commands, and integration with various APIs. Perfect for communities of all sizes looking to automate and enhance their Discord experience.',
|
||||
buttons: {
|
||||
invite: 'Invite'
|
||||
}
|
||||
},
|
||||
'image-manipulation': {
|
||||
title: 'Image Manipulation API - NPM Package',
|
||||
description: 'Popular NPM package for programmatic image manipulation. Canvas-based image generation with 100k+ downloads and active community.',
|
||||
longDescription: 'Comprehensive image manipulation library for Node.js applications. Features include meme generation, filters, effects, text overlay, and format conversion. Originally developed as a REST API, now available as an open-source NPM package. Used by Discord bots, web applications, and automation tools. Supports JPG, PNG, GIF, and WebP formats with streaming capabilities.'
|
||||
title: 'Image Manipulation - NPM Package',
|
||||
description: 'Discord Image Generation: NPM package for code-based image manipulation. Originally an API, now open-source.',
|
||||
longDescription: 'Open-source NPM package for programmatic image generation and manipulation. Originally developed as a proprietary API, then made available to the community. Offers advanced image processing features: meme generation, filters, visual effects, and custom compositions. Particularly popular in the Discord bot ecosystem for creating dynamic and interactive visual content.',
|
||||
buttons: {
|
||||
repository: 'Repository',
|
||||
'npm package': 'NPM Package'
|
||||
}
|
||||
},
|
||||
'primate-web-admin': {
|
||||
title: 'Primate Web Admin - Enterprise Deployment Tool',
|
||||
description: 'Modern web interface for Primate deployment system. Enterprise-grade software deployment and management for Windows infrastructure.',
|
||||
longDescription: 'Professional web administration panel for Primate, a Munki-like deployment tool for Windows environments. Built with Vue.js frontend and RESTful API backend. Features include package management, deployment scheduling, client monitoring, and detailed reporting. Designed for IT administrators managing large Windows deployments with role-based access control and audit logging.'
|
||||
title: 'Primate Web Admin - Management Interface',
|
||||
description: 'Primate Web Admin is a Web interface to manage Primate that is a Munki-like deployment tool for Windows.',
|
||||
longDescription: 'Modern web administration interface for Primate, a software deployment system for Windows environments. Inspired by Munki (macOS solution), Primate Web Admin offers centralized management of software deployments on Windows infrastructure. Intuitive interface for package management, update scheduling, client monitoring, and detailed reporting generation.',
|
||||
buttons: {}
|
||||
},
|
||||
'instagram-bot': {
|
||||
title: 'Instagram Bot - Automated Content Generation',
|
||||
description: 'Feature-rich Instagram bot with image generation commands. Built with Insta.js for stories, posts, and DM automation.',
|
||||
longDescription: 'Advanced Instagram automation bot developed with Insta.js framework. Features custom image generation commands (!stonk, !invert, !meme), story interactions, automated posting, and DM management. Includes rate limiting, proxy support, and account safety features. Perfect for content creators and social media managers looking to automate their Instagram presence.'
|
||||
title: 'Instagram Bot - Full Automation',
|
||||
description: 'Fully functional Instagram bot using Insta.js by androz2091. It has many commands. Generate images with commands like: !stonk or !invert.',
|
||||
longDescription: 'Instagram automation bot developed with androz2091\'s Insta.js library. Offers a complete range of automation features: content publishing, follower interaction, custom image generation, and direct message management. Includes specialized commands for meme creation and visual effects (!stonk, !invert) as well as moderation tools and performance analytics.',
|
||||
buttons: {
|
||||
repository: 'Repository'
|
||||
}
|
||||
},
|
||||
'crowdin-status-bot': {
|
||||
title: 'Crowdin Status Bot - Translation Progress Tracker',
|
||||
description: 'Discord bot for real-time Crowdin translation monitoring. Automated status updates and progress tracking for localization teams.',
|
||||
longDescription: 'Specialized Discord bot that integrates with Crowdin API to provide real-time translation progress updates. Features include automated status messages, progress bars, contributor leaderboards, and milestone notifications. Essential tool for open-source projects and localization teams managing translations across multiple languages. Supports webhooks and custom notification rules.'
|
||||
title: 'Crowdin Status Bot - Translation Tracker',
|
||||
description: 'A bot that fetches Crowdin translation status and updates Discord messages with the latest status. Stay informed on progress!',
|
||||
longDescription: 'Discord bot specialized in automatic monitoring of Crowdin translation projects. Connects to Crowdin API to retrieve real-time progress statistics and automatically updates Discord messages with the latest information. Essential tool for localization teams and multilingual open-source projects, keeping the community informed about translation progress and encouraging contributor participation.',
|
||||
buttons: {
|
||||
repository: 'Repository'
|
||||
}
|
||||
},
|
||||
'flowboard': {
|
||||
title: 'FlowBoard - Trello clone',
|
||||
description: 'FlowBoard is a complete project management solution for streamlining tasks, team collaboration, timeline management, and progress tracking with detailed analytics.',
|
||||
longDescription: 'FlowBoard revolutionizes team collaboration and project management with its comprehensive suite of tools. Built with modern web technologies, it offers an intuitive interface for organizing tasks, managing timelines, and tracking progress. The platform features customizable boards, real-time collaboration, advanced analytics, and seamless communication tools. Perfect for teams of all sizes looking to boost productivity and streamline their workflow processes.',
|
||||
buttons: {}
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -68,6 +68,21 @@ export default {
|
||||
npmpackage: 'Package NPM',
|
||||
viewProject: 'Voir les Détails'
|
||||
},
|
||||
projectDetail: {
|
||||
backToProjects: 'Retour aux Projets',
|
||||
viewDemo: 'Voir la Démo',
|
||||
sourceCode: 'Code Source',
|
||||
share: 'Partager',
|
||||
aboutProject: 'À propos du Projet',
|
||||
keyFeatures: 'Fonctionnalités Principales',
|
||||
technologiesUsed: 'Technologies Utilisées',
|
||||
gallery: 'Galerie',
|
||||
projectInfo: 'Informations du Projet',
|
||||
date: 'Date',
|
||||
category: 'Catégorie',
|
||||
status: 'Statut',
|
||||
relatedProjects: 'Projets Similaires'
|
||||
},
|
||||
noResults: {
|
||||
title: 'Aucun projet trouvé',
|
||||
description: 'Essayez de modifier vos critères de recherche ou de filtrage.'
|
||||
@@ -264,34 +279,57 @@ export default {
|
||||
// Project data
|
||||
projectData: {
|
||||
'virtual-tour': {
|
||||
title: 'Plateforme Virtual Tour - Expérience 3D Interactive',
|
||||
description: 'Plateforme de visite virtuelle interactive construite avec Vue.js et Three.js. Expériences 3D immersives pour l\'immobilier, les musées et les entreprises.',
|
||||
longDescription: 'Plateforme de visite virtuelle avancée avec panoramas à 360 degrés, points chauds interactifs et navigation fluide. Construite avec Vue.js pour le frontend, Three.js pour le rendu 3D et backend Node.js. Optimisée pour les performances avec lazy loading et accélération WebGL. Parfaite pour les présentations immobilières, musées virtuels et visites d\'entreprise.'
|
||||
title: 'Visite Virtuelle - Expérience 360° Interactive',
|
||||
description: 'Mon professeur de lycée et moi avons eu l\'idée de créer une visite virtuelle avec des vidéos 360° pour permettre à tous de visiter l\'école depuis le web.',
|
||||
longDescription: 'Projet collaboratif avec mon professeur de lycée pour créer une expérience de visite virtuelle immersive de notre établissement. Utilise des vidéos 360° pour offrir une navigation interactive et permettre aux futurs étudiants et parents de découvrir les installations scolaires à distance. Interface intuitive permettant d\'explorer les différents espaces : salles de classe, laboratoires, espaces communs et installations sportives.',
|
||||
buttons: {
|
||||
visit: 'Visiter'
|
||||
}
|
||||
},
|
||||
'xinko': {
|
||||
title: 'Xinko - Framework Bot Multi-Plateforme',
|
||||
description: 'Framework bot polyvalent supportant Discord, Telegram et Slack. Construit avec Node.js et TypeScript pour un développement de bot évolutif.',
|
||||
longDescription: 'Xinko est un framework bot multi-plateforme puissant conçu pour les développeurs. Les fonctionnalités incluent une API unifiée sur toutes les plateformes, système de plugins, abstraction de base de données et documentation complète. Construit avec Node.js, TypeScript et pratiques JavaScript modernes. Supporte Discord.js, Telegram Bot API et Slack SDK avec une seule base de code.'
|
||||
title: 'Xinko - Bot Discord Polyvalent',
|
||||
description: 'Xinko est un bot polyvalent qui peut vous aider à créer et gérer vos serveurs Discord avec facilité et plaisir. Il possède de nombreuses commandes et fonctionnalités.',
|
||||
longDescription: 'Bot Discord complet conçu pour simplifier la gestion des serveurs. Xinko offre une large gamme de commandes pour la modération, le divertissement, l\'utilitaire et la gestion communautaire. Interface conviviale avec système de permissions avancé, commandes slash modernes et intégration avec diverses APIs. Parfait pour les communautés de toutes tailles cherchant à automatiser et enrichir leur expérience Discord.',
|
||||
buttons: {
|
||||
invite: 'Inviter'
|
||||
}
|
||||
},
|
||||
'image-manipulation': {
|
||||
title: 'API Manipulation d\'Images - Package NPM',
|
||||
description: 'Package NPM populaire pour la manipulation d\'images programmatique. Génération d\'images basée sur Canvas avec 100k+ téléchargements et communauté active.',
|
||||
longDescription: 'Bibliothèque complète de manipulation d\'images pour applications Node.js. Les fonctionnalités incluent génération de memes, filtres, effets, superposition de texte et conversion de format. Développé à l\'origine comme API REST, maintenant disponible en package NPM open-source. Utilisé par des bots Discord, applications web et outils d\'automatisation. Supporte JPG, PNG, GIF et WebP avec capacités de streaming.'
|
||||
title: 'Manipulation d\'Images - Package NPM',
|
||||
description: 'Discord Image Generation : Package NPM pour la manipulation d\'images basée sur le code. Initialement une API, maintenant open-source.',
|
||||
longDescription: 'Package NPM open-source pour la génération et manipulation d\'images programmatique. Développé initialement comme API propriétaire, puis rendu disponible à la communauté. Offre des fonctionnalités avancées de traitement d\'images : génération de memes, filtres, effets visuels et compositions personnalisées. Particulièrement populaire dans l\'écosystème des bots Discord pour créer du contenu visuel dynamique et interactif.',
|
||||
buttons: {
|
||||
repository: 'Dépôt',
|
||||
'npm package': 'Package NPM'
|
||||
}
|
||||
},
|
||||
'primate-web-admin': {
|
||||
title: 'Primate Web Admin - Outil de Déploiement Entreprise',
|
||||
description: 'Interface web moderne pour système de déploiement Primate. Déploiement et gestion de logiciels de qualité entreprise pour infrastructure Windows.',
|
||||
longDescription: 'Panneau d\'administration web professionnel pour Primate, un outil de déploiement type Munki pour environnements Windows. Construit avec frontend Vue.js et backend API RESTful. Les fonctionnalités incluent gestion de packages, planification de déploiement, surveillance des clients et rapports détaillés. Conçu pour les administrateurs IT gérant de grands déploiements Windows avec contrôle d\'accès basé sur les rôles et journalisation d\'audit.'
|
||||
title: 'Primate Web Admin - Interface de Gestion',
|
||||
description: 'Primate Web Admin est une interface Web pour gérer Primate qui est un outil de déploiement similaire à Munki pour Windows.',
|
||||
longDescription: 'Interface d\'administration web moderne pour Primate, un système de déploiement de logiciels pour environnements Windows. Inspiré de Munki (solution macOS), Primate Web Admin offre une gestion centralisée des déploiements logiciels sur infrastructure Windows. Interface intuitive pour la gestion des packages, planification des mises à jour, surveillance des clients et génération de rapports détaillés.',
|
||||
buttons: {}
|
||||
},
|
||||
'instagram-bot': {
|
||||
title: 'Bot Instagram - Génération de Contenu Automatisée',
|
||||
description: 'Bot Instagram riche en fonctionnalités avec commandes de génération d\'images. Construit avec Insta.js pour stories, posts et automatisation DM.',
|
||||
longDescription: 'Bot d\'automatisation Instagram avancé développé avec le framework Insta.js. Comprend des commandes de génération d\'images personnalisées (!stonk, !invert, !meme), interactions stories, publication automatisée et gestion DM. Inclut limitation de débit, support proxy et fonctionnalités de sécurité du compte. Parfait pour les créateurs de contenu et gestionnaires de médias sociaux cherchant à automatiser leur présence Instagram.'
|
||||
title: 'Bot Instagram - Automatisation Complète',
|
||||
description: 'Bot Instagram entièrement fonctionnel utilisant Insta.js par androz2091. Il possède de nombreuses commandes. Génère des images avec des commandes comme : !stonk ou !invert.',
|
||||
longDescription: 'Bot d\'automatisation Instagram développé avec la bibliothèque Insta.js d\'androz2091. Offre une gamme complète de fonctionnalités d\'automatisation : publication de contenu, interaction avec les followers, génération d\'images personnalisées et gestion des messages directs. Inclut des commandes spécialisées pour la création de memes et effets visuels (!stonk, !invert) ainsi que des outils de modération et d\'analyse des performances.',
|
||||
buttons: {
|
||||
repository: 'Dépôt'
|
||||
}
|
||||
},
|
||||
'crowdin-status-bot': {
|
||||
title: 'Bot de Statut Crowdin - Suivi de Progression des Traductions',
|
||||
description: 'Bot Discord pour surveillance en temps réel des traductions Crowdin. Mises à jour automatiques du statut et suivi de progression pour équipes de localisation.',
|
||||
longDescription: 'Bot Discord spécialisé qui s\'intègre avec l\'API Crowdin pour fournir des mises à jour de progression de traduction en temps réel. Les fonctionnalités incluent messages de statut automatisés, barres de progression, classements des contributeurs et notifications de jalons. Outil essentiel pour les projets open-source et équipes de localisation gérant des traductions dans plusieurs langues. Supporte webhooks et règles de notification personnalisées.'
|
||||
title: 'Bot de Statut Crowdin - Suivi des Traductions',
|
||||
description: 'Un bot qui récupère le statut des traductions Crowdin et met à jour les messages Discord avec le dernier statut. Restez informé des progrès !',
|
||||
longDescription: 'Bot Discord spécialisé dans le suivi automatique des projets de traduction Crowdin. Se connecte à l\'API Crowdin pour récupérer les statistiques de progression en temps réel et met à jour automatiquement les messages Discord avec les dernières informations. Outil essentiel pour les équipes de localisation et projets open-source multilingues, permettant de maintenir la communauté informée des avancées de traduction et d\'encourager la participation des contributeurs.',
|
||||
buttons: {
|
||||
repository: 'Dépôt'
|
||||
}
|
||||
},
|
||||
'flowboard': {
|
||||
title: 'FlowBoard - Clone de Trello ',
|
||||
description: 'Clone de Trello moderne pour la gestion de projet et la collaboration d\'équipe. Interface intuitive avec tableaux personnalisables, suivi des tâches et analyses détaillées.',
|
||||
longDescription: 'FlowBoard révolutionne la collaboration d\'équipe et la gestion de projet avec sa suite complète d\'outils. Construite avec des technologies web modernes, elle offre une interface intuitive pour organiser les tâches, gérer les délais et suivre les progrès. La plateforme propose des tableaux personnalisables, une collaboration en temps réel, des analyses avancées et des outils de communication transparents. Parfaite pour les équipes de toutes tailles cherchant à booster leur productivité et rationaliser leurs processus de travail.',
|
||||
buttons: {}
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -2,10 +2,11 @@
|
||||
import { computed } from 'vue'
|
||||
import { useSeo } from '@/composables/useSeo'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { projects } from '@/data/projects'
|
||||
import { useProjects } from '@/composables/useProjects'
|
||||
import ProjectCard from '@/components/ProjectCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projects } = useProjects()
|
||||
|
||||
// Enhanced SEO with structured data
|
||||
useSeo({
|
||||
@@ -39,7 +40,7 @@ useSeo({
|
||||
|
||||
// Featured projects
|
||||
const featuredProjects = computed(() => {
|
||||
return projects.filter(project => project.featured).slice(0, 3)
|
||||
return projects.value.filter(project => project.featured).slice(0, 3)
|
||||
})
|
||||
|
||||
// Services data
|
||||
|
@@ -3,24 +3,30 @@ import { computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useSeo } from '@/composables/useSeo'
|
||||
import { useAssets } from '@/composables/useAssets'
|
||||
import { projects } from '@/data/projects'
|
||||
import { useProjects } from '@/composables/useProjects'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGallery } from '@/composables/useGallery'
|
||||
import TechBadge from '@/components/TechBadge.vue'
|
||||
import GalleryModal from '@/components/GalleryModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { getImageUrl } = useAssets()
|
||||
const { projects } = useProjects()
|
||||
const { t } = useI18n()
|
||||
const gallery = useGallery()
|
||||
|
||||
// Find project by ID
|
||||
const project = computed(() => {
|
||||
const id = route.params.id as string
|
||||
return projects.find(p => p.id === id)
|
||||
return projects.value.find(p => p.id === id)
|
||||
})
|
||||
|
||||
// Related projects
|
||||
const relatedProjects = computed(() => {
|
||||
if (!project.value) return []
|
||||
|
||||
return projects
|
||||
return projects.value
|
||||
.filter(p => p.id !== project.value?.id && p.category === project.value?.category)
|
||||
.slice(0, 3)
|
||||
})
|
||||
@@ -72,7 +78,7 @@ onMounted(() => {
|
||||
<svg class="breadcrumb-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
Retour aux projets
|
||||
{{ t('projects.projectDetail.backToProjects') }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@@ -101,7 +107,7 @@ onMounted(() => {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||
</svg>
|
||||
Voir la démo
|
||||
{{ t('projects.projectDetail.viewDemo') }}
|
||||
</a>
|
||||
|
||||
<a v-if="project.githubUrl" :href="project.githubUrl" target="_blank" rel="noopener noreferrer"
|
||||
@@ -110,7 +116,7 @@ onMounted(() => {
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
Code source
|
||||
{{ t('projects.projectDetail.sourceCode') }}
|
||||
</a>
|
||||
|
||||
<!-- Buttons from project data -->
|
||||
@@ -129,7 +135,7 @@ onMounted(() => {
|
||||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z">
|
||||
</path>
|
||||
</svg>
|
||||
Partager
|
||||
{{ t('projects.projectDetail.share') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,7 +152,7 @@ onMounted(() => {
|
||||
<div class="main-content">
|
||||
<!-- Project Details -->
|
||||
<div class="content-section">
|
||||
<h2 class="section-title">À propos du projet</h2>
|
||||
<h2 class="section-title">{{ t('projects.projectDetail.aboutProject') }}</h2>
|
||||
<div class="section-content">
|
||||
<p class="project-long-description">
|
||||
{{ project.longDescription || project.description }}
|
||||
@@ -154,7 +160,7 @@ onMounted(() => {
|
||||
|
||||
<!-- Features -->
|
||||
<div v-if="project.features" class="features-list">
|
||||
<h3 class="features-title">Fonctionnalités principales</h3>
|
||||
<h3 class="features-title">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
||||
<ul class="features">
|
||||
<li v-for="feature in project.features" :key="feature" class="feature-item">
|
||||
<svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -169,7 +175,7 @@ onMounted(() => {
|
||||
|
||||
<!-- Technologies -->
|
||||
<div v-if="project.technologies" class="content-section">
|
||||
<h2 class="section-title">Technologies utilisées</h2>
|
||||
<h2 class="section-title">{{ t('projects.projectDetail.technologiesUsed') }}</h2>
|
||||
<div class="section-content">
|
||||
<div class="tech-grid">
|
||||
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" class="tech-item" />
|
||||
@@ -179,11 +185,20 @@ onMounted(() => {
|
||||
|
||||
<!-- Gallery -->
|
||||
<div v-if="project.gallery" class="content-section">
|
||||
<h2 class="section-title">Galerie</h2>
|
||||
<h2 class="section-title">{{ t('projects.projectDetail.gallery') }}</h2>
|
||||
<div class="section-content">
|
||||
<div class="gallery-grid">
|
||||
<div v-for="(image, index) in project.gallery" :key="index" class="gallery-item">
|
||||
<div v-for="(image, index) in project.gallery" :key="index" class="gallery-item"
|
||||
@click="gallery.openGallery(project.gallery, index)">
|
||||
<img :src="getImageUrl(image)" :alt="`${project.title} - Image ${index + 1}`" class="gallery-image">
|
||||
<div class="gallery-overlay">
|
||||
<svg class="gallery-expand-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,20 +209,20 @@ onMounted(() => {
|
||||
<aside class="sidebar">
|
||||
<!-- Project Info Card -->
|
||||
<div class="info-card">
|
||||
<h3 class="info-title">Informations du projet</h3>
|
||||
<h3 class="info-title">{{ t('projects.projectDetail.projectInfo') }}</h3>
|
||||
<div class="info-list">
|
||||
<div v-if="project.date" class="info-item">
|
||||
<span class="info-label">Date</span>
|
||||
<span class="info-label">{{ t('projects.projectDetail.date') }}</span>
|
||||
<span class="info-value">{{ project.date }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="project.category" class="info-item">
|
||||
<span class="info-label">Catégorie</span>
|
||||
<span class="info-label">{{ t('projects.projectDetail.category') }}</span>
|
||||
<span class="info-value">{{ project.category }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="project.status" class="info-item">
|
||||
<span class="info-label">Statut</span>
|
||||
<span class="info-label">{{ t('projects.projectDetail.status') }}</span>
|
||||
<span class="info-value">{{ project.status }}</span>
|
||||
</div>
|
||||
|
||||
@@ -220,10 +235,10 @@ onMounted(() => {
|
||||
|
||||
<!-- Related Projects -->
|
||||
<div v-if="relatedProjects.length > 0" class="related-projects">
|
||||
<h3 class="related-title">Projets similaires</h3>
|
||||
<h3 class="related-title">{{ t('projects.projectDetail.relatedProjects') }}</h3>
|
||||
<div class="related-list">
|
||||
<router-link v-for="relatedProject in relatedProjects" :key="relatedProject.id"
|
||||
:to="`/projects/${relatedProject.id}`" class="related-item">
|
||||
:to="`/project/${relatedProject.id}`" class="related-item">
|
||||
<img v-if="relatedProject.image" :src="getImageUrl(relatedProject.image)" :alt="relatedProject.title"
|
||||
class="related-image">
|
||||
<div class="related-content">
|
||||
@@ -237,6 +252,13 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Gallery Modal -->
|
||||
<GalleryModal :is-open="gallery.isOpen.value" :current-image="gallery.currentImage.value"
|
||||
:current-index="gallery.currentIndex.value" :total-images="gallery.images.value.length"
|
||||
:has-next="gallery.hasNext.value" :has-previous="gallery.hasPrevious.value" :project-title="project?.title || ''"
|
||||
@close="gallery.closeGallery" @next="gallery.nextImage" @previous="gallery.previousImage"
|
||||
@go-to="gallery.goToImage" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
@@ -2,10 +2,11 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useSeo } from '@/composables/useSeo'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { projects } from '@/data/projects'
|
||||
import { useProjects } from '@/composables/useProjects'
|
||||
import ProjectCard from '@/components/ProjectCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projects } = useProjects()
|
||||
|
||||
// SEO
|
||||
useSeo({
|
||||
@@ -19,7 +20,7 @@ useSeo({
|
||||
'name': 'Web Development Portfolio Projects',
|
||||
'description': 'Browse professional web development projects including Vue.js applications, React websites, Node.js APIs, and Discord bots',
|
||||
'url': 'https://killiandalcin.fr/projects',
|
||||
'hasPart': projects.map(project => ({
|
||||
'hasPart': projects.value.map(project => ({
|
||||
'@type': 'CreativeWork',
|
||||
'name': project.title,
|
||||
'description': project.description,
|
||||
@@ -36,13 +37,13 @@ const sortBy = ref('date')
|
||||
|
||||
// Get unique categories
|
||||
const categories = computed(() => {
|
||||
const cats = ['all', ...new Set(projects.map(p => p.category).filter(Boolean))]
|
||||
const cats = ['all', ...new Set(projects.value.map(p => p.category).filter(Boolean))]
|
||||
return cats
|
||||
})
|
||||
|
||||
// Filtered and sorted projects
|
||||
const filteredProjects = computed(() => {
|
||||
let filtered = projects
|
||||
let filtered = projects.value
|
||||
|
||||
// Filter by search query
|
||||
if (searchQuery.value) {
|
||||
@@ -74,8 +75,8 @@ const filteredProjects = computed(() => {
|
||||
})
|
||||
|
||||
// Stats
|
||||
const totalProjects = computed(() => projects.length)
|
||||
const featuredProjects = computed(() => projects.filter(p => p.featured).length)
|
||||
const totalProjects = computed(() => projects.value.length)
|
||||
const featuredProjects = computed(() => projects.value.filter(p => p.featured).length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -273,6 +273,8 @@
|
||||
box-shadow: var(--shadow-lg);
|
||||
transition: all var(--transition-fast);
|
||||
background: var(--bg-secondary);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-item:hover {
|
||||
@@ -280,10 +282,41 @@
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
.gallery-item:hover .gallery-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
transition: transform var(--transition-normal);
|
||||
}
|
||||
|
||||
.gallery-item:hover .gallery-image {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.gallery-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-normal);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.gallery-expand-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: white;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
Reference in New Issue
Block a user