Compare commits

..

7 commits

Author SHA1 Message Date
261323b4ce Remontée des boutons prev/next au-dessus du footer sur mobile 2026-02-18 00:58:26 +01:00
90e673901e Réorganisation du photo blog avec URLs /photo/blog/YYYY/slug
Les images et les fichiers de contenu sont maintenant organisés par année (blog/2015/enigma/ au lieu de blog/enigma/) pour mieux s'y retrouver avec un volume croissant de posts. Le coverImage dans les frontmatters ne contient plus qu'un nom de fichier, résolu dynamiquement via import.meta.glob.
2026-02-18 00:55:13 +01:00
835519a0c2 Extraction du footer photo en composant et correction responsive mobile 2026-02-18 00:49:21 +01:00
37339f4ebe Ajout lien Photo dans le header avec séparateur et correction alignement mobile 2026-02-18 00:12:45 +01:00
d01d42fbfb Ajout d'un lien Photo dans le header de la navigation photo 2026-02-17 23:49:07 +01:00
c3cc6915db Ajout de padding entre les cartes sur /photo/blog 2026-02-17 23:33:12 +01:00
3566488a0a Correction affichage dates et style minimaliste sur /photo/blog 2026-02-17 23:18:32 +01:00
50 changed files with 186 additions and 135 deletions

View file

