LIGHT mode · photo-forward · UberEats/DoorDash pattern · Chy assembly reference · 2026-06-04
11 components · 5 with working JS interactions · single :root token block
/* LIGHT CONSUMER APP — DESIGN TOKENS */ :root { /* Backgrounds */ --bg-page: #f7f8fa; /* page/screen */ --bg-card: #ffffff; /* card surface */ --bg-elevated: #ffffff; /* nav bars */ --bg-divider: #e8eaed; /* hairline */ /* Text */ --text-primary: #1a1d24; /* headings + body */ --text-secondary: #5f636e; /* meta, labels */ --text-tertiary: #9ea3ae; /* placeholder */ /* Brand */ --brand-blue: #2a93c1; /* links, accents */ --brand-orange: #f1420b; /* CTAs, add-to-cart */ /* Status chips */ --status-open: #16a34a; --status-open-bg: #dcfce7; --status-closing: #d97706; --status-closing-bg: #fef3c7; --status-arriving: #2a93c1; --status-arriving-bg: #dbeafe; /* Radius */ --r-card: 16px; --r-btn: 50px; --r-chip: 100px; /* Shadows */ --shadow-card: 0 2px 12px rgba(26,29,36,0.08), 0 1px 3px rgba(26,29,36,0.06); --shadow-float: 0 8px 32px rgba(26,29,36,0.14), 0 2px 8px rgba(26,29,36,0.08); --shadow-nav: 0 -1px 0 rgba(26,29,36,0.08), 0 -4px 12px rgba(26,29,36,0.06); /* Fonts */ --font-display: 'Oswald', sans-serif; /* headings — brand thread */ --font-body: 'Inter', sans-serif; /* body */ }
<div class="app-bar">
<div class="app-bar__location">
<div class="app-bar__city">📍 Midtown, NYC <span class="arrow">⌄</span></div>
<div class="app-bar__street">342 W 42nd St</div>
</div>
<!-- Logotype: blue=PureBr+n, orange=ai, dark=.AI (light bg) -->
<span class="app-logotype">
<span class="p-br">PureBr</span><span class="p-ai">ai</span>
<span class="p-n">n</span><span class="p-dot">.AI</span>
</span>
<div class="app-bar__avatar">JR</div>
</div>
<div class="search-bar">
<span class="search-bar__icon">🔍</span>
<input class="search-bar__input" type="text"
placeholder="Search trucks, cuisines, dishes…" />
<button class="search-bar__filter">Filter</button>
</div>
Green dot pulses (CSS keyframes). Blue dot pulses on arrival. Amber is static.
<!-- Open Now: green, pulsing dot --> <span class="chip chip--open"> <span class="chip-dot"></span>Open Now </span> <!-- Closing Soon: amber, static --> <span class="chip chip--closing"> <span class="chip-dot"></span>Closing Soon </span> <!-- Arriving: blue (= brand-blue), pulsing dot --> <span class="chip chip--arriving"> <span class="chip-dot"></span>Arriving at 11:30 </span> /* CSS for pulsing dots: @keyframes pulse-green { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.4); } } */
<!-- Horizontal scroll — JS toggles .active class --> <div class="cuisine-scroll" id="cuisineScroll"> <div class="cuisine-chip active" data-cuisine="all"> <div class="cuisine-chip__icon">🍽️</div> <div class="cuisine-chip__label">All</div> </div> <div class="cuisine-chip" data-cuisine="tacos"> <div class="cuisine-chip__icon">🌮</div> <div class="cuisine-chip__label">Tacos</div> </div> <!-- ...more chips --> </div> <script> // Active toggle: swap .active on click document.querySelectorAll('.cuisine-chip').forEach(chip => { chip.addEventListener('click', () => { document.querySelectorAll('.cuisine-chip').forEach(c => c.classList.remove('active')); chip.classList.add('active'); // Emit custom event for filtering: document.dispatchEvent(new CustomEvent('cuisineChange', {detail: chip.dataset.cuisine})) }); }); </script>
<!-- Truck Card anatomy: photo(16:9) → name → rating+distance → wait → cuisine → add --> <div class="truck-card"> <div class="truck-card__photo"> <!-- [swap for real truck photo] — use CSS bg-image or <img> --> <div class="truck-card__status"> <span class="chip chip--open"><span class="chip-dot"></span>Open Now</span> </div> <div class="truck-card__distance">0.3 mi</div> </div> <div class="truck-card__body"> <div class="truck-card__row1"> <div class="truck-card__name">El Fuego Tacos</div> <div class="truck-card__rating"><span class="star">★</span>4.8 (142)</div> </div> <div class="truck-card__row2"> <div class="truck-card__meta"> <span class="truck-card__wait">~12 min wait</span> <!-- WAIT not delivery --> <span class="truck-card__cuisine">Mexican</span> </div> <button class="btn-add" data-card="1">+</button> </div> </div> </div> <script> document.querySelectorAll('.btn-add').forEach(btn => { btn.addEventListener('click', e => { e.stopPropagation(); btn.textContent = '✓'; btn.style.background = 'var(--status-open)'; setTimeout(() => { btn.textContent = '+'; btn.style.background = ''; }, 1400); }); }); </script>
<!-- Map view: CSS grid + road lines + truck markers + user dot + drawer In production: replace map-view__grid with Mapbox/Google Maps iframe or canvas. Marker positions come from truck lat/lng → CSS left/top via JS coordinate transform. --> <div class="map-view"> <div class="map-view__grid"></div> <!-- Truck marker --> <div class="map-marker" style="left:135px;top:70px"> <div class="map-marker__bubble open">🚚 El Fuego</div> <div class="map-marker__pin open"></div> </div> <div class="map-marker" style="left:80px;top:185px"> <div class="map-marker__bubble arriving">🚚 Green Garden</div> <div class="map-marker__pin arriving"></div> </div> <!-- User blue dot --> <div class="map-user" style="left:190px;top:160px"></div> <div class="map-overlay-pill"><span class="dot">●</span> 7 trucks near you</div> <!-- Horizontal mini-card drawer --> <div class="map-drawer"> <div class="map-drawer__handle"></div> <div class="map-drawer__title">Trucks near you</div> <div class="map-drawer__scroll"> <div class="map-mini-card"> ... </div> </div> </div> </div>
<!-- Profile hero + sticky tabs — tabs are sticky:top:0 in real app --> <div class="profile-hero"> <!-- [swap for real truck/food photo: background-image on .profile-hero] --> <div class="profile-hero__back">←</div> <div class="profile-hero__favorite" id="favoriteBtn">🤍</div> </div> <div class="profile-info"> ... </div> <!-- Sticky menu category tabs --> <div class="menu-tabs" id="menuTabs"> <div class="menu-tab active" data-tab="popular">Popular</div> <div class="menu-tab" data-tab="tacos">Tacos</div> <div class="menu-tab" data-tab="sides">Sides</div> <div class="menu-tab" data-tab="drinks">Drinks</div> </div> <script> // Sticky tab active switching document.querySelectorAll('#menuTabs .menu-tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('#menuTabs .menu-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Scroll to section: document.getElementById('section-' + tab.dataset.tab)?.scrollIntoView({behavior:'smooth'}); }); }); // Favorite toggle document.getElementById('favoriteBtn')?.addEventListener('click', function() { this.textContent = this.textContent === '🤍' ? '❤️' : '🤍'; }); </script>
<div class="menu-item">
<!-- 72x72 food photo — swap CSS gradient for background-image: url('truck-photo.jpg') -->
<div class="menu-item__photo"></div>
<div class="menu-item__body">
<div class="menu-item__name">Al Pastor Taco (3-pack)</div>
<div class="menu-item__desc">Marinated pork, pineapple, onion, cilantro, salsa verde.</div>
<div class="menu-item__price">$11.50</div>
</div>
<div class="menu-item__action"><button class="btn-add">+</button></div>
</div>
Page content area
<!-- Bottom Tab Nav: position:fixed in real app (bottom:0) --> <div class="bottom-nav" id="bottomNav"> <div class="bottom-nav__tab active" data-tab="home"> <span class="bottom-nav__icon">🏠</span> <span class="bottom-nav__label">Home</span> </div> <div class="bottom-nav__tab" data-tab="map"> <span class="bottom-nav__icon">🗺️</span> <span class="bottom-nav__label">Map</span> </div> <div class="bottom-nav__tab" data-tab="search"> <span class="bottom-nav__icon">🔍</span> <span class="bottom-nav__label">Search</span> </div> <div class="bottom-nav__tab" data-tab="orders" style="position:relative"> <span class="bottom-nav__icon">📦</span> <span class="bottom-nav__label">Orders</span> <span class="bottom-nav__badge">2</span> <!-- orange badge count --> </div> <div class="bottom-nav__tab" data-tab="profile"> <span class="bottom-nav__icon">👤</span> <span class="bottom-nav__label">Profile</span> </div> </div> <script> document.querySelectorAll('#bottomNav .bottom-nav__tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('#bottomNav .bottom-nav__tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Route: window.location.hash = tab.dataset.tab; }); }); </script>
<!-- Cart summary with qty buttons --> <div class="cart-summary"> <div class="cart-summary__header">Your Order <span class="cart-summary__edit">Edit</span></div> <div class="cart-item"> <div class="cart-item__qty"> <button class="qty-btn" data-action="dec" data-item="1">−</button> <span class="qty-num" id="qty1">2</span> <button class="qty-btn" data-action="inc" data-item="1">+</button> </div> <div class="cart-item__name">Al Pastor Taco (3-pack)</div> <div class="cart-item__price" id="price1">$23.00</div> </div> <div class="cart-totals"> <div class="cart-totals__total"><span>Total</span><span>$30.00</span></div> </div> </div> <!-- Floating cart FAB — position:fixed bottom:80px in real app --> <div class="cart-fab"> <div class="cart-fab__left"> <div class="cart-fab__count">3</div> <span class="cart-fab__label">View Order</span> </div> <span class="cart-fab__price">$30.00</span> </div> <script> const items = { 1: { qty: 2, unit: 11.50 }, 2: { qty: 1, unit: 5.50 } }; document.querySelectorAll('.qty-btn').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.item; if (btn.dataset.action === 'inc') items[id].qty++; else if (items[id].qty > 0) items[id].qty--; document.getElementById('qty'+id).textContent = items[id].qty; document.getElementById('price'+id).textContent = '$' + (items[id].qty * items[id].unit).toFixed(2); // recalc total + update cart-fab }); }); </script>
<!-- Order tracking: progress bar + wait minutes + step tracker --> <div class="tracking-card"> <div class="tracking-card__bar"> <div class="tracking-card__progress" style="width:55%"></div> <!-- JS updates width --> </div> <div class="tracking-card__body"> <div class="tracking-card__row1"> <div> <div class="tracking-card__status-label">Order in progress</div> <div class="tracking-card__title">El Fuego Tacos</div> </div> <div class="tracking-card__wait"> <div class="tracking-card__wait-num">8</div> <!-- WAIT time, not delivery --> <div class="tracking-card__wait-label">min wait</div> </div> </div> <div class="tracking-card__steps"> <div class="tracking-step done"> <div class="tracking-step__dot">✓</div> <div class="tracking-step__label">Confirmed</div> </div> <div class="tracking-connector done"></div> <div class="tracking-step current"> <div class="tracking-step__dot">🔥</div> <div class="tracking-step__label">Cooking</div> </div> <div class="tracking-connector"></div> <div class="tracking-step">...</div> </div> </div> </div>
Content above
<!-- CTA Bar: position:fixed bottom:0 in real app. Adjust bottom for safe-area-inset. --> <div class="cta-bar"> <button class="btn-secondary">Save for Later</button> <button class="btn-primary">🛒 Order Now · $30.00</button> </div> /* For real app: .cta-bar { position: fixed; bottom: 0; left: 0; right: 0; padding-bottom: calc(20px + env(safe-area-inset-bottom)); } */
Logotype spans preserved exactly: PureBrain.AI — blue/orange/blue/dark-on-light. The .AI reads as near-black (#1a1d24) on white (vs white on dark). All other color segments identical to the operator app.
Oswald Bold everywhere it matters: truck names, section headers, prices, CTA buttons, tab labels, app bar. This is the brand's personality weight — same font as the dark operator app.
Orange #f1420b for all CTAs: "Add", "Order Now", "View Order" floating cart, active nav tab, active cuisine chip — the action color is the same brand orange throughout.
Blue #2a93c1 for trust signals: links, Edit, arriving status chip (= brand-blue), user location dot, progress accents. Keeps the brand dual-color system alive.
Light because food demands it: #ffffff cards + #f7f8fa page. Food photography reads on white, not dark. This is the design research finding from UberEats/DoorDash: dark = operator tool, light = consumer app. The brand stays PureBrain through typography and color, not background.