// 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 (
{onMenuClick ? : }
English
);
}
function FloatingActions() {
return (
Order History
);
}
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 (
);
}
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 */}
Your all-in-one food app
Discover, share, and unlock foodie deals!
Get the App
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 (
Select Order Method
);
}
// ── 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)}
/>
) : (
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( );