// FRZN — Main App (landing) and Shop app const { useState: uS, useEffect: uE } = React; // Persisted state helpers const load = (k, d) => { try { return JSON.parse(localStorage.getItem(k)) ?? d; } catch { return d; } }; const save = (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} }; // Tweaks defaults — wrapped in EDITMODE markers so host can persist const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "glacial", "accent": "#E8542A", "copy": "thermal", "font": "display", "card": "default", "density": "normal" }/*EDITMODE-END*/; function useTweaks() { const [tweaks, setTweaks] = uS(() => ({ ...TWEAK_DEFAULTS, ...load('frzn_tweaks', {}) })); uE(() => { const root = document.documentElement; root.setAttribute('data-theme', tweaks.theme); root.setAttribute('data-font', tweaks.font); root.setAttribute('data-card', tweaks.card); root.setAttribute('data-density', tweaks.density); root.style.setProperty('--accent', tweaks.accent); save('frzn_tweaks', tweaks); }, [tweaks]); return [tweaks, (k, v) => setTweaks(t => ({ ...t, [k]: v }))]; } function useEditMode() { const [visible, setVisible] = uS(false); uE(() => { const handler = (e) => { if (e.data?.type === '__activate_edit_mode') setVisible(true); if (e.data?.type === '__deactivate_edit_mode') setVisible(false); }; window.addEventListener('message', handler); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', handler); }, []); return [visible, setVisible]; } function persistKey(k, v) { window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [k]: v } }, '*'); } // ================== LANDING APP ================== function App() { const [tweaks, setTweak] = useTweaks(); const [editVisible, setEditVisible] = useEditMode(); const [lang, setLang] = uS(() => load('frzn_lang', 'en')); const [cart, setCart] = uS(() => load('frzn_cart', [])); const [drawerOpen, setDrawerOpen] = uS(false); const [checkoutOpen, setCheckoutOpen] = uS(false); const [waitlistModal, setWaitlistModal] = uS(null); const [orderModal, setOrderModal] = uS(null); const [toast, setToast] = uS({ show: false, msg: '' }); const [liveTemp, setLiveTemp] = uS(3.6); const [showSticky, setShowSticky] = uS(false); uE(() => save('frzn_lang', lang), [lang]); uE(() => save('frzn_cart', cart), [cart]); uE(() => { const i = setInterval(() => setLiveTemp(t => Math.max(1.2, Math.min(7, t + (Math.random() - 0.5) * 0.5))), 2400); return () => clearInterval(i); }, []); uE(() => { const h = () => setShowSticky(window.scrollY > 800); window.addEventListener('scroll', h, { passive: true }); return () => window.removeEventListener('scroll', h); }, []); const t = I18N[lang] || I18N.en; const showToast = (msg) => { setToast({ show: true, msg }); setTimeout(() => setToast(s => ({ ...s, show: false })), 2400); }; const addToCart = (product) => { const cartId = `${product.id}-${product.size}`; setCart(prev => { const existing = prev.find(it => it.cartId === cartId); if (existing) return prev.map(it => it.cartId === cartId ? { ...it, qty: it.qty + 1 } : it); return [...prev, { cartId, id: product.id, name: product.name, size: product.size, price: product.price, qty: 1 }]; }); showToast(`✓ ${product.name} · ${product.size} reserved`); }; const onQty = (id, q) => setCart(prev => q <= 0 ? prev.filter(it => it.cartId !== id) : prev.map(it => it.cartId === id ? { ...it, qty: q } : it)); const onRemove = (id) => setCart(prev => prev.filter(it => it.cartId !== id)); const onTweak = (k, v) => { setTweak(k, v); persistKey(k, v); }; const onCheckout = () => { setDrawerOpen(false); setCheckoutOpen(true); }; const onConfirm = () => { setCheckoutOpen(false); setOrderModal({ title: <>Reservation locked.>, body: `Edition 001 · unit #${342 + cart.reduce((s, it) => s + it.qty, 0)} / 500. You'll receive tracking the day we ship the first cold front.`, }); setCart([]); }; const onWaitlist = (email) => { setWaitlistModal({ title: <>You're on the list.>, body: `${email} — we've secured your place in Edition 001. Expect a dispatch from the cold before release.`, }); }; return ( <> s+it.qty,0)} onOpenCart={() => setDrawerOpen(true)} liveTemp={liveTemp} current="home" /> document.getElementById('waitlist')?.scrollIntoView({ behavior: 'smooth' })} /> setDrawerOpen(false)} items={cart} onQty={onQty} onRemove={onRemove} onCheckout={onCheckout} /> setCheckoutOpen(false)} onConfirm={onConfirm} /> setOrderModal(null)} /> setWaitlistModal(null)} /> setEditVisible(false)} /> document.getElementById('waitlist')?.scrollIntoView({ behavior: 'smooth' })} show={showSticky && !checkoutOpen} /> > ); } // ================== SHOP APP ================== function ShopApp() { const [tweaks, setTweak] = useTweaks(); const [editVisible, setEditVisible] = useEditMode(); const [lang, setLang] = uS(() => load('frzn_lang', 'en')); const [cart, setCart] = uS(() => load('frzn_cart', [])); const [drawerOpen, setDrawerOpen] = uS(false); const [checkoutOpen, setCheckoutOpen] = uS(false); const [orderModal, setOrderModal] = uS(null); const [toast, setToast] = uS({ show: false, msg: '' }); const [liveTemp, setLiveTemp] = uS(3.6); const [filter, setFilter] = uS('all'); const [sort, setSort] = uS('featured'); uE(() => save('frzn_lang', lang), [lang]); uE(() => save('frzn_cart', cart), [cart]); uE(() => { const i = setInterval(() => setLiveTemp(t => Math.max(1.2, Math.min(7, t + (Math.random() - 0.5) * 0.5))), 2400); return () => clearInterval(i); }, []); const t = I18N[lang] || I18N.en; const showToast = (msg) => { setToast({ show: true, msg }); setTimeout(() => setToast(s => ({ ...s, show: false })), 2400); }; const addToCart = (product) => { const cartId = `${product.id}-${product.size}`; setCart(prev => { const existing = prev.find(it => it.cartId === cartId); if (existing) return prev.map(it => it.cartId === cartId ? { ...it, qty: it.qty + 1 } : it); return [...prev, { cartId, id: product.id, name: product.name, size: product.size, price: product.price, qty: 1 }]; }); showToast(`✓ ${product.name} · ${product.size} reserved`); }; const onQty = (id, q) => setCart(prev => q <= 0 ? prev.filter(it => it.cartId !== id) : prev.map(it => it.cartId === id ? { ...it, qty: q } : it)); const onRemove = (id) => setCart(prev => prev.filter(it => it.cartId !== id)); const onCheckout = () => { setDrawerOpen(false); setCheckoutOpen(true); }; const onConfirm = () => { setCheckoutOpen(false); setOrderModal({ title: <>Reservation locked.>, body: `Your place in Edition 001 is secured. You'll be billed only when we ship.` }); setCart([]); }; let list = CATALOG; if (filter !== 'all') list = list.filter(p => p.cat === filter); if (sort === 'price-asc') list = [...list].sort((a,b)=>a.price-b.price); if (sort === 'price-desc') list = [...list].sort((a,b)=>b.price-a.price); if (sort === 'new') list = [...list].sort((a,b)=> (b.tags.includes('NEW')?1:0) - (a.tags.includes('NEW')?1:0)); return ( <> s+it.qty,0)} onOpenCart={() => setDrawerOpen(true)} liveTemp={liveTemp} current="shop" /> EDITION 001 · {CATALOG.length} {t.shop.count} {renderTitle(t.shop.title, [1])} {t.shop.sub} {t.shop.filters.map(([k, l]) => { const count = k === 'all' ? CATALOG.length : CATALOG.filter(p => p.cat === k).length; return ( setFilter(k)}>{l} ({count})); })} {t.shop.sort} setSort(e.target.value)}> {t.shop.sortOpts.map(([k,l]) => {l})} {list.map(p => )} setDrawerOpen(false)} items={cart} onQty={onQty} onRemove={onRemove} onCheckout={onCheckout} /> setCheckoutOpen(false)} onConfirm={onConfirm} /> setOrderModal(null)} /> { setTweak(k,v); persistKey(k,v); }} onClose={() => setEditVisible(false)} /> > ); } Object.assign(window, { App, ShopApp });
{t.shop.sub}