// IceYoo Desaru — mobile ordering page recreation (full menu) const { useState } = React; // ── icons ───────────────────────────────────────────────────── const IconBack = ({ size = 22 }) => ( ); const IconHamburger = ({ size = 22 }) => ( ); const IconGlobe = ({ size = 16 }) => ( ); const IconChev = ({ size = 14 }) => ( ); const IconReceipt = ({ size = 16 }) => ( ); const IconDots = ({ size = 18 }) => ( ); const IconInfo = ({ size = 18 }) => ( ); const IconMemberPlus = ({ size = 26 }) => ( ); // ── thumbnail primitives ───────────────────────────────────── // YY yellow badge const YYBadge = ({ cx = 50, cy = 46, r = 9 }) => ( YY ); // Iceyoo cup thumbnail — shaved-ice mound in a small cup with YY badge const IceyooThumb = ({ mound = '#3a2418', bg = '#fbeed2', topping }) => ( {/* cup */} {/* mound */} {topping} ); const BingsuThumb = ({ mound = '#f5f5f5', drip }) => ( {/* wood board */} {/* bowl */} {/* mound */} {drip} {/* side cups */} ); const ChickenWingThumb = ({ glaze = '#c13a1f' }) => ( {/* lettuce */} {/* wings clusters */} {[[36,52],[50,50],[64,54],[42,62],[58,62],[50,66]].map(([x,y], i) => ( ))} ); const PopcornChickenThumb = ({ glaze = '#d97a25' }) => ( {Array.from({length: 22}).map((_, i) => { const x = 22 + (i % 6) * 12 + (Math.floor(i/6)%2 ? 4 : 0); const y = 50 + Math.floor(i/6) * 7; return ; })} ); const NoodleBoxThumb = () => ( {/* kraft box */} {/* noodles */} {/* chicken bits */} {/* greens */} ); const WrapThumb = () => ( {/* two wraps */} {[[28,60,-10],[62,58,8]].map(([cx,cy,rot], i) => ( ))} ); const FriesThumb = ({ topping = 'cheese' }) => ( {/* black tray */} {/* fries pile */} {Array.from({length: 14}).map((_, i) => { const x = 22 + i * 4 + (i % 2 ? 1 : 0); const y = 50 + (i % 3) * 2; return ; })} {/* topping */} {topping === 'cheese' && ( )} {topping === 'teriyaki' && ( )} {topping === 'wedge' && ( {[30,40,50,60,70].map((x, i) => ( ))} )} ); const NuggetsThumb = () => ( {/* paper-lined cup */} {/* nuggets */} {[[36,38],[50,32],[62,40],[44,28],[58,28]].map(([x,y], i) => ( ))} ); const WaffleThumb = () => ( {/* plate */} {/* waffle */} {[0,1,2,3].map(c => [0,1,2].map(r => ( )))} {/* ice cream scoops */} ); // Woori tall blended cup — translucent dome lid, body color varies, 우리 wordmark const WooriThumb = ({ body = '#f7c948' }) => ( {/* shadow */} {/* cup body — tapered tumbler */} {/* dome lid */} {/* mound visible behind dome */} {/* highlight */} {/* 우리 wordmark */} 우리 ); const BowlThumb = () => ( {/* lettuce */} {/* bowl */} ); // 1+1 promo const PromoBanner = () => ( 1+1 RM18.9 ); // ── chrome ──────────────────────────────────────────────────── function HeroTopBar({ onMenuClick }) { // When onMenuClick is provided (mobile only), swap the kiosk's back arrow // for a hamburger menu that opens the mobile nav drawer. return (
); } function FloatingActions() { return (
); } function RestaurantHeader({ onChatClick }) { return (

IceYoo Desaru

Open until 10:00 PM (Sat)
{/* chat bubble — sits to the LEFT of the photo placeholder per design */}
AI Assistant
Join Member
); } // ── menu data ───────────────────────────────────────────────── const yyTopping = { oreo: , milo: , straw: , }; const sections = [ { title: 'YOOYOO SAVER', items: [ { name: '(Any 2) YooYoo Saver', thumb: , noImg: true }, { name: 'YS01. Oreo Iceyoo (SE)', kw: 'oreo shaved ice', thumb: }, { name: 'YS02. Milo Lava Iceyoo (SE)', kw: 'milo dessert', thumb: }, { name: 'YS03. Coconut Iceyoo (SE)', kw: 'coconut shaved ice', thumb: }, { name: 'YS04. Mango Iceyoo (SE)', kw: 'mango shaved ice', thumb: }, { name: 'YS05. Watermelon Iceyoo (SE)', kw: 'watermelon shaved ice', thumb: }, { name: 'YS06. Thai Tea Iceyoo (SE)', kw: 'thai tea dessert', thumb: }, { name: 'YS07. Milo Dinosaur Iceyoo (SE)', kw: 'chocolate dessert', thumb: }, { name: 'YS08. Popcorn Chicken Noodle (S)', kw: 'korean noodles', thumb: }, { name: 'YS09. Korean Chicken Wrap...', kw: 'chicken wrap', thumb: }, { name: 'YS10. Teriyaki Fries (M)..', kw: 'loaded fries', thumb: }, { name: 'YS11. Cheezy Wedges (M)', kw: 'potato wedges', thumb: }, { name: 'YS12. Cheezy Fries (M)', kw: 'cheese fries', thumb: }, { name: 'YS013. Chicken Nuggets (6 pcs).', kw: 'chicken nuggets', thumb: }, { name: 'YS14. Classic Honey Waffle w/ Ice Cream (2 pcs)', kw: 'waffle ice cream', thumb: }, ], }, { title: 'BINGSU', kw: 'bingsu', items: [ { name: 'CB01. MANGO BINGSU', thumb: }, { name: 'CB02. WATERMELON BINGSU', thumb: }, { name: 'CB03. HONEYDEW BINGSU', thumb: }, { name: 'CB04. COCONUT BINGSU', thumb: }, { name: 'CB05. LYCHEE BINGSU', thumb: }, { name: 'CB06. MUSANG KING DURIAN BINGSU', thumb: }, { name: 'CB07. CHOCOLATE OREO BINGSU', thumb: }/> }, { name: 'CB08. MILO LAVA BINGSU', thumb: }/> }, { name: 'CB09. MIX FRUIT FRUITY BINGSU', thumb: }/> }, { name: 'CB10. CHOCOLATE FRUITY BINGSU', sub: 'Chocolate ice flavour', thumb: }/> }, { name: 'CB11. TUTTI FRUITY BINGSU', sub: 'Mango ice flavour', thumb: }/> }, { name: 'CB12. BLUE YOGURT KITKAT BINGSU', thumb: }, { name: 'CB13. SOYA BEAN BINGSU', thumb: }, { name: 'CB14. MATCHA BINGSU', thumb: }, { name: 'CB15. MILK TEA BINGSU', thumb: }, { name: 'CB16. THAI TEA BINGSU', thumb: }, { name: 'CB17. CHOCOLATE CARAMEL BINGSU', thumb: }, { name: 'CB18. TIRAMISU BINGSU', thumb: }, { name: 'CB19. RED VELVET CAKE BINGSU', thumb: }, { name: 'CB20. OREO CHEESECAKE BINGSU', thumb: }, { name: 'CB21. STRAWBERRY CHEESECAKE BINGSU', thumb: }, { name: 'CB22. BLUEBERRY CHEESECAKE BINGSU', thumb: }, { name: 'CB24. KINDER BUENO BINGSU', thumb: }, ], }, { title: 'YOOYOO BOWL', kw: 'rice bowl', items: [ { name: 'YYB01. YooYoo Bowl', thumb: }, ], }, { title: 'WOORI ICE BLENDED', kw: 'smoothie drink', items: [ { name: 'W01. Summer Frutti Ice Blended', sub: 'Mixed with Grapefruit, Peach & Lychee', kw: 'mango smoothie', thumb: }, { name: 'W02. Tutti Frutti Ice Blended', sub: 'Mixed with Mango, Passion Fruit & Peach', kw: 'fruit smoothie', thumb: }, { name: 'W03. Oreo Ice Blended', kw: 'oreo milkshake', thumb: }, { name: 'W05. Coffee Ice Blended', kw: 'iced coffee', thumb: }, { name: 'W06. Local Flavoured Ice Blended', kw: 'matcha smoothie', thumb: }, ], }, { title: 'YOOYOO EAT', kw: 'korean fried chicken', items: [ { name: 'E01. KOREAN CHICKEN WINGETTE & DRUMETTE (6PCS)', kw: 'korean chicken wings', thumb: }, { name: 'E02. KOREAN CHICKEN WINGETTE & DRUMETTE (10PCS)', kw: 'korean chicken wings', thumb: }, { name: 'E03. KOREAN CHICKEN WINGETTE & DRUMETTE (16PCS)', kw: 'korean chicken wings', thumb: }, { name: 'E04. KOREAN POPCORN CHICKEN (ORIGINAL FRIED)', kw: 'popcorn chicken', thumb: }, { name: 'E05. KOREAN POPCORN CHICKEN (KOREAN SAUCE)', kw: 'spicy popcorn chicken', thumb: }, { name: 'E06. KOREAN POPCORN CHICKEN NOODLES', kw: 'korean noodles chicken', thumb: }, { name: 'E07. KOREAN CHICKEN WING NOODLES', kw: 'korean ramen chicken', thumb: }, { name: 'E08. CHICKEN WING & POPCORN CHICKEN WITH NOODLES', kw: 'korean fried chicken noodles', thumb: }, ], }, ]; // ── photo loader (local images/ folder, SVG fallback) ──────── // To register real images later, populate a window-scoped manifest: // window.FOOD_IMAGE_MANIFEST = new Set(['YS01', 'YS02', ...]); // FoodImage only attempts for codes in the manifest, avoiding 404s // in the console for items that don't have a real photo yet. function FoodImage({ code, fallback }) { const manifest = window.FOOD_IMAGE_MANIFEST; const hasPhoto = !!(code && manifest && manifest.has && manifest.has(code)); if (!hasPhoto) return fallback; const [errored, setErrored] = useState(false); const [loaded, setLoaded] = useState(false); if (errored) return fallback; const url = `images/${code}.jpg`; return (
{!loaded &&
{fallback}
} setLoaded(true)} onError={() => setErrored(true)} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', opacity: loaded ? 1 : 0, transition: 'opacity 0.25s', position: 'relative', zIndex: 1, }} />
); } // ── menu list ───────────────────────────────────────────────── function MenuItem({ thumb, name, sub, code, noImg }) { return (
{noImg ? thumb : }
{name}
{sub &&
{sub}
}
); } function MenuSection({ title, items }) { return (

{title}

{items.map((it, i) => { // Auto-derive image code from name (e.g. "YS01. Oreo Iceyoo" -> "YS01") const m = it.name.match(/^([A-Z]+\d+)/); const code = it.code || (m ? m[1] : null); return ; })}
); } function PromoBannerFeedVibe() { return (
{/* Left: copy */}
FeedVibe
Your all-in-one food app
Discover, share, and unlock foodie deals!
10K+ foodies · 4.2 rated
{/* Right: phone illustration */}
{/* yellow blob */} {/* dots */} {/* phone */}
{/* fingers holding phone */} {/* Like bubble */}
Like!
{/* Wow bubble */}
Wow!
{/* % starburst */} % {/* mascot */}
); } function SelectOrderCTA() { // Bottom CTA is back to a single full-width button — chat bubble moved // up into FloatingActions (top-right corner of the hero banner). return (
); } // ── page ────────────────────────────────────────────────────── function OrderPage({ onChatClick, onMenuClick }) { return (
{sections.map((s, i) => ( {s.title === 'WOORI ICE BLENDED' && } {i < sections.length - 1 && s.title !== 'WOORI ICE BLENDED' &&
} ))}
); } // ── Layout shell ──────────────────────────────────────────────── function useIsMobile(breakpoint = 768) { const [isMobile, setIsMobile] = React.useState( typeof window !== "undefined" ? window.innerWidth < breakpoint : false, ); React.useEffect(() => { const handler = () => setIsMobile(window.innerWidth < breakpoint); window.addEventListener("resize", handler); return () => window.removeEventListener("resize", handler); }, [breakpoint]); return isMobile; } function renderPage(route, onChatClick, chatPanel) { if (!route || route === "app/customer-facing-agent") { return { kind: "phone", content: ( {chatPanel} ), }; } if (route === "app/kitchen-agent") return { kind: "dashboard", content: }; if (route === "app/inventory-agent") return { kind: "dashboard", content: }; if (route === "summary") return { kind: "dashboard", content: }; if (route === "docs/cicd") return { kind: "dashboard", content: }; const item = window.findNavItem(route); if (item && item.file) { if (item.file.endsWith(".svg")) { return { kind: "dashboard", content: }; } return { kind: "dashboard", content: }; } return { kind: "dashboard", content: }; } function App() { const [chatOpen, setChatOpen] = React.useState(false); const [navOpen, setNavOpen] = React.useState(false); const route = window.useHashRoute(); const isMobile = useIsMobile(); // Defensive: chat and nav drawer should never be open by default after a // layout switch. Without this, an open chat on desktop survives a resize // to mobile width. React.useEffect(() => { if (isMobile) { setChatOpen(false); setNavOpen(false); } }, [isMobile]); // Close the nav drawer whenever the route changes (user picked an item). React.useEffect(() => { setNavOpen(false); }, [route]); const chatPanel = setChatOpen(false)} />; // Mobile: kiosk for the customer-facing route, dashboard pages for the // others. Sidebar + iPhone frame always hidden on mobile. // fm-mobile (positioned) is the containing block for ChatPanel's absolute // positioning so translateY(100%) actually moves it below the viewport. if (isMobile) { const isKioskRoute = !route || route === "app/customer-facing-agent"; const navItem = window.findNavItem ? window.findNavItem(route) : null; return (
{isKioskRoute ? ( setChatOpen(true)} onMenuClick={() => setNavOpen(true)} /> ) : (
{navItem ? navItem.label : "FeedMe"}
{renderPage(route, () => {}, null).content}
)}
{isKioskRoute && chatPanel} setNavOpen(false)} />
); } // Desktop: sidebar + content area. const page = renderPage(route, () => setChatOpen(true), chatPanel); return (
{page.kind === "phone" ? ( {page.content} ) : ( page.content )}
); } ReactDOM.createRoot(document.getElementById('root')).render();