// app/chrome.jsx — device frames (laptop / iPad / iPhone), device switcher, // responsive navigation (top header + mobile bottom nav), and the Shell wrapper. const { useState: useStateC, useEffect: useEffectC, useRef: useRefC, useCallback: useCallbackC } = React; // ───────────────────────────────────────────────────────────── // Fit-to-viewport scaling // ───────────────────────────────────────────────────────────── function useFitScale(outerW, outerH, { padX = 40, padY = 96, max = 1.4 } = {}) { const [scale, setScale] = useStateC(1); useEffectC(() => { const compute = () => { const availW = window.innerWidth - padX; const availH = window.innerHeight - padY; const k = Math.min(availW / outerW, availH / outerH, max); setScale(Math.max(0.12, k)); }; compute(); window.addEventListener('resize', compute); return () => window.removeEventListener('resize', compute); }, [outerW, outerH, padX, padY, max]); return scale; } // ───────────────────────────────────────────────────────────── // The device-switcher bar (lives outside the scaled frame) // ───────────────────────────────────────────────────────────── const DEVICE_ICONS = { laptop: (s) => ( ), ipad: (s) => ( ), iphone: (s) => ( ), }; function DeviceSwitcher() { const { device, pickDevice, exitDevice, resetAll } = useApp(); return (
luis cooks.
{['laptop', 'ipad', 'iphone'].map((d) => { const active = device === d; return ( ); })}
); } // ───────────────────────────────────────────────────────────── // Device frames — each renders the app (children) inside a fixed screen // ───────────────────────────────────────────────────────────── function ScreenSlot({ w, h, radius, children }) { return (
{children}
); } function LaptopFrame({ children }) { const { w, h } = DEVICE_SCREEN.laptop; const lidPadX = 16, lidPadTop = 16, chin = 30; const lidW = w + lidPadX * 2; const lidH = lidPadTop + h + chin; const baseExtra = 130; const baseW = lidW + baseExtra; const baseH = 22; const outerW = baseW; const outerH = lidH + baseH; const scale = useFitScale(outerW, outerH); return (
{/* Lid */}
{/* camera */}
{children} {/* chin logo */}
luis cooks.
{/* Base / hinge */}
); } function IpadFrame({ children }) { const { w, h } = DEVICE_SCREEN.ipad; const bezel = 26; const outerW = w + bezel * 2; const outerH = h + bezel * 2; const scale = useFitScale(outerW, outerH); return (
{/* camera on long top edge */}
{children}
); } function IphoneFrame({ children }) { const { w, h } = DEVICE_SCREEN.iphone; const bezel = 13; const outerW = w + bezel * 2; const outerH = h + bezel * 2; const scale = useFitScale(outerW, outerH, { max: 1.35 }); return (
{children} {/* Dynamic island */}
); } function DeviceFrame({ children }) { const { device } = useApp(); if (device === 'laptop') return {children}; if (device === 'ipad') return {children}; return {children}; } // ───────────────────────────────────────────────────────────── // Navigation // ───────────────────────────────────────────────────────────── const NAV_TABS = [ { id: 'recipes', label: 'Recipes', icon: (p) => }, { id: 'cook', label: 'Cook', icon: (p) => }, { id: 'plan', label: 'Plan', icon: (p) => }, { id: 'groceries', label: 'Groceries', icon: (p) => }, ]; function AvatarMenu() { const { logout, go, state } = useApp(); const [open, setOpen] = useStateC(false); return (
{open && ( <>
setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 30 }} />
Luis
{state.favourites.length} favourites
{[ { l: 'Settings', fn: () => { go('settings'); setOpen(false); } }, { l: 'From other kitchens', fn: () => { go('shared'); setOpen(false); } }, { l: 'Sign out', fn: () => { logout(); setOpen(false); } }, ].map((m) => ( ))}
)}
); } function AppHeader({ active, compact }) { const { setTab, go } = useApp(); const pad = compact ? '16px 32px' : '20px 48px'; return (
); } function MobileHeader({ title }) { const { setTab } = useApp(); return (
); } function MobileBottomNav({ active }) { const { setTab } = useApp(); return ( ); } // ───────────────────────────────────────────────────────────── // Shell — wraps a screen with chrome appropriate to the layout. // chrome: 'full' (header + nav), 'none' (bare, e.g. cook mode / login) // ───────────────────────────────────────────────────────────── function Shell({ tab, chrome = 'full', children }) { const { layout, isLive } = useApp(); // Prototype: 38px clears the fake iPhone dynamic island. Live: respect the real // device's safe-area inset (notch/status bar), falling back to a small inset. const frameStyle = { height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', position: 'relative', '--lc-mobile-top': isLive ? 'max(14px, env(safe-area-inset-top))' : '38px', '--lc-mobile-bottom': isLive ? 'max(0px, env(safe-area-inset-bottom))' : '0px' }; if (chrome === 'none') { return
{children}
; } if (layout === 'mobile') { return (
{children}
); } const compact = layout === 'tablet'; return (
{children}
); } Object.assign(window, { DeviceSwitcher, DeviceFrame, LaptopFrame, IpadFrame, IphoneFrame, AppHeader, MobileHeader, MobileBottomNav, Shell, NAV_TABS, DEVICE_ICONS, });