'use client' import { AnimatePresence, motion, useInView } from 'framer-motion' import Image from 'next/image' import { useCallback, useEffect, useRef, useState } from 'react' import { AnimatedGridBackground } from '../AnimatedGridBackground' // All available images from Supabase Select event const allImages = [ '/images/wrapped/select-2025-021.jpg', '/images/wrapped/select-2025-091.jpg', '/images/wrapped/select-2025-143.jpg', '/images/wrapped/select-2025-163.jpg', '/images/wrapped/select-2025-190.jpg', '/images/wrapped/select-2025-202.jpg', '/images/wrapped/select-2025-216.jpg', '/images/wrapped/select-2025-229.jpg', '/images/wrapped/select-2025-247.jpg', '/images/wrapped/select-2025-250.jpg', '/images/wrapped/select-2025-252.jpg', '/images/wrapped/select-2025-003.jpg', '/images/wrapped/select-2025-099.jpg', ] // Gallery layout configuration const gallerySlots = [ { id: 0, span: 'col-span-1 lg:col-span-2 row-span-1 lg:row-span-2' }, { id: 1, span: 'col-span-1 lg:col-span-3 row-span-1 lg:row-span-2' }, { id: 2, span: 'col-span-1 lg:col-span-3 row-span-1 lg:row-span-2' }, { id: 3, span: 'col-span-1 lg:col-span-2 row-span-1 lg:row-span-2' }, ] function RotatingImage({ currentImage, span, imageKey, priority = false, }: { currentImage: string span: string imageKey: number priority?: boolean }) { return (
Supabase Select event photo
) } export const SupabaseSelect = () => { return ( <>
{/* Grid background */} {/* Content */}

Supabase Select

Our first user conference was a blast — Thank you for being part of it.

{/* Grid separator top */}
{/* Bento gallery */} ) } function GalleryGrid() { const ref = useRef(null) const isInView = useInView(ref, { amount: 0.3 }) // Track which image index each slot is showing const [slotImages, setSlotImages] = useState(() => { // Initialize with unique random indices const indices: number[] = [] const available = [...Array(allImages.length).keys()] for (let i = 0; i < gallerySlots.length; i++) { const randomIdx = Math.floor(Math.random() * available.length) indices.push(available[randomIdx]) available.splice(randomIdx, 1) } return indices }) // Track animation keys for each slot const [imageKeys, setImageKeys] = useState(() => gallerySlots.map(() => 0)) // Get available images (not currently shown in any slot) const getAvailableImageIndex = useCallback( (excludeSlot: number) => { const usedIndices = slotImages.filter((_, idx) => idx !== excludeSlot) const available = allImages.map((_, idx) => idx).filter((idx) => !usedIndices.includes(idx)) if (available.length === 0) return slotImages[excludeSlot] return available[Math.floor(Math.random() * available.length)] }, [slotImages] ) // Rotate a specific slot's image const rotateSlot = useCallback( (slotIndex: number) => { const newImageIndex = getAvailableImageIndex(slotIndex) setSlotImages((prev) => { const next = [...prev] next[slotIndex] = newImageIndex return next }) setImageKeys((prev) => { const next = [...prev] next[slotIndex] = prev[slotIndex] + 1 return next }) }, [getAvailableImageIndex] ) // Set up independent timers for each slot useEffect(() => { if (!isInView) return let isCancelled = false const timeouts: NodeJS.Timeout[] = [] const scheduleRotation = (slotIndex: number) => { if (isCancelled) return const delay = 3000 + Math.random() * 2000 // 3-5 seconds const timeoutId = setTimeout(() => { if (isCancelled) return rotateSlot(slotIndex) scheduleRotation(slotIndex) }, delay) timeouts.push(timeoutId) } // Start rotation for each slot with staggered initial delays gallerySlots.forEach((_, index) => { const initialDelay = Math.random() * 3000 // Stagger start times const timeoutId = setTimeout(() => { if (isCancelled) return scheduleRotation(index) }, initialDelay) timeouts.push(timeoutId) }) return () => { isCancelled = true timeouts.forEach(clearTimeout) } }, [isInView, rotateSlot]) return (
{gallerySlots.map((slot, index) => ( ))}
) }