// waiv-gallery.jsx — ProductGallery with two explorable layouts the user can // switch between: "Deck" (cover-flow 3D arc, drag/flick through) and "Wall" // (magnetic tilt grid with a cursor spotlight). Both share a front/back modal // and morph the section background to the focused card's signature colour. (function () { const { useState, useRef, useEffect } = React; const PRODUCTS = [ { id: 'sapphire', name: 'Sapphire', tag: 'Crystalline polygon blue', accent: '#3f8bff', bg1: '#0d2b57', bg2: '#03101f' }, { id: 'cobalt', name: 'Cobalt Waves', tag: 'Deep ocean wave', accent: '#2f63e0', bg1: '#0a2350', bg2: '#030c1e' }, { id: 'azure', name: 'Azure', tag: 'Bright sky blue', accent: '#46a6ff', bg1: '#0b3358', bg2: '#03101f' }, { id: 'seafrost', name: 'Sea Frost', tag: 'Frosted glass blue', accent: '#8fc6d6', bg1: '#10303a', bg2: '#06141a' }, { id: 'midnight', name: 'Midnight Gold', tag: 'Navy & gold leaf', accent: '#e7c873', bg1: '#101f3d', bg2: '#05060f' }, { id: 'sphinxgold', name: 'Sphinx Gold', tag: 'Geometric gold lines', accent: '#d8b24a', bg1: '#16203a', bg2: '#070a12' }, { id: 'obsidian', name: 'Obsidian', tag: 'Gold-dust eclipse', accent: '#d2ab57', bg1: '#1b160e', bg2: '#070707' }, { id: 'sphinxsilver', name: 'Sphinx Silver', tag: 'Geometric silver', accent: '#c2cad6', bg1: '#1a2230', bg2: '#080b10' }, { id: 'heritage', name: 'Heritage Cruiser', tag: 'Vintage motor', accent: '#67b9a4', bg1: '#143029', bg2: '#06120e' }, { id: 'bumblebee', name: 'Bumblebee', tag: 'Electric wave yellow', accent: '#f2c200', bg1: '#2a2706', bg2: '#0c0b03' }, { id: 'composite', name: 'Composite', tag: 'Sunset gradient mesh', accent: '#e5703f', bg1: '#3a1726', bg2: '#10070d' }, { id: 'barbie', name: 'Barbie', tag: 'Bauhaus rose & plum', accent: '#e0577f', bg1: '#3a1430', bg2: '#10060d' }, { id: 'ivory', name: 'Ivory', tag: 'Warm minimalist cream', accent: '#cbb9a0', bg1: '#2a2418', bg2: '#0d0b07' }, { id: 'limelight', name: 'Limelight', tag: 'Signal-green emboss', accent: '#bcd24a', bg1: '#212d10', bg2: '#0a0d06' }, ]; PRODUCTS.forEach(p => { const R = window.__resources; p.front = (R && R['pf_' + p.id]) || `assets/products/${p.id}.jpg`; p.back = (R && R['pb_' + p.id]) || `assets/products/${p.id}-back.jpg`; }); const N = PRODUCTS.length; function ProductGallery() { const secRef = useRef(null); const [mode, setMode] = useState('deck'); const [focus, setFocus] = useState(0); const [open, setOpen] = useState(null); const [flip, setFlip] = useState(false); const setBg = (p) => { const el = secRef.current; if (!el || !p) return; el.style.setProperty('--pg-bg1', p.bg1); el.style.setProperty('--pg-bg2', p.bg2); el.style.setProperty('--pg-accent', p.accent); }; useEffect(() => { setBg(PRODUCTS[focus]); }, [focus]); const openCard = (p) => { setFlip(false); setOpen(p); setBg(p); }; const close = () => { setOpen(null); setBg(PRODUCTS[focus]); }; useEffect(() => { const k = (e) => { if (e.key === 'Escape') close(); }; window.addEventListener('keydown', k); return () => window.removeEventListener('keydown', k); }, [focus]); return (
THE COLLECTION

Pick a card people won’t put down.

Fourteen finishes, one secure chip. {mode === 'deck' ? 'Flick through the deck' : 'Sweep across the wall'} — tap any card to see both sides.

{mode === 'deck' ? : }

{PRODUCTS[focus].name}

{PRODUCTS[focus].tag}

