// Commerce screens: Home, PDP, Box/Cart function GLHome({ app, go }) { // Launch catalog: pasta sets only — single-category UX, no filter tabs const items = GL_PASTA; const hero = { kicker: 'Complete Meal Kits', title: <>5-minute
Gourmet
Dinner, sub: 'Everything you need in one box.' }; const subMode = app.shopMode === 'sub'; return (
GUSTAI LAB
} right={ } />
{/* Hero */}
{hero.kicker}

{hero.title}

{hero.sub &&

{hero.sub}

}
{/* Shop mode toggle: Subscribe vs À la carte */}
{[ { id: 'alc', t1: 'À La Carte', t2: 'One-time order' }, { id: 'sub', t1: 'Subscribe', t2: 'Pre-pay · Save up to 12%' }, ].map(o => { const on = o.id === app.shopMode; return ( ); })}
{subMode && (
Tap items to build your standing order. Pick a schedule + duration on the next step. Pay once via QR — we deliver every cycle.
)}
{/* Section header */}

Pasta + Sauce Sets

Hand-made pasta & signature sauce
{/* Grid */}
{items.map(p => (
go('pdp', p)} style={{ cursor: 'pointer' }}>
{p.name}
Pasta · {p.pair}
{p.diet_labels && p.diet_labels.length > 0 && (
)}
{p.price} THB
))}
{/* Footer card */}
The Gustai Standard
Chef-curated pairings of fresh pasta and slow-cooked sauces.
); } // PDP — detailed "in the box" variant function GLProductDetail({ product, app, go }) { const [qty, setQty] = React.useState(1); // Mode is sourced from global app state so the PDP toggle stays in sync with // the Home toggle. Selecting here also flips the global mode for consistency. const mode = app.shopMode || 'alc'; const setMode = (m) => app.setShopMode(m); const p = product || GL_PASTA[0]; const fav = app.isFavorite ? app.isFavorite(p.id) : false; const setFav = () => app.toggleFavorite && app.toggleFavorite(p.id); const [expanded, setExpanded] = React.useState(''); const [shareToast, setShareToast] = React.useState(''); // '' | 'shared' | 'copied' async function handleShare() { const shareUrl = `${window.location.origin}${window.location.pathname}?product=${p.id}`; const shareData = { title: `${p.name} — Gustai Lab`, text: `${p.short_desc || p.name} · ฿${p.price} · Fresh pasta from Gustai Lab, Phuket.`, url: shareUrl, }; try { if (navigator.share && navigator.canShare && navigator.canShare(shareData)) { await navigator.share(shareData); setShareToast('shared'); } else { // Desktop fallback: copy link await navigator.clipboard.writeText(shareUrl); setShareToast('copied'); } } catch (e) { if (e.name !== 'AbortError') { // User cancelled — no toast needed try { await navigator.clipboard.writeText(shareUrl); setShareToast('copied'); } catch {} } } setTimeout(() => setShareToast(''), 2200); } return (
{/* Hero image */}
{/* Back */} {/* Share + Favorite */}
{/* Share / Copy toast */} {shareToast && (
{shareToast === 'copied' ? '🔗 Link Copied' : '✓ Shared'}
)}
{/* Content */}
{/* Marketing tags only — diet info flows through GLDietBadges below */} {(() => { const DIET_WORDS = ['VEGETARIAN','VEGAN','GLUTEN-FREE','DAIRY-FREE','GLUTEN FREE','DAIRY FREE','CONTAINS-PORK','CONTAINS-DAIRY','CONTAINS-GLUTEN','CONTAINS-EGGS','CONTAINS-NUTS','CONTAINS-SHELLFISH']; const marketing = (p.tags || []).filter(t => !DIET_WORDS.includes(String(t).toUpperCase())); if (!marketing.length) return null; return (
{marketing.map(t => ( {t} ))}
); })()}

{p.name.split(' ').map((w, i, arr) => ( {w}{i < arr.length - 1 &&
}
))}