@ -4,6 +4,7 @@ import tailwind from "@astrojs/tailwind";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
devToolbar: { enabled: false },
integrations: [tailwind()], integrations: [tailwind()],
i18n: { i18n: {
defaultLocale: "fr", defaultLocale: "fr",

View file

@ -20,6 +20,8 @@ const categories = sortedCategories.map(cat => ({ id: cat.id, title: cat.data.ti
<HomeIcon size={16} /> <HomeIcon size={16} />
<span class="site-name">Jalil Arfaoui</span> <span class="site-name">Jalil Arfaoui</span>
</a> </a>
<span class="nav-separator"></span>
<a href="/photo" class="nav-link">Photo</a>
</div> </div>
<!-- Bouton hamburger (mobile) --> <!-- Bouton hamburger (mobile) -->
@ -69,6 +71,13 @@ const categories = sortedCategories.map(cat => ({ id: cat.id, title: cat.data.ti
height: 53px; height: 53px;
} }
.site-title {
display: flex;
align-items: center;
gap: 10px;
height: 100%;
}
.site-link { .site-link {
display: flex; display: flex;
align-items: center; align-items: center;
@ -84,6 +93,11 @@ const categories = sortedCategories.map(cat => ({ id: cat.id, title: cat.data.ti
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
} }
.site-title .nav-link {
padding-bottom: 0;
font-size: 18px;
}
.hamburger { .hamburger {
display: none; display: none;
flex-direction: column; flex-direction: column;
@ -193,14 +207,14 @@ const categories = sortedCategories.map(cat => ({ id: cat.id, title: cat.data.ti
text-align: center; text-align: center;
} }
.nav-link { .nav-menu .nav-link {
display: block; display: block;
padding: 15px 20px; padding: 15px 20px;
font-size: 18px; font-size: 18px;
border-bottom: none; border-bottom: none;
} }
.nav-link.active { .nav-menu .nav-link.active {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
border-bottom: none; border-bottom: none;
} }
@ -220,7 +234,8 @@ const categories = sortedCategories.map(cat => ({ id: cat.id, title: cat.data.ti
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.site-name { .site-name,
.site-title .nav-separator {
display: none; display: none;
} }
} }

View file

@ -0,0 +1,63 @@
<footer class="photo-footer">
<div class="photo-footer-inner">
<div class="photo-footer-links">
<a href="/a-propos">À propos</a>
<a href="mailto:jalil@arfaoui.net">Contact</a>
<a href="https://instagram.com/l.i.l.a.j" target="_blank" rel="noopener noreferrer">Instagram</a>
</div>
<div class="photo-footer-copy">
© Jalil Arfaoui <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener noreferrer">Creative Commons CC-BY-NC 4.0</a>
</div>
</div>
</footer>
<style>
.photo-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 40;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(8px);
transition: transform 0.3s;
}
.photo-footer-inner {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 54px;
font-size: 16px;
color: rgba(255, 255, 255, 0.7);
}
.photo-footer-links {
display: flex;
gap: 15px;
}
.photo-footer-links a,
.photo-footer-copy,
.photo-footer-copy a {
color: rgba(255, 255, 255, 0.7);
text-decoration: none;
transition: color 0.2s;
}
.photo-footer-links a:hover,
.photo-footer-copy a:hover {
color: white;
}
@media (max-width: 640px) {
.photo-footer-inner {
flex-direction: column;
height: auto;
padding: 10px 20px;
gap: 4px;
font-size: 13px;
}
}
</style>

View file

@ -72,7 +72,7 @@
.prev-btn, .prev-btn,
.next-btn { .next-btn {
top: auto; top: auto;
bottom: 2rem; bottom: 5rem;
transform: none; transform: none;
} }

View file

@ -67,11 +67,11 @@ const talksCollection = defineCollection({
const photoBlogPostsCollection = defineCollection({ const photoBlogPostsCollection = defineCollection({
type: "content", type: "content",
schema: ({ image }) => z.object({ schema: z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
date: z.date(), date: z.date(),
coverImage: image(), coverImage: z.string(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
featured: z.boolean().default(false), featured: z.boolean().default(false),
draft: z.boolean().default(false), draft: z.boolean().default(false),

View file

@ -2,7 +2,7 @@
title: "تصوير إيرول" title: "تصوير إيرول"
description: "أراد إيرول صورًا له لإعداد كتاب أعمال. عملنا طوال اليوم لتنويع الأجواء..." description: "أراد إيرول صورًا له لإعداد كتاب أعمال. عملنا طوال اليوم لتنويع الأجواء..."
date: 2011-10-02 date: 2011-10-02
coverImage: "../../assets/images/photos/blog/eroll/18-Eroll-Shooting-1-19.jpg" coverImage: "18-Eroll-Shooting-1-19.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Shooting Eroll" title: "Shooting Eroll"
description: "Eroll wanted some photos of him in order to have a modeling book. We worked all day in order to have some ambiance variations..." description: "Eroll wanted some photos of him in order to have a modeling book. We worked all day in order to have some ambiance variations..."
date: 2011-10-02 date: 2011-10-02
coverImage: "../../assets/images/photos/blog/eroll/18-Eroll-Shooting-1-19.jpg" coverImage: "18-Eroll-Shooting-1-19.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Shooting Eroll" title: "Shooting Eroll"
description: "Eroll voulait des photos de lui pour constituer un book. On a travaillé toute la journée pour varier les ambiances..." description: "Eroll voulait des photos de lui pour constituer un book. On a travaillé toute la journée pour varier les ambiances..."
date: 2011-10-02 date: 2011-10-02
coverImage: "../../assets/images/photos/blog/eroll/18-Eroll-Shooting-1-19.jpg" coverImage: "18-Eroll-Shooting-1-19.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Inox Park Paris 2011" title: "Inox Park Paris 2011"
description: "بعد نجاحه في 2010، يعود مهرجان Inox Park Paris إلى جزيرة شاتو في نسخته الثانية. ثلاث مسارح، 15 دي جي، 12 ساعة من الحفل في الهواء الطلق: Tiësto، Joachim Garraud، Sven Väth، Steve Aoki..." description: "بعد نجاحه في 2010، يعود مهرجان Inox Park Paris إلى جزيرة شاتو في نسخته الثانية. ثلاث مسارح، 15 دي جي، 12 ساعة من الحفل في الهواء الطلق: Tiësto، Joachim Garraud، Sven Väth، Steve Aoki..."
date: 2011-09-10 date: 2011-09-10
coverImage: "../../assets/images/photos/blog/inox-park-2011/01-Inox-Park-Paris-Chatou-2011.jpg" coverImage: "01-Inox-Park-Paris-Chatou-2011.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Inox Park Paris 2011" title: "Inox Park Paris 2011"
description: "After its 2010 success, the Inox Park Paris Electro Festival is back to the island of Chatou for its second edition. Three stages, 15 DJs, 12 hours of outdoor party: Tiësto, Joachim Garraud, Sven Väth, Steve Aoki..." description: "After its 2010 success, the Inox Park Paris Electro Festival is back to the island of Chatou for its second edition. Three stages, 15 DJs, 12 hours of outdoor party: Tiësto, Joachim Garraud, Sven Väth, Steve Aoki..."
date: 2011-09-10 date: 2011-09-10
coverImage: "../../assets/images/photos/blog/inox-park-2011/01-Inox-Park-Paris-Chatou-2011.jpg" coverImage: "01-Inox-Park-Paris-Chatou-2011.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Inox Park Paris 2011" title: "Inox Park Paris 2011"
description: "Après son succès de 2010, le festival Inox Park Paris revient sur l'île de Chatou pour sa deuxième édition. Trois scènes, 15 DJs, 12 heures de fête en plein air : Tiësto, Joachim Garraud, Sven Väth, Steve Aoki..." description: "Après son succès de 2010, le festival Inox Park Paris revient sur l'île de Chatou pour sa deuxième édition. Trois scènes, 15 DJs, 12 heures de fête en plein air : Tiësto, Joachim Garraud, Sven Väth, Steve Aoki..."
date: 2011-09-10 date: 2011-09-10
coverImage: "../../assets/images/photos/blog/inox-park-2011/10-Inox-Park-Paris-Chatou-2011-7.jpg" coverImage: "10-Inox-Park-Paris-Chatou-2011-7.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "عملية المحفظة 2012" title: "عملية المحفظة 2012"
description: "توزيع محافظ مدرسية مجانية في مدارس محرومة من طرف جمعية محلية (JCI)، طنجة، المغرب." description: "توزيع محافظ مدرسية مجانية في مدارس محرومة من طرف جمعية محلية (JCI)، طنجة، المغرب."
date: 2012-09-30 date: 2012-09-30
coverImage: "../../assets/images/photos/blog/schoolbag-operation-2012/35-Moroccan-Schoolgirls.jpg" coverImage: "35-Moroccan-Schoolgirls.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Schoolbag Operation 2012" title: "Schoolbag Operation 2012"
description: "During a distribution of free schoolbags in poor schools by a local association (JCI), Tangier, Morocco." description: "During a distribution of free schoolbags in poor schools by a local association (JCI), Tangier, Morocco."
date: 2012-09-30 date: 2012-09-30
coverImage: "../../assets/images/photos/blog/schoolbag-operation-2012/35-Moroccan-Schoolgirls.jpg" coverImage: "35-Moroccan-Schoolgirls.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Opération Cartable 2012" title: "Opération Cartable 2012"
description: "Distribution gratuite de cartables dans des écoles défavorisées par une association locale (JCI), Tanger, Maroc." description: "Distribution gratuite de cartables dans des écoles défavorisées par une association locale (JCI), Tanger, Maroc."
date: 2012-09-30 date: 2012-09-30
coverImage: "../../assets/images/photos/blog/schoolbag-operation-2012/35-Moroccan-Schoolgirls.jpg" coverImage: "35-Moroccan-Schoolgirls.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "جولة في طنجة" title: "جولة في طنجة"
description: "جولة فوتوغرافية في شوارع طنجة." description: "جولة فوتوغرافية في شوارع طنجة."
date: 2012-05-26 date: 2012-05-26
coverImage: "../../assets/images/photos/blog/tangier-walk/01-Observer-le-changement.jpg" coverImage: "01-Observer-le-changement.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Tangier Walk" title: "Tangier Walk"
description: "A photographic walk through the streets of Tangier." description: "A photographic walk through the streets of Tangier."
date: 2012-05-26 date: 2012-05-26
coverImage: "../../assets/images/photos/blog/tangier-walk/01-Observer-le-changement.jpg" coverImage: "01-Observer-le-changement.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Balade à Tanger" title: "Balade à Tanger"
description: "Promenade photographique dans les rues de Tanger" description: "Promenade photographique dans les rues de Tanger"
date: 2012-05-26 date: 2012-05-26
coverImage: "../../assets/images/photos/blog/tangier-walk/01-Observer-le-changement.jpg" coverImage: "01-Observer-le-changement.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "هلسنكي" title: "هلسنكي"
description: "اختفى الثلج من هلسنكي وسرعان ما أفسح المجال للربيع..." description: "اختفى الثلج من هلسنكي وسرعان ما أفسح المجال للربيع..."
date: 2013-05-15 date: 2013-05-15
coverImage: "../../assets/images/photos/blog/helsinki/01-Library-of-University-of-Helsinki.jpg" coverImage: "01-Library-of-University-of-Helsinki.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Helsinki" title: "Helsinki"
description: "The snow has disappeared from Helsinki and quickly gave way to spring..." description: "The snow has disappeared from Helsinki and quickly gave way to spring..."
date: 2013-05-15 date: 2013-05-15
coverImage: "../../assets/images/photos/blog/helsinki/01-Library-of-University-of-Helsinki.jpg" coverImage: "01-Library-of-University-of-Helsinki.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Helsinki" title: "Helsinki"
description: "La neige a disparu d'Helsinki et a vite laissé place au printemps..." description: "La neige a disparu d'Helsinki et a vite laissé place au printemps..."
date: 2013-05-15 date: 2013-05-15
coverImage: "../../assets/images/photos/blog/helsinki/01-Library-of-University-of-Helsinki.jpg" coverImage: "01-Library-of-University-of-Helsinki.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "نزهة في إفران" title: "نزهة في إفران"
description: "نزهة شتوية في جبال الأطلس المتوسط." description: "نزهة شتوية في جبال الأطلس المتوسط."
date: 2013-01-13 date: 2013-01-13
coverImage: "../../assets/images/photos/blog/ifrane-hike/03-3.jpg" coverImage: "03-3.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Ifrane Hike" title: "Ifrane Hike"
description: "Winter hike in the Middle Atlas mountains." description: "Winter hike in the Middle Atlas mountains."
date: 2013-01-13 date: 2013-01-13
coverImage: "../../assets/images/photos/blog/ifrane-hike/03-3.jpg" coverImage: "03-3.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Randonnée à Ifrane" title: "Randonnée à Ifrane"
description: "Randonnée hivernale dans les montagnes du Moyen Atlas" description: "Randonnée hivernale dans les montagnes du Moyen Atlas"
date: 2013-01-13 date: 2013-01-13
coverImage: "../../assets/images/photos/blog/ifrane-hike/03-3.jpg" coverImage: "03-3.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "London Calling" title: "London Calling"
description: "عطلة نهاية أسبوع فوتوغرافية في لندن." description: "عطلة نهاية أسبوع فوتوغرافية في لندن."
date: 2014-07-15 date: 2014-07-15
coverImage: "../../assets/images/photos/blog/london-calling/01-The-sky-inside.jpg" coverImage: "01-The-sky-inside.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "London Calling" title: "London Calling"
description: "A photographic weekend in London." description: "A photographic weekend in London."
date: 2014-07-15 date: 2014-07-15
coverImage: "../../assets/images/photos/blog/london-calling/01-The-sky-inside.jpg" coverImage: "01-The-sky-inside.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "London Calling" title: "London Calling"
description: "Week-end photographique à Londres" description: "Week-end photographique à Londres"
date: 2014-07-15 date: 2014-07-15
coverImage: "../../assets/images/photos/blog/london-calling/01-The-sky-inside.jpg" coverImage: "01-The-sky-inside.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "أحد سيكواني" title: "أحد سيكواني"
description: "نزهة يوم أحد على ضفاف نهر السين." description: "نزهة يوم أحد على ضفاف نهر السين."
date: 2014-05-18 date: 2014-05-18
coverImage: "../../assets/images/photos/blog/sequanian-sunday/04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg" coverImage: "04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Sequanian Sunday" title: "Sequanian Sunday"
description: "A sunday walk near the Seine." description: "A sunday walk near the Seine."
date: 2014-05-18 date: 2014-05-18
coverImage: "../../assets/images/photos/blog/sequanian-sunday/04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg" coverImage: "04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Dimanche Séquanais" title: "Dimanche Séquanais"
description: "Balade dominicale au bord de la Seine." description: "Balade dominicale au bord de la Seine."
date: 2014-05-18 date: 2014-05-18
coverImage: "../../assets/images/photos/blog/sequanian-sunday/04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg" coverImage: "04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "تجوال في مدينة طنجة القديمة" title: "تجوال في مدينة طنجة القديمة"
description: "أثناء التجوال في أزقة طنجة القديمة، صادفت ساعاتيًا، ونجّارًا، وقمرًا عملاقًا..." description: "أثناء التجوال في أزقة طنجة القديمة، صادفت ساعاتيًا، ونجّارًا، وقمرًا عملاقًا..."
date: 2014-08-10 date: 2014-08-10
coverImage: "../../assets/images/photos/blog/wandering-tangier-medina/01-The-watchmaker.jpg" coverImage: "01-The-watchmaker.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Wandering Tangier Medina" title: "Wandering Tangier Medina"
description: "Walking in the streets of the old Tangier, met a watchmaker, a carpenter and a super-moon..." description: "Walking in the streets of the old Tangier, met a watchmaker, a carpenter and a super-moon..."
date: 2014-08-10 date: 2014-08-10
coverImage: "../../assets/images/photos/blog/wandering-tangier-medina/01-The-watchmaker.jpg" coverImage: "01-The-watchmaker.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Dans la médina de Tanger" title: "Dans la médina de Tanger"
description: "En marchant dans les rues du vieux Tanger, j'ai croisé un horloger, un menuisier et une super-lune..." description: "En marchant dans les rues du vieux Tanger, j'ai croisé un horloger, un menuisier et une super-lune..."
date: 2014-08-10 date: 2014-08-10
coverImage: "../../assets/images/photos/blog/wandering-tangier-medina/01-The-watchmaker.jpg" coverImage: "01-The-watchmaker.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -5,7 +5,7 @@ description: |
تغطية. تغطية.
date: 2015-04-25 date: 2015-04-25
coverImage: "../../assets/images/photos/blog/enigma/01-Enigma-v1.jpg" coverImage: "01-Enigma-v1.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -5,7 +5,7 @@ description: |
Recap. Recap.
date: 2015-04-25 date: 2015-04-25
coverImage: "../../assets/images/photos/blog/enigma/01-Enigma-v1.jpg" coverImage: "01-Enigma-v1.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -5,7 +5,7 @@ description: |
Récap. Récap.
date: 2015-04-25 date: 2015-04-25
coverImage: "../../assets/images/photos/blog/enigma/11-Enigma-v1-11.jpg" coverImage: "11-Enigma-v1-11.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Field of Stones" title: "Field of Stones"
description: "كواليس تصوير غلاف ألبوم ماركو وولتر. أراد أن تُلتقط الصورة في سينماتيك طنجة. لا وقت، لا إضاءة، لكن لا خيار. حاولنا تقديم أفضل ما لدينا..." description: "كواليس تصوير غلاف ألبوم ماركو وولتر. أراد أن تُلتقط الصورة في سينماتيك طنجة. لا وقت، لا إضاءة، لكن لا خيار. حاولنا تقديم أفضل ما لدينا..."
date: 2015-04-02 date: 2015-04-02
coverImage: "../../assets/images/photos/blog/field-of-stones/01-Marco-Wolter-Field-of-Stones-2.jpg" coverImage: "01-Marco-Wolter-Field-of-Stones-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Field of Stones" title: "Field of Stones"
description: "Making of the album cover for Marco Wolter. He wanted it to be shot in the Cinémathèque of Tangier. We had no time, no light, but no choice. We tried to make the best of it..." description: "Making of the album cover for Marco Wolter. He wanted it to be shot in the Cinémathèque of Tangier. We had no time, no light, but no choice. We tried to make the best of it..."
date: 2015-04-02 date: 2015-04-02
coverImage: "../../assets/images/photos/blog/field-of-stones/01-Marco-Wolter-Field-of-Stones-2.jpg" coverImage: "01-Marco-Wolter-Field-of-Stones-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Field of Stones" title: "Field of Stones"
description: "Making of de la pochette de l'album de Marco Wolter. Il voulait que la photo soit prise à la Cinémathèque de Tanger. Pas de temps, pas de lumière, mais pas le choix. On a fait au mieux..." description: "Making of de la pochette de l'album de Marco Wolter. Il voulait que la photo soit prise à la Cinémathèque de Tanger. Pas de temps, pas de lumière, mais pas le choix. On a fait au mieux..."
date: 2015-04-02 date: 2015-04-02
coverImage: "../../assets/images/photos/blog/field-of-stones/01-Marco-Wolter-Field-of-Stones-2.jpg" coverImage: "01-Marco-Wolter-Field-of-Stones-2.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "لا رياح في لاس كويفاس" title: "لا رياح في لاس كويفاس"
description: "كان من المفترض أن يكون يومًا مثاليًا لتطيير طائرتنا الورقية: مشمس وعاصف. مشمس كان، لكن الرياح لم تأتِ أبدًا." description: "كان من المفترض أن يكون يومًا مثاليًا لتطيير طائرتنا الورقية: مشمس وعاصف. مشمس كان، لكن الرياح لم تأتِ أبدًا."
date: 2015-01-10 date: 2015-01-10
coverImage: "../../assets/images/photos/blog/no-wind-las-cuevas/13-No-wind-at-Las-Cuevas.jpg" coverImage: "13-No-wind-at-Las-Cuevas.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "No Wind at Las Cuevas" title: "No Wind at Las Cuevas"
description: "It was supposed to be a perfect day for flying our kite: sunny and windy. Sunny it was, but the wind never came." description: "It was supposed to be a perfect day for flying our kite: sunny and windy. Sunny it was, but the wind never came."
date: 2015-01-10 date: 2015-01-10
coverImage: "../../assets/images/photos/blog/no-wind-las-cuevas/13-No-wind-at-Las-Cuevas.jpg" coverImage: "13-No-wind-at-Las-Cuevas.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Pas de vent à Las Cuevas" title: "Pas de vent à Las Cuevas"
description: "Ça devait être une journée parfaite pour faire voler notre cerf-volant : ensoleillée et venteuse. Ensoleillée, oui. Mais le vent n'est jamais venu." description: "Ça devait être une journée parfaite pour faire voler notre cerf-volant : ensoleillée et venteuse. Ensoleillée, oui. Mais le vent n'est jamais venu."
date: 2015-01-10 date: 2015-01-10
coverImage: "../../assets/images/photos/blog/no-wind-las-cuevas/13-No-wind-at-Las-Cuevas.jpg" coverImage: "13-No-wind-at-Las-Cuevas.jpg"
tags: [] tags: []
featured: false featured: false
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "زفاف أورور وتوما" title: "زفاف أورور وتوما"
description: "كان لي شرف ومتعة أن أكون شاهد توما في زفافه الجميل مع أورور. ليس سهلًا التصوير في نفس الوقت، لكن كل الصور مليئة بالحب." description: "كان لي شرف ومتعة أن أكون شاهد توما في زفافه الجميل مع أورور. ليس سهلًا التصوير في نفس الوقت، لكن كل الصور مليئة بالحب."
date: 2015-09-26 date: 2015-09-26
coverImage: "../../assets/images/photos/blog/wedding-aurore-thomas/10-Mariage-Aurore-Thomas-10.jpg" coverImage: "10-Mariage-Aurore-Thomas-10.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Wedding Aurore & Thomas" title: "Wedding Aurore & Thomas"
description: "I had the honor and pleasure to be Thomas' best man for his beautiful wedding with Aurore. Not easy to shoot pictures though, but all of them are filled with love." description: "I had the honor and pleasure to be Thomas' best man for his beautiful wedding with Aurore. Not easy to shoot pictures though, but all of them are filled with love."
date: 2015-09-26 date: 2015-09-26
coverImage: "../../assets/images/photos/blog/wedding-aurore-thomas/10-Mariage-Aurore-Thomas-10.jpg" coverImage: "10-Mariage-Aurore-Thomas-10.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -2,7 +2,7 @@
title: "Mariage Aurore & Thomas" title: "Mariage Aurore & Thomas"
description: "J'ai eu l'honneur et le plaisir d'être le témoin de Thomas pour son beau mariage avec Aurore. Pas facile de shooter en même temps, mais toutes les photos sont remplies d'amour." description: "J'ai eu l'honneur et le plaisir d'être le témoin de Thomas pour son beau mariage avec Aurore. Pas facile de shooter en même temps, mais toutes les photos sont remplies d'amour."
date: 2015-09-26 date: 2015-09-26
coverImage: "../../assets/images/photos/blog/wedding-aurore-thomas/10-Mariage-Aurore-Thomas-10.jpg" coverImage: "10-Mariage-Aurore-Thomas-10.jpg"
tags: [] tags: []
featured: true featured: true
draft: false draft: false

View file

@ -1,12 +0,0 @@
---
title: "في انتظار العروس"
description: "تحضيرات زفاف."
date: 2014-10-25
coverImage: "../../assets/images/photos/blog/waiting-for-the-bride/01-.jpg"
tags: []
featured: false
draft: false
lang: ar
---
تحضيرات زفاف.

View file

@ -1,12 +0,0 @@
---
title: "Waiting for the Bride"
description: "Wedding preparations."
date: 2014-10-25
coverImage: "../../assets/images/photos/blog/waiting-for-the-bride/01-.jpg"
tags: []
featured: false
draft: false
lang: en
---
Wedding preparations.

View file

@ -1,11 +0,0 @@
---
title: "En attendant la mariée"
description: "Préparatifs d'un mariage"
date: 2014-10-25
coverImage: "../../assets/images/photos/blog/waiting-for-the-bride/01-.jpg"
tags: []
featured: false
draft: false
---
Préparatifs d'un mariage.

View file

@ -1,4 +1,6 @@
--- ---
import PhotoFooter from '../components/photo/PhotoFooter.astro';
const { title = "Galerie Photo - Jalil Arfaoui", enableScroll = false } = Astro.props; const { title = "Galerie Photo - Jalil Arfaoui", enableScroll = false } = Astro.props;
--- ---
@ -25,19 +27,7 @@ const { title = "Galerie Photo - Jalil Arfaoui", enableScroll = false } = Astro.
<body class={`antialiased bg-black text-white ${enableScroll ? '' : 'overflow-hidden'}`} style="font-family: 'Karla', 'Helvetica Neue', Helvetica, Arial, sans-serif;"> <body class={`antialiased bg-black text-white ${enableScroll ? '' : 'overflow-hidden'}`} style="font-family: 'Karla', 'Helvetica Neue', Helvetica, Arial, sans-serif;">
<slot /> <slot />
<!-- Footer bandeau --> <PhotoFooter />
<footer class="fixed bottom-0 left-0 right-0 z-40 bg-black/30 backdrop-blur-sm transition-transform duration-300">
<div class="flex justify-between items-center w-full" style="padding-left: 20px; padding-right: 20px; height: 54px; line-height: 54px;">
<div class="footer-left flex items-center text-white/70" style="font-size: 16px; font-weight: normal;">
<a href="/a-propos" class="hover:text-white transition-colors" style="margin-right: 15px;">À propos</a>
<a href="mailto:jalil@arfaoui.net" class="hover:text-white transition-colors" style="margin-right: 15px;">Contact</a>
<a href="https://instagram.com/l.i.l.a.j" target="_blank" rel="noopener noreferrer" class="hover:text-white transition-colors">Instagram</a>
</div>
<div class="footer-right text-white/70" style="font-size: 16px; font-weight: normal;">
© Jalil Arfaoui Creative Commons CC-BY-NC 4.0
</div>
</div>
</footer>
<Fragment set:html={import.meta.env.FOOTER_INJECT} /> <Fragment set:html={import.meta.env.FOOTER_INJECT} />
</body> </body>

View file

@ -1,9 +1,9 @@
--- ---
import PhotoLayout from '../../../layouts/PhotoLayout.astro'; import PhotoLayout from '../../../../layouts/PhotoLayout.astro';
import CategoryNav from '../../../components/photo/CategoryNav.astro'; import CategoryNav from '../../../../components/photo/CategoryNav.astro';
import AlbumHeader from '../../../components/photo/AlbumHeader.astro'; import AlbumHeader from '../../../../components/photo/AlbumHeader.astro';
import MasonryGallery from '../../../components/photo/MasonryGallery.astro'; import MasonryGallery from '../../../../components/photo/MasonryGallery.astro';
import Lightbox from '../../../components/photo/Lightbox.astro'; import Lightbox from '../../../../components/photo/Lightbox.astro';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
// Importer toutes les images du dossier photos // Importer toutes les images du dossier photos
@ -11,27 +11,37 @@ const allImages = import.meta.glob<{ default: ImageMetadata }>('/src/assets/imag
export async function getStaticPaths() { export async function getStaticPaths() {
const allPhotoBlogPosts = await getCollection('photoBlogPosts'); const allPhotoBlogPosts = await getCollection('photoBlogPosts');
return allPhotoBlogPosts.map(post => ({ return allPhotoBlogPosts.map(post => {
params: { slug: post.slug }, // Le slug Astro inclut le préfixe d'année (ex: "2015/enigma.en")
props: { post }, const slug = post.slug.replace(/^\d{4}\//, '');
})); return {
params: {
year: String(post.data.date.getFullYear()),
slug,
},
props: { post },
};
});
} }
const { post } = Astro.props; const { post } = Astro.props;
const { Content } = await post.render(); const { Content } = await post.render();
// coverImage est déjà un ImageMetadata grâce au schema image() dans config.ts // Slug de base sans préfixe d'année ni suffixe de langue (2015/enigma.en → enigma)
const coverImage = post.data.coverImage; const baseSlug = post.slug.replace(/^\d{4}\//, '').replace(/\.(en|ar)$/, '');
// Slug de base sans suffixe de langue (enigma.en → enigma) // Construire le chemin de l'album avec l'année
const baseSlug = post.slug.replace(/\.(en|ar)$/, ''); const year = post.data.date.getFullYear();
const albumPath = `/src/assets/images/photos/blog/${year}/${baseSlug}/`;
// Charger toutes les images du dossier correspondant au slug
const albumPath = `/src/assets/images/photos/blog/${baseSlug}/`;
const albumImages = Object.keys(allImages) const albumImages = Object.keys(allImages)
.filter(path => path.startsWith(albumPath)) .filter(path => path.startsWith(albumPath))
.sort(); .sort();
// Résoudre la cover image depuis le glob
const coverPath = `/src/assets/images/photos/blog/${year}/${baseSlug}/${post.data.coverImage}`;
const coverImageLoader = allImages[coverPath];
const coverImage = coverImageLoader ? (await coverImageLoader()).default : undefined;
// Résoudre les images de la galerie // Résoudre les images de la galerie
const galleryImages = await Promise.all( const galleryImages = await Promise.all(
albumImages.map(async (imagePath) => { albumImages.map(async (imagePath) => {

View file

@ -4,6 +4,11 @@ import CategoryNav from '../../../components/photo/CategoryNav.astro';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import { Picture } from 'astro:assets'; import { Picture } from 'astro:assets';
// Importer toutes les images pour résoudre les cover images
const allImages = import.meta.glob<{ default: ImageMetadata }>(
'/src/assets/images/photos/blog/**/*.{jpg,jpeg,png,webp}'
);
// Récupération des posts photo (langue par défaut : FR) // Récupération des posts photo (langue par défaut : FR)
const allPhotoBlogPosts = (await getCollection('photoBlogPosts')) const allPhotoBlogPosts = (await getCollection('photoBlogPosts'))
.filter(post => (post.data.lang ?? 'fr') === 'fr'); .filter(post => (post.data.lang ?? 'fr') === 'fr');
@ -13,10 +18,14 @@ const sortedPosts = allPhotoBlogPosts.sort((a, b) =>
new Date(b.data.date).getTime() - new Date(a.data.date).getTime() new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
); );
// coverImage est déjà un ImageMetadata grâce au schema image() dans config.ts // Résoudre les cover images via le glob
const postsWithImages = sortedPosts.map((post) => ({ const postsWithImages = await Promise.all(sortedPosts.map(async (post) => {
...post, const year = post.data.date.getFullYear();
resolvedCoverImage: post.data.coverImage const baseSlug = post.slug.replace(/^\d{4}\//, '').replace(/\.(en|ar)$/, '');
const coverPath = `/src/assets/images/photos/blog/${year}/${baseSlug}/${post.data.coverImage}`;
const loader = allImages[coverPath];
const resolvedCoverImage = loader ? (await loader()).default : undefined;
return { ...post, resolvedCoverImage };
})); }));
// Séparer les posts à la une des autres // Séparer les posts à la une des autres
@ -41,7 +50,6 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
<div class="post-content"> <div class="post-content">
<span class="post-badge">À la une</span> <span class="post-badge">À la une</span>
<h2 class="post-title">{post.data.title}</h2> <h2 class="post-title">{post.data.title}</h2>
<p class="post-description">{post.data.description}</p>
<time class="post-date"> <time class="post-date">
{new Date(post.data.date).toLocaleDateString('fr-FR', { {new Date(post.data.date).toLocaleDateString('fr-FR', {
year: 'numeric', year: 'numeric',
@ -69,7 +77,6 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
<div class="post-overlay"> <div class="post-overlay">
<div class="overlay-content"> <div class="overlay-content">
<h3 class="post-title">{post.data.title}</h3> <h3 class="post-title">{post.data.title}</h3>
<p class="post-subtitle">{post.data.description}</p>
<time class="post-date"> <time class="post-date">
{new Date(post.data.date).toLocaleDateString('fr-FR', { {new Date(post.data.date).toLocaleDateString('fr-FR', {
year: 'numeric', year: 'numeric',
@ -96,15 +103,13 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
/* Section à la une */ /* Section à la une */
.featured-section { .featured-section {
padding: 40px 20px 60px; padding: 16px 16px 0;
max-width: 1600px;
margin: 0 auto;
} }
.featured-grid { .featured-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 20px; gap: 16px;
} }
.featured-post { .featured-post {
@ -141,13 +146,10 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
padding: 40px; padding: 40px;
overflow: hidden;
} }
.featured-post .post-content { .featured-post .post-content {
color: white; color: white;
max-height: 100%;
overflow: hidden;
} }
.post-badge { .post-badge {
@ -165,7 +167,7 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
.featured-post .post-title { .featured-post .post-title {
font-size: 28px; font-size: 28px;
font-weight: 600; font-weight: 600;
margin: 0 0 12px 0; margin: 0 0 4px 0;
line-height: 1.2; line-height: 1.2;
} }
@ -182,21 +184,20 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
} }
.featured-post .post-date { .featured-post .post-date {
display: block;
font-size: 14px; font-size: 14px;
opacity: 0.8; opacity: 0.8;
} }
/* Grille des posts */ /* Grille des posts */
.posts-grid { .posts-grid {
padding: 0 20px 100px; padding: 16px 16px 100px;
max-width: 1600px;
margin: 0 auto;
} }
.grid-container { .grid-container {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 20px; gap: 16px;
} }
.post-item { .post-item {
@ -211,6 +212,11 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
aspect-ratio: 3/2; aspect-ratio: 3/2;
} }
.featured-post .post-link {
aspect-ratio: auto;
height: 100%;
}
.post-link img { .post-link img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -221,18 +227,19 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
.post-item .post-overlay { .post-item .post-overlay {
position: absolute; position: absolute;
top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 100%); background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.5) 25%, rgba(0,0,0,0) 50%);
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
padding: 20px; padding: 12px 16px;
transition: background 0.3s ease; transition: background 0.3s ease;
} }
.post-item:hover .post-overlay { .post-item:hover .post-overlay {
background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.2) 100%); background: linear-gradient(to top, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.7) 25%, rgba(0,0,0,0) 50%);
} }
.post-item:hover img { .post-item:hover img {
@ -246,7 +253,7 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
.post-item .post-title { .post-item .post-title {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
margin: 0 0 4px 0; margin: 0 0 2px 0;
line-height: 1.3; line-height: 1.3;
text-shadow: 0 1px 3px rgba(0,0,0,0.8); text-shadow: 0 1px 3px rgba(0,0,0,0.8);
} }
@ -279,7 +286,7 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
@media (max-width: 768px) { @media (max-width: 768px) {
.featured-grid { .featured-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 20px; gap: 12px;
} }
.featured-post { .featured-post {
@ -309,11 +316,11 @@ const regularPosts = postsWithImages.filter(post => !post.data.featured);
} }
.featured-section { .featured-section {
padding: 20px 15px 40px; padding: 12px 12px 0;
} }
.posts-grid { .posts-grid {
padding: 0 15px 80px; padding: 12px 12px 80px;
} }
} }
</style> </style>