{/* shared front/back modal */}
{open && (
e.stopPropagation()}>
setFlip(f => !f)}>
{`${open.name}
{`${open.name}
{open.tag.toUpperCase()}

{open.name}

Premium UV-printed finish with a secure NTAG 424 DNA chip sealed inside. Unlimited taps, no subscription to share.

Price₹899
ChipNTAG 424 DNA
TapsUnlimited
)}
); } /* ─────────── DECK: cover-flow 3D arc ─────────── */ function DeckView({ focus, setFocus, onOpen }) { const stageRef = useRef(null); const cardEls = useRef([]); const cur = useRef(focus); // eased float index const target = useRef(focus); const drag = useRef(null); const moved = useRef(false); useEffect(() => { let raf; const loop = () => { cur.current += (target.current - cur.current) * 0.12; const vw = window.innerWidth; const cardW = vw < 640 ? 190 : vw < 1000 ? 230 : 270; const spacing = cardW * 0.62; cardEls.current.forEach((el, i) => { if (!el) return; const o = i - cur.current; // signed offset from center const ao = Math.abs(o); const x = o * spacing; const rotY = Math.max(-58, Math.min(58, -o * 26)); const z = -ao * 120; const sc = Math.max(0.7, 1 - ao * 0.08); el.style.width = cardW + 'px'; el.style.transform = `translate(-50%,-50%) translateX(${x.toFixed(1)}px) translateZ(${z.toFixed(1)}px) rotateY(${rotY.toFixed(1)}deg) scale(${sc.toFixed(3)})`; el.style.zIndex = String(200 - Math.round(ao * 10)); el.style.opacity = ao > 5.5 ? '0' : (1 - ao * 0.12).toFixed(3); el.style.filter = ao < 0.5 ? 'none' : `brightness(${(0.95 - ao * 0.12).toFixed(2)})`; el.classList.toggle('is-center', ao < 0.5); }); const f = Math.round(cur.current); if (f !== focusRef.current) { focusRef.current = f; setFocus(((f % N) + N) % N); } raf = requestAnimationFrame(loop); }; const focusRef = { current: focus }; raf = requestAnimationFrame(loop); return () => cancelAnimationFrame(raf); }, []); const go = (n) => { target.current = Math.max(0, Math.min(N - 1, n)); }; useEffect(() => { const stage = stageRef.current; const down = (e) => { drag.current = { x: e.clientX, start: target.current }; moved.current = false; stage.classList.add('is-grab'); }; const move = (e) => { if (!drag.current) return; const dx = e.clientX - drag.current.x; if (Math.abs(dx) > 3) moved.current = true; const vw = window.innerWidth, cardW = vw < 640 ? 190 : vw < 1000 ? 230 : 270; target.current = Math.max(0, Math.min(N - 1, drag.current.start - dx / (cardW * 0.62))); }; const up = () => { if (!drag.current) return; drag.current = null; stage.classList.remove('is-grab'); target.current = Math.max(0, Math.min(N - 1, Math.round(target.current))); }; const wheel = (e) => { if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { e.preventDefault(); target.current = Math.max(0, Math.min(N - 1, target.current + e.deltaX * 0.01)); } }; stage.addEventListener('pointerdown', down); window.addEventListener('pointermove', move); window.addEventListener('pointerup', up); stage.addEventListener('wheel', wheel, { passive: false }); return () => { stage.removeEventListener('pointerdown', down); window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); stage.removeEventListener('wheel', wheel); }; }, []); const click = (i) => { if (moved.current) return; if (Math.abs(i - cur.current) < 0.5) onOpen(PRODUCTS[i]); else go(i); }; return (
{PRODUCTS.map((p, i) => ( ))}
{PRODUCTS.map((p, i) => (
); } /* ─────────── WALL: magnetic tilt grid ─────────── */ function WallView({ setFocus, onOpen, setBg }) { const gridRef = useRef(null); useEffect(() => { const grid = gridRef.current; const cards = [...grid.querySelectorAll('.pg-wall-card')]; const onMove = (e) => { cards.forEach((el) => { const r = el.getBoundingClientRect(); const inside = e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom; if (inside) { const px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height; el.style.setProperty('--mx', (px * 100).toFixed(1) + '%'); el.style.setProperty('--my', (py * 100).toFixed(1) + '%'); el.style.transform = `perspective(700px) rotateY(${((px - 0.5) * 18).toFixed(1)}deg) rotateX(${((0.5 - py) * 18).toFixed(1)}deg) translateZ(26px) scale(1.06)`; } }); }; window.addEventListener('pointermove', onMove, { passive: true }); return () => window.removeEventListener('pointermove', onMove); }, []); return (
{PRODUCTS.map((p, i) => ( ))}
); } window.ProductGallery = ProductGallery; })();