Ajout de la section galerie photo et nettoyage du template
Galerie photo :
- Ajout du layout photo avec slideshow plein écran
- Navigation par catégories (portraits, paysages, nature, etc.)
- Section "Fil Photo" avec posts illustrés (photoBlogPosts)
- Lightbox pour les albums de catégories
- Composants : Slideshow, CategoryNav, CategoryGrid, Lightbox, MasonryGallery
Nettoyage :
- Suppression du contenu démo du template (posts, images, about)
- Consolidation src/collections/ dans src/data/
- Suppression du config.js dupliqué (garde config.ts)
- Nettoyage des assets inutilisés (posts/, experiences/)
Corrections :
- Favicon récupéré du site actuel
- Chemins favicon corrigés dans les layouts
UI :
- Page d'accueil mise à jour
- Header/Footer simplifiés
- Nouvelle page À propos
2026-01-07 01:45:40 +01:00
|
|
|
---
|
|
|
|
|
import CategoryNav from './CategoryNav.astro';
|
|
|
|
|
import Slideshow from './Slideshow.astro';
|
|
|
|
|
import SlideControls from './SlideControls.astro';
|
|
|
|
|
import favorites from '../../data/favorites.json';
|
|
|
|
|
|
|
|
|
|
// Auto-détection des images
|
|
|
|
|
const allImages = import.meta.glob<{ default: ImageMetadata }>('/src/assets/images/photos/categories/**/*.{jpg,jpeg,png,webp}');
|
|
|
|
|
|
|
|
|
|
// Charger les images favorites
|
|
|
|
|
const favoriteImages = await Promise.all(
|
|
|
|
|
favorites.map(async (relativePath: string) => {
|
|
|
|
|
const fullPath = `/src/assets/images/photos/categories/${relativePath}`;
|
|
|
|
|
const loader = allImages[fullPath];
|
|
|
|
|
if (!loader) {
|
|
|
|
|
console.warn(`Image favorite non trouvée: ${fullPath}`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const img = await loader();
|
|
|
|
|
const filename = relativePath.split('/').pop() || '';
|
|
|
|
|
const title = filename
|
|
|
|
|
.replace(/\.[^/.]+$/, '')
|
|
|
|
|
.replace(/-/g, ' ')
|
|
|
|
|
.replace(/_/g, ' ');
|
|
|
|
|
return {
|
|
|
|
|
src: img.default, // ImageMetadata pour <Image>
|
|
|
|
|
alt: title,
|
|
|
|
|
title: title,
|
|
|
|
|
category: relativePath.split('/')[0]
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Filtrer les nulls (images non trouvées)
|
|
|
|
|
const images = favoriteImages.filter(img => img !== null);
|
|
|
|
|
|
|
|
|
|
// Préparation des données pour JavaScript (avec URL au lieu de ImageMetadata)
|
|
|
|
|
const imagesForJS = JSON.stringify(images.map(img => ({
|
|
|
|
|
src: img.src.src, // Extraire l'URL de ImageMetadata
|
|
|
|
|
alt: img.alt,
|
|
|
|
|
title: img.title,
|
|
|
|
|
category: img.category
|
|
|
|
|
})));
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<div id="photo-gallery" class="gallery-container">
|
|
|
|
|
<CategoryNav currentCategory="" />
|
|
|
|
|
<Slideshow images={images} />
|
|
|
|
|
<SlideControls />
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-07 11:15:26 +01:00
|
|
|
<script is:inline define:vars={{ imagesForJS }}>
|
Ajout de la section galerie photo et nettoyage du template
Galerie photo :
- Ajout du layout photo avec slideshow plein écran
- Navigation par catégories (portraits, paysages, nature, etc.)
- Section "Fil Photo" avec posts illustrés (photoBlogPosts)
- Lightbox pour les albums de catégories
- Composants : Slideshow, CategoryNav, CategoryGrid, Lightbox, MasonryGallery
Nettoyage :
- Suppression du contenu démo du template (posts, images, about)
- Consolidation src/collections/ dans src/data/
- Suppression du config.js dupliqué (garde config.ts)
- Nettoyage des assets inutilisés (posts/, experiences/)
Corrections :
- Favicon récupéré du site actuel
- Chemins favicon corrigés dans les layouts
UI :
- Page d'accueil mise à jour
- Header/Footer simplifiés
- Nouvelle page À propos
2026-01-07 01:45:40 +01:00
|
|
|
window.galleryData = {
|
|
|
|
|
images: JSON.parse(imagesForJS),
|
|
|
|
|
currentCategory: 'favorites'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// PhotoGallerySlideshow controller
|
|
|
|
|
class PhotoGallerySlideshow {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.images = window.galleryData?.images || [];
|
|
|
|
|
this.currentIndex = 0;
|
|
|
|
|
this.isPlaying = true;
|
|
|
|
|
this.autoplayInterval = null;
|
|
|
|
|
this.transitionDuration = 800;
|
|
|
|
|
this.autoplaySpeed = 5000;
|
|
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
if (this.images.length === 0) {
|
|
|
|
|
console.warn('Aucune image trouvée dans galleryData');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.bindEvents();
|
|
|
|
|
this.startAutoplay();
|
|
|
|
|
this.preloadNextImages();
|
|
|
|
|
this.updatePlayPauseButton();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindEvents() {
|
|
|
|
|
document.getElementById('prev-btn')?.addEventListener('click', () => this.prevSlide());
|
|
|
|
|
document.getElementById('next-btn')?.addEventListener('click', () => this.nextSlide());
|
|
|
|
|
document.getElementById('play-pause-btn')?.addEventListener('click', () => this.toggleAutoplay());
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.indicator').forEach((btn, index) => {
|
|
|
|
|
btn.addEventListener('click', () => this.goToSlide(index));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
|
switch(e.key) {
|
|
|
|
|
case 'ArrowLeft':
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.prevSlide();
|
|
|
|
|
break;
|
|
|
|
|
case 'ArrowRight':
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.nextSlide();
|
|
|
|
|
break;
|
|
|
|
|
case ' ':
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.toggleAutoplay();
|
|
|
|
|
break;
|
|
|
|
|
case 'Escape':
|
|
|
|
|
if (window.history.length > 1) {
|
|
|
|
|
history.back();
|
|
|
|
|
} else {
|
|
|
|
|
window.location.href = '/';
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.setupTouchEvents();
|
|
|
|
|
|
|
|
|
|
if (!this.isMobile()) {
|
|
|
|
|
const slideshow = document.getElementById('slideshow-container');
|
|
|
|
|
slideshow?.addEventListener('mouseenter', () => this.pauseAutoplay());
|
|
|
|
|
slideshow?.addEventListener('mouseleave', () => this.resumeAutoplay());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupTouchEvents() {
|
|
|
|
|
const slideshow = document.getElementById('slideshow-container');
|
|
|
|
|
if (!slideshow) return;
|
|
|
|
|
|
|
|
|
|
let startX = 0;
|
|
|
|
|
let startY = 0;
|
|
|
|
|
|
|
|
|
|
slideshow.addEventListener('touchstart', (e) => {
|
|
|
|
|
startX = e.touches[0].clientX;
|
|
|
|
|
startY = e.touches[0].clientY;
|
|
|
|
|
}, { passive: true });
|
|
|
|
|
|
|
|
|
|
slideshow.addEventListener('touchend', (e) => {
|
|
|
|
|
const endX = e.changedTouches[0].clientX;
|
|
|
|
|
const endY = e.changedTouches[0].clientY;
|
|
|
|
|
const deltaX = startX - endX;
|
|
|
|
|
const deltaY = startY - endY;
|
|
|
|
|
|
|
|
|
|
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
|
|
|
|
|
if (deltaX > 0) {
|
|
|
|
|
this.nextSlide();
|
|
|
|
|
} else {
|
|
|
|
|
this.prevSlide();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, { passive: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goToSlide(index) {
|
|
|
|
|
if (index === this.currentIndex || index < 0 || index >= this.images.length) return;
|
|
|
|
|
|
|
|
|
|
const currentSlide = document.querySelector('.slide.active');
|
|
|
|
|
const nextSlide = document.querySelector(`[data-index="${index}"]`);
|
|
|
|
|
const currentIndicator = document.querySelector('.indicator.active');
|
|
|
|
|
const nextIndicator = document.querySelector(`[data-slide="${index}"]`);
|
|
|
|
|
|
|
|
|
|
if (currentSlide && nextSlide) {
|
|
|
|
|
currentSlide.classList.remove('active');
|
|
|
|
|
nextSlide.classList.add('active');
|
|
|
|
|
currentIndicator?.classList.remove('active');
|
|
|
|
|
nextIndicator?.classList.add('active');
|
|
|
|
|
this.currentIndex = index;
|
|
|
|
|
this.preloadNextImages();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextSlide() {
|
|
|
|
|
const nextIndex = (this.currentIndex + 1) % this.images.length;
|
|
|
|
|
this.goToSlide(nextIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prevSlide() {
|
|
|
|
|
const prevIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
|
|
|
|
|
this.goToSlide(prevIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleAutoplay() {
|
|
|
|
|
if (this.isPlaying) {
|
|
|
|
|
this.pauseAutoplay();
|
|
|
|
|
} else {
|
|
|
|
|
this.startAutoplay();
|
|
|
|
|
}
|
|
|
|
|
this.updatePlayPauseButton();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startAutoplay() {
|
|
|
|
|
if (this.autoplayInterval) return;
|
|
|
|
|
this.isPlaying = true;
|
|
|
|
|
this.autoplayInterval = setInterval(() => this.nextSlide(), this.autoplaySpeed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pauseAutoplay() {
|
|
|
|
|
if (this.autoplayInterval) {
|
|
|
|
|
clearInterval(this.autoplayInterval);
|
|
|
|
|
this.autoplayInterval = null;
|
|
|
|
|
}
|
|
|
|
|
this.isPlaying = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resumeAutoplay() {
|
|
|
|
|
if (!this.isPlaying) return;
|
|
|
|
|
this.startAutoplay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updatePlayPauseButton() {
|
|
|
|
|
const playIcon = document.querySelector('.play-icon');
|
|
|
|
|
const pauseIcon = document.querySelector('.pause-icon');
|
|
|
|
|
|
|
|
|
|
if (this.isPlaying) {
|
|
|
|
|
playIcon?.classList.add('hidden');
|
|
|
|
|
pauseIcon?.classList.remove('hidden');
|
|
|
|
|
} else {
|
|
|
|
|
playIcon?.classList.remove('hidden');
|
|
|
|
|
pauseIcon?.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
preloadNextImages() {
|
|
|
|
|
for (let i = 1; i <= 2; i++) {
|
|
|
|
|
const nextIndex = (this.currentIndex + i) % this.images.length;
|
|
|
|
|
const nextImage = this.images[nextIndex];
|
|
|
|
|
|
|
|
|
|
if (nextImage && !document.querySelector(`img[src="${nextImage.src}"]`)) {
|
|
|
|
|
const img = new Image();
|
|
|
|
|
img.src = nextImage.src;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isMobile() {
|
|
|
|
|
return window.innerWidth <= 768;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
new PhotoGallerySlideshow();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.gallery-container {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
background: #000000;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
</style>
|