{/* Short description — tagline under title */} {p.short_desc && (

{p.short_desc}

)} {/* Diet labels — prominent on PDP */} {p.diet_labels && p.diet_labels.length > 0 && (
)} {/* In the box pill */}
Pasta + Sauce + Toppings Included
{/* In the box list */}
In the box
{[`Fresh Extruded ${p.name}`, p.pair, 'Aged Parmigiano Reggiano'].map((item, i) => (
{item}
))}
{/* A la carte vs Subscribe toggle (synced with global shop mode) */}
{[ { id: 'alc', t1: 'A La Carte', t2: 'Single Set' }, { id: 'sub', t1: 'Subscribe', t2: 'Pre-pay · Save up to 12%' }, ].map(o => { const on = o.id === mode; return ( ); })}
{mode === 'sub' && (
Cancel anytime. Pause or skip drops from your profile.
)} {/* Accordions */} {[ { title: 'About', content: p.long_desc || 'Made in small batches each morning using imported Italian semolina. Ask us about seasonal specials and chef pairings.' }, { title: 'Ingredients', content: p.ingredients || '• Semolina flour (Imported from Italy)\n• Organic eggs\n• Fresh black summer truffle\n• White truffle oil\n• Sea salt' }, { title: 'Method', content: p.method || 'Bronze-extruded through custom dies for a rough, porous surface. Cold-pressed at under 40°C to preserve protein structure. Slow-dried for 24 hours minimum.' }, { title: 'Cooking Instructions', content: p.cooking_instr || '1. Bring a large pot of salted water to a rolling boil.\n2. Add pasta and cook 3–4 minutes until al dente.\n3. Reserve ½ cup pasta water, drain, and toss with sauce.' }, { title: 'Allergens', content: p.allergens || 'Contains: Gluten (wheat), Eggs. May contain traces of nuts. Please inform us of any dietary requirements.' }, { title: 'Storage', content: p.storage_info || 'Keep refrigerated below 4°C. Best consumed within 3 days of delivery. Can be frozen for up to 1 month.' }, ].map(({ title, content }) => (
{expanded === title && (
{content.split('\n').map((l, i) =>
{l}
)}
)}
))}
{/* Sticky bottom bar */}
{/* Qty stepper — used by both modes */}
{qty}
{mode === 'sub' ? ( ) : ( )}
); } // Box / Cart — branches between À la carte cart and Subscription template function GLBox({ app, go }) { const subMode = app.shopMode === 'sub'; const items = subMode ? app.subTemplate : app.cart; const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0); const delivery = subtotal > 0 ? 60 : 0; const total = subtotal + delivery; // À la carte total const perBox = subtotal; // Subscribe: total = subtotal per delivery (delivery handled in onboarding) const clearItems = subMode ? app.clearSubTemplate : app.clearCart; const updateItemQty = subMode ? app.updateSubQty : app.updateQty; const removeItem = subMode ? app.removeFromSubTemplate : app.removeFromCart; return (
go('home')} center={
{subMode ? 'MY SUBSCRIPTION' : 'MY BOX'}
} right={items.length > 0 && ( )}/>

{subMode ? <>My
Subscription : <>My
Box}

{subMode && items.length > 0 && (
These items will be in every box. Pick your schedule and how many deliveries to prepay on the next step.
)}
{items.length === 0 ? (
{subMode ? 'No items yet' : 'Your box is empty'}
{subMode ? 'Browse and tap items to add them to your standing order.' : 'Start with a fresh-extruded classic.'}
go('home')} variant="primary" style={{ maxWidth: 220, margin: '0 auto' }}>Browse Menu
) : (
{items.map((it, idx) => (
{!subMode && idx === 0 && (
Subscribe & Save 12%
)} {subMode && (
In every box · ×{it.qty}
)}
{it.name}
฿{it.price}{subMode ? '/box' : ''}
{it.qty}
))}
)}
{items.length > 0 && (
{subMode ? ( <>
Per box฿{perBox.toLocaleString()}
Delivery + duration set on next step
Per delivery ฿{perBox.toLocaleString()}
app.requireAuth('subscribe-onboard')} variant="teal" icon={}> Pick Schedule ) : ( <>
Subtotal฿{subtotal.toLocaleString()}
Delivery Fee฿{delivery}
Total ฿{total.toLocaleString()}
app.requireAuth('checkout')} variant="primary" icon={}> Checkout )}
)}
); } Object.assign(window, { GLHome, GLProductDetail, GLBox });