'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 (
)
}
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) => (
))}
)
}