/* app.jsx — Ice Cup app: state, cart, scroll reveals, floating ice, tweaks */ const { useState, useEffect, useMemo, useRef } = React; /* Resolve an asset path to its inlined blob URL when running as a standalone bundle (window.__resources populated by the offline bundler); otherwise return the original path. Safe in both the live and bundled file. */ window.resolveAsset = window.resolveAsset || function(path){ try { if (window.__resources) { const metas = document.querySelectorAll('meta[name="ext-resource-dependency"]'); for (const m of metas) { if (m.getAttribute('content') === path) { const id = m.getAttribute('data-resource-id'); if (window.__resources[id]) return window.__resources[id]; } } } } catch (e) {} return path; }; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "hero": "Split", "accent": "#2f6bff", "displayFont": "Archivo", "floatingIce": true, "iceCount": 14 }/*EDITMODE-END*/; const ACCENTS = { "#2f6bff": { a:"oklch(0.60 0.19 245)", a2:"oklch(0.74 0.14 222)", ad:"oklch(0.46 0.17 252)" }, "#19b8d8": { a:"oklch(0.68 0.13 215)", a2:"oklch(0.81 0.11 203)", ad:"oklch(0.52 0.13 224)" }, "#5b63f5": { a:"oklch(0.58 0.18 277)", a2:"oklch(0.73 0.13 258)", ad:"oklch(0.46 0.18 288)" }, "#0ea59a": { a:"oklch(0.64 0.12 188)", a2:"oklch(0.78 0.10 196)", ad:"oklch(0.50 0.12 200)" }, }; const HERO_MAP = { "Split":"split", "Poster":"poster", "Deep Freeze":"dark" }; function IceField({ count, on }){ const cubes = useMemo(()=>{ const rnd = (a,b)=> a + Math.random()*(b-a); return Array.from({length:count}).map((_,i)=>({ left: rnd(2,96)+'%', top: rnd(2,96)+'%', size: rnd(14,32), dur: rnd(11,22)+'s', r: rnd(-18,18)+'deg', delay: -rnd(0,12)+'s', op: rnd(0.3,0.5), })); }, [count]); return (
); } function App(){ const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [scrolled, setScrolled] = useState(false); const [cart, setCart] = useState({}); // id -> {…product, qty} const [drawer, setDrawer] = useState(false); const [addedId, setAddedId] = useState(null); const addTimer = useRef(null); /* apply tweak-driven CSS vars + body attributes */ useEffect(()=>{ const root = document.documentElement; const ac = ACCENTS[t.accent] || ACCENTS["#2f6bff"]; root.style.setProperty('--accent', ac.a); root.style.setProperty('--accent-2', ac.a2); root.style.setProperty('--accent-deep', ac.ad); root.style.setProperty('--font-display', `"${t.displayFont}", system-ui, sans-serif`); document.body.dataset.anim = t.floatingIce ? 'on' : 'off'; }, [t.accent, t.displayFont, t.floatingIce]); /* sticky nav */ useEffect(()=>{ const onScroll = ()=> setScrolled(window.scrollY > 24); onScroll(); window.addEventListener('scroll', onScroll, { passive:true }); return ()=> window.removeEventListener('scroll', onScroll); }, []); /* scroll reveals */ useEffect(()=>{ const els = document.querySelectorAll('.reveal:not(.in)'); const io = new IntersectionObserver((entries)=>{ entries.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold:0.12, rootMargin:'0px 0px -8% 0px' }); els.forEach(el=> io.observe(el)); return ()=> io.disconnect(); }, [t.hero]); /* cart ops */ const addToCart = (p)=>{ setCart(c=>{ const ex = c[p.id]; return { ...c, [p.id]: ex ? {...ex, qty:ex.qty+1} : { id:p.id, name:p.name, price:p.price, unit:p.unit, qty:1 } }; }); setAddedId(p.id); clearTimeout(addTimer.current); addTimer.current = setTimeout(()=> setAddedId(null), 1400); }; const changeQty = (id,d)=> setCart(c=>{ const ex = c[id]; if(!ex) return c; const q = ex.qty + d; if(q<=0){ const n = {...c}; delete n[id]; return n; } return { ...c, [id]: {...ex, qty:q} }; }); const removeLine = (id)=> setCart(c=>{ const n={...c}; delete n[id]; return n; }); const cartCount = Object.values(cart).reduce((s,l)=> s+l.qty, 0); const scrollTo = (sel)=>{ const el = document.querySelector(sel); if(el){ window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 80, behavior:'smooth' }); } }; return ( <>