33<article
44 x-data =" {
55 currentSlide: 0,
6+ lightboxSlide: 0,
7+ lightboxOpen: false,
68 screenshots: {{ json_encode ($showcase -> screenshots ?? []) } } ,
9+ screenshotUrls: {{ json_encode (collect ($showcase -> screenshots ?? [])-> map (fn ($s ) => Storage:: disk (' public' )-> url ($s ))-> values ()) } } ,
710 get hasScreenshots() {
811 return this.screenshots && this.screenshots.length > 0
912 },
1922 if (this.currentSlide > 0) {
2023 this.currentSlide--
2124 }
25+ },
26+ nextLightboxSlide() {
27+ this.lightboxSlide = (this.lightboxSlide + 1) % this.totalSlides
28+ },
29+ prevLightboxSlide() {
30+ this.lightboxSlide = (this.lightboxSlide - 1 + this.totalSlides) % this.totalSlides
31+ },
32+ openLightbox(index) {
33+ this.lightboxSlide = index
34+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
35+ document.body.style.overflow = 'hidden'
36+ document.body.style.paddingRight = scrollbarWidth + 'px'
37+ this.lightboxOpen = true
38+ },
39+ closeLightbox() {
40+ this.lightboxOpen = false
41+ setTimeout(() => {
42+ document.body.style.overflow = ''
43+ document.body.style.paddingRight = ''
44+ }, 200)
2245 }
2346 }"
47+ @keydown .escape.window =" closeLightbox()"
48+ @keydown .arrow-right.window =" if (lightboxOpen) nextLightboxSlide()"
49+ @keydown .arrow-left.window =" if (lightboxOpen) prevLightboxSlide()"
2450 class =" group relative overflow-hidden rounded-2xl border border-gray-200 bg-white transition-all hover:shadow-xl dark:border-gray-700 dark:bg-gray-800"
2551>
2652 {{-- NEW Badge --}}
@@ -37,15 +63,21 @@ class="group relative overflow-hidden rounded-2xl border border-gray-200 bg-whit
3763 @if ($showcase -> screenshots && count ($showcase -> screenshots ) > 0 )
3864 <div class =" relative h-full" >
3965 @foreach ($showcase -> screenshots as $index => $screenshot )
40- <img
66+ <button
67+ type =" button"
4168 x-show =" currentSlide === {{ $index } }"
4269 x-transition:enter =" transition ease-out duration-300"
4370 x-transition:enter-start =" opacity-0"
4471 x-transition:enter-end =" opacity-100"
45- src =" {{ Storage:: disk (' public' )-> url ($screenshot ) } }"
46- alt =" {{ $showcase -> title } } screenshot {{ $index + 1 } }"
47- class =" absolute inset-0 w-full h-full object-contain"
72+ @click =" openLightbox({{ $index } } )"
73+ class =" absolute inset-0 w-full h-full cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-inset"
4874 >
75+ <img
76+ src =" {{ Storage:: disk (' public' )-> url ($screenshot ) } }"
77+ alt =" {{ $showcase -> title } } screenshot {{ $index + 1 } }"
78+ class =" w-full h-full object-contain"
79+ >
80+ </button >
4981 @endforeach
5082
5183 {{-- Navigation Arrows --}}
@@ -213,4 +245,70 @@ class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gra
213245 @endif
214246 </div >
215247 </div >
248+
249+ {{-- Lightbox Modal --}}
250+ <template x-teleport =" body" >
251+ <div
252+ x-show =" lightboxOpen"
253+ x-transition:enter =" transition ease-out duration-300"
254+ x-transition:enter-start =" opacity-0"
255+ x-transition:enter-end =" opacity-100"
256+ x-transition:leave =" transition ease-in duration-200"
257+ x-transition:leave-start =" opacity-100"
258+ x-transition:leave-end =" opacity-0"
259+ class =" fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4 sm:p-8"
260+ @click .self =" closeLightbox()"
261+ >
262+ {{-- Close Button --}}
263+ <button
264+ @click =" closeLightbox()"
265+ class =" absolute top-4 right-4 z-10 p-2 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
266+ aria-label =" Close lightbox"
267+ >
268+ <svg class =" w-6 h-6" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
269+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M6 18L18 6M6 6l12 12" />
270+ </svg >
271+ </button >
272+
273+ {{-- Image Counter --}}
274+ <div
275+ x-show =" totalSlides > 1"
276+ class =" absolute top-4 left-4 px-3 py-1.5 rounded-full bg-white/10 text-white text-sm font-medium"
277+ >
278+ <span x-text =" lightboxSlide + 1" ></span > / <span x-text =" totalSlides" ></span >
279+ </div >
280+
281+ {{-- Previous Button --}}
282+ <button
283+ x-show =" totalSlides > 1"
284+ @click .stop =" prevLightboxSlide()"
285+ class =" absolute left-4 top-1/2 -translate-y-1/2 z-10 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
286+ aria-label =" Previous screenshot"
287+ >
288+ <svg class =" w-6 h-6" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
289+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M15 19l-7-7 7-7" />
290+ </svg >
291+ </button >
292+
293+ {{-- Next Button --}}
294+ <button
295+ x-show =" totalSlides > 1"
296+ @click .stop =" nextLightboxSlide()"
297+ class =" absolute right-4 top-1/2 -translate-y-1/2 z-10 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
298+ aria-label =" Next screenshot"
299+ >
300+ <svg class =" w-6 h-6" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
301+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M9 5l7 7-7 7" />
302+ </svg >
303+ </button >
304+
305+ {{-- Main Image --}}
306+ <img
307+ :src =" screenshotUrls[lightboxSlide]"
308+ :alt =" '{{ $showcase -> title } } screenshot ' + (lightboxSlide + 1)"
309+ class =" max-h-full max-w-full object-contain"
310+ @click .stop
311+ >
312+ </div >
313+ </template >
216314</article >
0 commit comments