Food truck websiteOne-line
Mistral Large 4
by MistralReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 6 min 52 s
- Cost
- $0.071
- Output tokens
- 33,730
- Spent thinking
- 19,734 tokens
- Speed
- 82.2 tok/s
- Console errors
- 0
- First token after
- 1.7 s
- First visible text after
- 4 min 59 s
- Prompt tokens
- 375
- Code size
- 41.5 KB
- Finish reason
- stop
- Served by
- Mistral
- Model id
mistralai/mistral-large-4-0- Reasoning sent
{"effort":"high"}- Max output
- 262,144 tokens
- Generated
- Nov 12, 2026
What it was asked
The prompt
“i'm opening a korean taco food truck called "Kimchi Loco", make me a website that makes people really want to come”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 788 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kimchi Loco — Korean Tacos Food Truck</title>
<style>
:root{
--red:#E8391D; --red-dark:#C22A12; --ink:#201310; --cream:#FFF4E3; --paper:#FFF9EF;
--yellow:#FFC93C; --green:#4E9B2E; --orange:#F08A2E;
--font-display:'Arial Black','Archivo Black',Impact,'Helvetica Neue',Arial,sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:smooth;scroll-padding-top:84px;}
html,body{overflow-x:hidden;}
body{
font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
background:var(--paper); color:var(--ink);
-webkit-font-smoothing:antialiased;
}
body::after{
content:""; position:fixed; inset:0; pointer-events:none; z-index:60; opacity:.05;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}
.display{font-family:var(--font-display);font-weight:900;text-transform:uppercase;letter-spacing:-.02em;line-height:.92;}
.dots-dark{background-image:radial-gradient(rgba(32,19,16,.16) 1.5px,transparent 1.7px);background-size:18px 18px;}
.dots-light{background-image:radial-gradient(rgba(255,244,227,.13) 1.5px,transparent 1.7px);background-size:18px 18px;}
.outline-cream{color:var(--cream);}
.outline-ink{color:var(--ink);}
@supports ((-webkit-text-stroke:2px #000)){
.outline-cream{color:transparent;-webkit-text-stroke:3px var(--cream);}
.outline-ink{color:transparent;-webkit-text-stroke:3px var(--ink);}
}
img,svg{display:block;max-width:100%;}
button{font-family:inherit;}
/* ---------- ticker ---------- */
.ticker{background:var(--yellow);color:var(--ink);border-bottom:3px solid var(--ink);overflow:hidden;white-space:nowrap;padding:9px 0;}
.ticker-track{display:inline-flex;width:max-content;animation:tick 24s linear infinite;}
.ticker-track span{font-family:var(--font-display);font-size:13px;letter-spacing:.06em;text-transform:uppercase;padding-right:8px;}
@keyframes tick{to{transform:translateX(-50%);}}
/* ---------- nav ---------- */
.nav{
position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:26px;
background:var(--ink);color:var(--cream);padding:14px 24px;border-bottom:4px solid var(--red);
}
.logo{font-family:var(--font-display);font-size:19px;text-transform:uppercase;text-decoration:none;color:var(--cream);display:flex;align-items:center;gap:10px;white-space:nowrap;}
.logo-mark{background:var(--yellow);color:var(--ink);border-radius:9px;padding:3px 9px;transform:rotate(-4deg);display:inline-block;font-size:17px;}
.nav-links{display:flex;gap:22px;margin-left:auto;}
.nav-links a{color:var(--cream);text-decoration:none;font-weight:800;font-size:13px;letter-spacing:.1em;text-transform:uppercase;}
.nav-links a:hover{color:var(--yellow);text-decoration:underline wavy var(--yellow) 2px;text-underline-offset:5px;}
.cart-btn{
background:var(--red);color:var(--cream);border:3px solid var(--cream);border-radius:999px;
padding:8px 16px;font-weight:900;font-size:14px;cursor:pointer;white-space:nowrap;
box-shadow:3px 3px 0 rgba(255,244,227,.3);transition:transform .15s;
}
.cart-btn:hover{transform:rotate(-2deg) scale(1.05);}
#cartCount{display:inline-block;}
#cartCount.bump{animation:bump .35s ease;}
@keyframes bump{40%{transform:scale(1.6) rotate(10deg);}}
/* ---------- hero ---------- */
.hero{
position:relative;background-color:var(--red);color:var(--cream);
display:grid;grid-template-columns:1.1fr .9fr;gap:36px;align-items:center;
max-width:1220px;margin:0 auto;padding:60px 24px 110px;
}
.hero h1{font-size:clamp(58px,9vw,122px);}
.hero h1 .l2{display:inline-block;transform:rotate(-2deg);}
.hero-sub{margin-top:22px;font-size:clamp(16px,1.6vw,20px);font-weight:600;max-width:46ch;line-height:1.45;}
.hero-ctas{display:flex;gap:16px;flex-wrap:wrap;margin-top:30px;}
.hero-stats{display:flex;gap:20px;flex-wrap:wrap;margin-top:28px;font-weight:800;font-size:14px;letter-spacing:.02em;}
.hero-stats span{background:rgba(32,19,16,.28);padding:8px 14px;border-radius:999px;border:2px solid rgba(255,244,227,.35);}
.hero-art{position:relative;display:flex;justify-content:center;}
.hero-art::before{content:"";position:absolute;inset:6% 2%;background:radial-gradient(circle,rgba(255,201,60,.55),transparent 65%);border-radius:50%;}
.bob{animation:bob 5s ease-in-out infinite;position:relative;}
@keyframes bob{50%{transform:translateY(-12px) rotate(-1.5deg);}}
.boing-target{cursor:pointer;}
.boing-target.boing{animation:boing .55s ease;}
@keyframes boing{30%{transform:scale(1.1,.88);}55%{transform:scale(.93,1.07);}78%{transform:scale(1.04,.97);}}
.taco{width:min(100%,440px);filter:drop-shadow(0 26px 22px rgba(0,0,0,.28));}
.steam path{opacity:0;animation:steamUp 2.8s ease-in-out infinite;}
.steam .s2{animation-delay:.9s;}
.steam .s3{animation-delay:1.8s;}
@keyframes steamUp{0%{opacity:0;transform:translateY(10px);}35%{opacity:.85;}100%{opacity:0;transform:translateY(-16px);}}
.sticker{
position:absolute;left:-4px;top:44%;width:108px;height:108px;border-radius:50%;
background:var(--yellow);color:var(--ink);border:3px solid var(--ink);
display:grid;place-items:center;text-align:center;font-family:var(--font-display);
font-size:13px;line-height:1.15;text-transform:uppercase;box-shadow:4px 4px 0 var(--ink);
transform:rotate(-10deg);animation:floaty 4s ease-in-out infinite;
}
@keyframes floaty{50%{transform:rotate(-6deg) translateY(-8px);}}
.spin-wrap{position:absolute;top:-14px;right:-4px;width:118px;height:118px;}
.spin-badge{width:100%;height:100%;animation:spin 16s linear infinite;}
.spin-badge text{font-family:var(--font-display);font-size:12.5px;letter-spacing:2.6px;fill:var(--cream);font-weight:900;}
.spin-center{position:absolute;inset:0;display:grid;place-items:center;font-size:30px;}
@keyframes spin{to{transform:rotate(360deg);}}
.wave{position:absolute;left:0;bottom:-1px;width:100%;height:56px;}
/* ---------- buttons ---------- */
.btn{
font-family:var(--font-display);text-transform:uppercase;font-size:15px;letter-spacing:.03em;
padding:16px 26px;border-radius:999px;border:3px solid var(--ink);cursor:pointer;
box-shadow:5px 5px 0 var(--ink);transition:transform .15s,box-shadow .15s;text-decoration:none;display:inline-block;
}
.btn:hover{transform:translate(-2px,-2px);box-shadow:8px 8px 0 var(--ink);}
.btn:active{transform:translate(3px,3px);box-shadow:0 0 0 var(--ink);}
.btn-yellow{background:var(--yellow);color:var(--ink);}
.btn-ghost{background:transparent;color:var(--cream);border-color:var(--cream);box-shadow:5px 5px 0 rgba(32,19,16,.45);}
.btn-ghost:hover{box-shadow:8px 8px 0 rgba(32,19,16,.45);}
.btn-red{background:var(--red);color:var(--cream);border-color:var(--cream);box-shadow:5px 5px 0 rgba(255,244,227,.3);}
.btn-red:hover{box-shadow:8px 8px 0 rgba(255,244,227,.3);}
.btn-red:active{box-shadow:0 0 0 rgba(255,244,227,.3);}
/* ---------- sections ---------- */
.section{max-width:1220px;margin:0 auto;padding:84px 24px;}
.eyebrow{display:inline-block;background:var(--ink);color:var(--yellow);padding:7px 15px;border-radius:999px;font-weight:900;font-size:12px;letter-spacing:.14em;text-transform:uppercase;margin-bottom:16px;}
.eyebrow.red{background:var(--red);color:var(--cream);}
.sec-head h2{font-size:clamp(40px,6vw,74px);}
.sec-head .sub{margin-top:16px;font-size:17px;font-weight:600;max-width:56ch;line-height:1.5;}
.sec-head.center{text-align:center;}
.sec-head.center .sub{margin-left:auto;margin-right:auto;}
/* ---------- menu ---------- */
.menu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:24px;margin-top:44px;}
.card{
background:#fff;border:3px solid var(--ink);border-radius:20px;overflow:hidden;
box-shadow:7px 7px 0 var(--ink);transition:transform .2s,box-shadow .2s;
}
.card:hover{transform:translateY(-7px) rotate(-1deg);box-shadow:11px 13px 0 var(--ink);}
.card-art{position:relative;height:150px;display:grid;place-items:center;border-bottom:3px solid var(--ink);}
.card:nth-child(1) .card-art{background:#FFD98A;}
.card:nth-child(2) .card-art{background:#FF9E8A;}
.card:nth-child(3) .card-art{background:#B9E48A;}
.emoji{font-size:64px;filter:drop-shadow(3px 4px 0 rgba(32,19,16,.2));}
.card:hover .emoji{animation:wiggle .45s ease;}
@keyframes wiggle{25%{transform:rotate(-12deg) scale(1.15);}60%{transform:rotate(10deg) scale(1.1);}}
.heat{position:absolute;top:10px;right:12px;font-size:13px;background:var(--ink);color:var(--cream);padding:4px 9px;border-radius:999px;}
.card-body{padding:18px 20px 20px;}
.card-body h3{font-family:var(--font-display);font-size:21px;text-transform:uppercase;}
.card-body p{margin-top:8px;font-size:14px;line-height:1.45;font-weight:500;}
.card-foot{display:flex;align-items:center;justify-content:space-between;margin-top:16px;}
.price{background:var(--red);color:var(--cream);font-family:var(--font-display);font-size:17px;padding:5px 14px;border-radius:999px;transform:rotate(-4deg);border:2px solid var(--ink);}
.tag{background:var(--green);color:#fff;font-weight:900;font-size:10.5px;letter-spacing:.1em;padding:5px 10px;border-radius:999px;border:2px solid var(--ink);text-transform:uppercase;}
.sides{display:flex;flex-wrap:wrap;gap:14px;margin-top:28px;align-items:center;}
.sides-label{font-family:var(--font-display);font-size:14px;text-transform:uppercase;margin-right:6px;}
.side{background:#fff;border:3px solid var(--ink);border-radius:999px;padding:10px 18px;font-weight:800;font-size:14px;box-shadow:4px 4px 0 var(--ink);transition:transform .15s;}
.side:hover{transform:rotate(-2deg) scale(1.05);}
.side b{color:var(--red-dark);}
/* ---------- builder ---------- */
.build{background-color:var(--ink);color:var(--cream);}
.build-grid{display:grid;grid-template-columns:1fr 1fr;gap:52px;align-items:center;margin-top:48px;}
.build-preview{text-align:center;position:relative;}
.build-taco{width:min(100%,380px);margin:0 auto;filter:drop-shadow(0 22px 20px rgba(0,0,0,.5));}
.layer{opacity:0;transform:scale(.5);transform-box:fill-box;transform-origin:center;transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .22s;}
.layer.on{opacity:1;transform:scale(1);}
.price-big{font-family:var(--font-display);font-size:clamp(44px,5vw,60px);color:var(--yellow);margin-top:8px;display:inline-block;}
.price-big.pop{animation:pop .3s ease;}
@keyframes pop{0%{transform:scale(1.35) rotate(-4deg);}100%{transform:scale(1);}}
.preview-cap{font-weight:700;letter-spacing:.14em;text-transform:uppercase;font-size:12px;opacity:.75;margin-top:4px;}
.chips{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:24px;}
.chip{
background:transparent;border:2px solid rgba(255,244,227,.4);color:var(--cream);
padding:11px 16px;border-radius:999px;cursor:pointer;font-weight:800;font-size:14px;
display:flex;gap:8px;align-items:center;transition:all .15s;
}
.chip b{color:var(--yellow);font-weight:900;}
.chip:hover{border-color:var(--cream);transform:translateY(-2px);}
.chip.active{background:var(--red);border-color:var(--red);}
.build-btns{display:flex;gap:14px;flex-wrap:wrap;}
.chip-note{font-size:13px;opacity:.7;margin-top:18px;font-weight:600;}
/* ---------- truck schedule ---------- */
.truck{background-color:var(--yellow);}
.day-pills{display:flex;flex-wrap:wrap;gap:10px;margin-top:36px;}
.pill{
font-family:var(--font-display);font-size:14px;padding:11px 18px;border-radius:999px;
border:3px solid var(--ink);background:var(--cream);color:var(--ink);cursor:pointer;
box-shadow:3px 3px 0 var(--ink);transition:all .15s;text-transform:uppercase;
}
.pill:hover{transform:translate(-1px,-1px);box-shadow:5px 5px 0 var(--ink);}
.pill.active{background:var(--ink);color:var(--yellow);}
.pill.today::after{content:" ●";color:var(--red);}
.pill.active.today::after{color:var(--yellow);}
.sched-card{
position:relative;display:flex;align-items:center;gap:26px;flex-wrap:wrap;
background:var(--cream);border:3px solid var(--ink);border-radius:22px;
box-shadow:9px 9px 0 var(--ink);padding:26px 30px;margin-top:26px;
}
.sched-card.swap{animation:fadeUp .45s ease;}
@keyframes fadeUp{from{opacity:0;transform:translateY(14px);}}
.sched-day{
font-family:var(--font-display);font-size:38px;background:var(--red);color:var(--cream);
width:96px;height:96px;border-radius:18px;display:grid;place-items:center;
border:3px solid var(--ink);flex-shrink:0;transform:rotate(-3deg);
}
.sched-info h3{font-family:var(--font-display);font-size:clamp(20px,2.4vw,28px);text-transform:uppercase;}
.sched-time{font-weight:900;margin-top:6px;font-size:16px;}
.sched-note{margin-top:6px;font-weight:600;font-size:14px;opacity:.8;}
.today-badge{
position:absolute;top:-16px;right:26px;background:var(--green);color:#fff;
font-weight:900;font-size:12px;letter-spacing:.12em;padding:6px 14px;border-radius:999px;
border:3px solid var(--ink);display:none;text-transform:uppercase;
}
/* ---------- reviews ---------- */
.reviews{background-color:var(--red);color:var(--cream);overflow:hidden;}
.rev-track{display:flex;width:max-content;margin-top:44px;animation:slide 34s linear infinite;}
.reviews:hover .rev-track{animation-play-state:paused;}
@keyframes slide{to{transform:translateX(-50%);}}
.rev-card{
width:310px;flex-shrink:0;margin-right:22px;background:var(--cream);color:var(--ink);
border:3px solid var(--ink);border-radius:18px;padding:22px;box-shadow:6px 6px 0 var(--ink);
}
.rev-card .stars{color:var(--red);font-size:15px;letter-spacing:2px;}
.rev-card p{margin-top:10px;font-weight:700;font-size:15px;line-height:1.45;}
.rev-name{display:block;margin-top:12px;font-weight:900;font-size:13px;text-transform:uppercase;letter-spacing:.06em;}
/* ---------- story ---------- */
.story-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:52px;align-items:center;margin-top:10px;}
.story-grid p{margin-top:18px;font-size:17px;line-height:1.6;font-weight:500;}
.stats{display:grid;grid-template-columns:1fr 1fr;gap:20px;}
.stat{background:#fff;border:3px solid var(--ink);border-radius:18px;padding:24px;box-shadow:6px 6px 0 var(--ink);}
.num{font-family:var(--font-display);font-size:clamp(34px,4vw,48px);color:var(--red-dark);display:block;}
.lbl{display:block;margin-top:6px;font-weight:900;font-size:12px;letter-spacing:.1em;text-transform:uppercase;}
/* ---------- cta ---------- */
.cta-panel{
background-color:var(--red);color:var(--cream);border:4px solid var(--ink);border-radius:26px;
box-shadow:12px 12px 0 var(--ink);padding:clamp(40px,6vw,72px);text-align:center;
}
.cta-panel h2{font-size:clamp(46px,7vw,92px);}
.cta-panel p{margin:18px auto 0;max-width:52ch;font-weight:600;font-size:16px;line-height:1.5;}
.nl-form{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:30px;}
.nl-form input{
padding:15px 20px;border-radius:999px;border:3px solid var(--ink);font-size:15px;font-weight:700;
min-width:min(320px,80vw);background:var(--cream);color:var(--ink);outline:none;
}
.nl-form input:focus{box-shadow:0 0 0 4px var(--yellow);}
.nl-form.shake{animation:shake .4s;}
@keyframes shake{20%{transform:translateX(-8px);}45%{transform:translateX(7px);}70%{transform:translateX(-5px);}90%{transform:translateX(3px);}}
.form-msg{margin-top:16px;font-weight:900;min-height:22px;font-size:15px;}
.form-msg.ok{color:var(--yellow);}
.form-msg.err{color:#FFD0C4;}
.cta-small{margin-top:14px;font-size:13px;font-weight:700;opacity:.85;}
/* ---------- footer ---------- */
footer{background:var(--ink);color:var(--cream);padding:56px 24px 34px;}
.foot-grid{max-width:1220px;margin:0 auto;display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:36px;}
.foot-grid h4{font-family:var(--font-display);font-size:15px;text-transform:uppercase;color:var(--yellow);margin-bottom:14px;}
.foot-grid p,.foot-grid a{color:var(--cream);font-size:14px;line-height:1.7;text-decoration:none;font-weight:600;}
.foot-grid a:hover{color:var(--yellow);}
.foot-brand .logo{font-size:22px;}
.foot-brand p{margin-top:14px;max-width:34ch;opacity:.85;}
.foot-bottom{max-width:1220px;margin:40px auto 0;padding-top:22px;border-top:2px solid rgba(255,244,227,.2);font-size:12.5px;font-weight:600;opacity:.7;}
/* ---------- toast ---------- */
#toast{
position:fixed;left:50%;bottom:26px;transform:translate(-50%,20px);z-index:120;
background:var(--ink);color:var(--cream);border:3px solid var(--yellow);
padding:13px 22px;border-radius:14px;font-weight:800;font-size:14px;
opacity:0;pointer-events:none;transition:all .3s;max-width:min(90vw,500px);text-align:center;
}
#toast.show{opacity:1;transform:translate(-50%,0);}
/* ---------- responsive ---------- */
@media (max-width:940px){
.hero{grid-template-columns:1fr;padding-top:48px;text-align:center;}
.hero-ctas,.hero-stats{justify-content:center;}
.hero-sub{margin-left:auto;margin-right:auto;}
.hero-art{margin-top:16px;}
.build-grid,.story-grid{grid-template-columns:1fr;gap:40px;}
.foot-grid{grid-template-columns:1fr;gap:28px;}
.nav-links{display:none;}
.nav{justify-content:space-between;}
}
@media (max-width:480px){
.section{padding:60px 18px;}
.sticker{width:88px;height:88px;font-size:11px;left:-2px;}
.spin-wrap{width:96px;height:96px;top:-10px;right:-2px;}
.sched-card{gap:18px;padding:22px;}
.stats{grid-template-columns:1fr 1fr;gap:14px;}
}
@media (prefers-reduced-motion:reduce){
*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;}
html{scroll-behavior:auto;}
}
</style>
</head>
<body>
<!-- TICKER -->
<div class="ticker" aria-hidden="true">
<div class="ticker-track">
<span>Kimchi Loco 🌮 Korean BBQ × street tacos ★ Fermented 48 hours ★ 3 tacos = $12 ★ Find the truck tonight ★ Hot & fresh, never frozen ★ Seoul meets LA ★ </span>
<span>Kimchi Loco 🌮 Korean BBQ × street tacos ★ Fermented 48 hours ★ 3 tacos = $12 ★ Find the truck tonight ★ Hot & fresh, never frozen ★ Seoul meets LA ★ </span>
</div>
</div>
<!-- NAV -->
<header class="nav">
<a class="logo" href="#top"><span class="logo-mark">🌮</span> Kimchi Loco</a>
<nav class="nav-links">
<a href="#menu">Menu</a>
<a href="#build">Build</a>
<a href="#truck">Truck</a>
<a href="#story">Story</a>
</nav>
<button class="cart-btn" id="cartBtn" type="button">🛒 Bag (<span id="cartCount">0</span>)</button>
</header>
<!-- HERO -->
<section class="hero dots-dark" id="top">
<div class="hero-copy">
<h1 class="display">Kimchi<br><span class="l2 outline-cream">Loco</span></h1>
<p class="hero-sub">Korean BBQ meets street tacos. 48-hour fermented kimchi, bulgogi seared to order, all stuffed into warm corn tortillas. Fermented, fiery, and usually gone by 10pm.</p>
<div class="hero-ctas">
<a class="btn btn-yellow" href="#menu">See the menu</a>
<a class="btn btn-ghost" href="#truck">Find the truck</a>
</div>
<div class="hero-stats">
<span>★ 4.9 — 2,300+ reviews</span>
<span>3 tacos = $12</span>
<span>Open 7 days a week</span>
</div>
</div>
<div class="hero-art">
<div class="bob">
<div class="boing-target" id="heroTaco" title="Click the taco">
<svg class="taco" viewBox="0 0 320 300" role="img" aria-label="A steaming kimchi taco">
<defs>
<linearGradient id="shellG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#F7B955"/><stop offset="1" stop-color="#E07F2E"/>
</linearGradient>
</defs>
<g class="steam" stroke="#FFF6E8" stroke-width="7" fill="none" stroke-linecap="round">
<path class="s1" d="M118 92 C110 76 126 68 118 52 C110 36 124 28 120 12"/>
<path class="s2" d="M162 88 C154 72 170 64 162 48 C154 32 168 24 164 8"/>
<path class="s3" d="M206 92 C198 76 214 68 206 52 C198 36 212 28 208 12"/>
</g>
<ellipse cx="160" cy="262" rx="128" ry="20" fill="#F3D9A4"/>
<ellipse cx="160" cy="258" rx="112" ry="15" fill="#FBE7C0"/>
<g>
<path d="M46 178 L58 122 L80 150 L102 106 L124 142 L146 98 L168 138 L190 100 L212 142 L234 108 L256 148 L274 124 L274 200 L46 200 Z" fill="#8BC34A"/>
<path d="M58 182 L76 142 L98 168 L120 134 L142 164 L164 130 L186 162 L208 136 L230 166 L252 142 L264 172 L264 205 L58 205 Z" fill="#E23A22"/>
<circle cx="95" cy="160" r="4" fill="#A81B0C"/>
<circle cx="150" cy="150" r="4.5" fill="#A81B0C"/>
<circle cx="205" cy="158" r="4" fill="#A81B0C"/>
<circle cx="235" cy="170" r="3.5" fill="#A81B0C"/>
<ellipse cx="120" cy="176" rx="30" ry="13" fill="#7A4A21" transform="rotate(-6 120 176)"/>
<ellipse cx="185" cy="172" rx="32" ry="14" fill="#74431F" transform="rotate(5 185 172)"/>
<ellipse cx="152" cy="160" rx="26" ry="11" fill="#8A5427"/>
<path d="M108 172 l14 -3 M172 168 l15 3 M146 156 l12 -2" stroke="#5C3416" stroke-width="2.4" stroke-linecap="round"/>
<ellipse cx="100" cy="150" rx="6" ry="3.5" fill="#3E7C1F" transform="rotate(-20 100 150)"/>
<ellipse cx="170" cy="145" rx="6" ry="3.5" fill="#3E7C1F" transform="rotate(15 170 145)"/>
<ellipse cx="220" cy="152" rx="6" ry="3.5" fill="#3E7C1F" transform="rotate(-10 220 152)"/>
</g>
<path d="M28 240 A132 132 0 0 1 292 240 Z" fill="url(#shellG)"/>
<path d="M28 240 A132 132 0 0 1 292 240" fill="none" stroke="#FBD38D" stroke-width="7" stroke-linecap="round"/>
<ellipse cx="90" cy="205" rx="14" ry="8" fill="#C96A24" opacity=".55" transform="rotate(-18 90 205)"/>
<ellipse cx="160" cy="185" rx="16" ry="9" fill="#C96A24" opacity=".5"/>
<ellipse cx="232" cy="208" rx="13" ry="7" fill="#C96A24" opacity=".55" transform="rotate(15 232 208)"/>
<ellipse cx="125" cy="228" rx="10" ry="6" fill="#C96A24" opacity=".45"/>
<ellipse cx="200" cy="230" rx="11" ry="6" fill="#C96A24" opacity=".45"/>
<g fill="#FFE9B0">
<ellipse cx="70" cy="215" rx="4" ry="2.4" transform="rotate(-24 70 215)"/>
<ellipse cx="110" cy="192" rx="4" ry="2.4" transform="rotate(12 110 192)"/>
<ellipse cx="150" cy="215" rx="4" ry="2.4" transform="rotate(-8 150 215)"/>
<ellipse cx="190" cy="195" rx="4" ry="2.4" transform="rotate(20 190 195)"/>
<ellipse cx="245" cy="222" rx="4" ry="2.4" transform="rotate(-14 245 222)"/>
<ellipse cx="255" cy="200" rx="3.6" ry="2.2" transform="rotate(30 255 200)"/>
</g>
</svg>
</div>
</div>
<div class="sticker">100%<br>Kimchi<br>🌶️</div>
<div class="spin-wrap" aria-hidden="true">
<svg class="spin-badge" viewBox="0 0 120 120">
<defs><path id="circ" d="M60,60 m-45,0 a45,45 0 1,1 90,0 a45,45 0 1,1 -90,0"/></defs>
<text><textPath href="#circ">KIMCHI LOCO ★ SEOUL × STREET ★ FRESH DAILY ★ </textPath></text>
</svg>
<span class="spin-center">🌮</span>
</div>
</div>
<svg class="wave" viewBox="0 0 1440 60" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 60 L0 32 Q120 4 240 32 T480 32 T720 32 T960 32 T1200 32 T1440 32 L1440 60 Z" fill="#FFF9EF"/>
</svg>
</section>
<!-- MENU -->
<section class="section" id="menu">
<div class="sec-head">
<span class="eyebrow">The goods</span>
<h2 class="display">The <span class="outline-ink">Lineup</span></h2>
<p class="sub">Three tacos. Zero regrets. All on warm corn tortillas with a side of attitude. Heat level: your call.</p>
</div>
<div class="menu-grid">
<article class="card">
<div class="card-art"><span class="emoji">🥩</span><span class="heat">🌶️🌶️🌶️</span></div>
<div class="card-body">
<h3>The Loco</h3>
<p>Soy-garlic bulgogi, house kimchi, gochujang aioli, crispy garlic, sesame.</p>
<div class="card-foot"><span class="price">$5.50</span><span class="tag">Best seller</span></div>
</div>
</article>
<article class="card">
<div class="card-art"><span class="emoji">🧀</span><span class="heat">🌶️🌶️</span></div>
<div class="card-body">
<h3>Kimchi Queso</h3>
<p>Crispy pork belly, kimchi, queso fresco, lime crema, pickled jalapeño.</p>
<div class="card-foot"><span class="price">$6.00</span><span class="tag">Extra messy</span></div>
</div>
</article>
<article class="card">
<div class="card-art"><span class="emoji">🍗</span><span class="heat">🌶️🌶️🌶️🌶️</span></div>
<div class="card-body">
<h3>Seoul Fire</h3>
<p>Double-fried chicken, sticky gochujang glaze, pickled daikon, cilantro.</p>
<div class="card-foot"><span class="price">$5.75</span><span class="tag">Handle w/ care</span></div>
</div>
</article>
</div>
<div class="sides">
<span class="sides-label">Sides:</span>
<span class="side">🍟 Kimchi fries <b>$5</b> 🌶️🌶️</span>
<span class="side">🌽 Elote cup <b>$4.50</b> 🌶️</span>
<span class="side">🥤 Horchata <b>$3.50</b></span>
<span class="side">🥬 Extra kimchi <b>$2</b></span>
</div>
</section>
<!-- BUILDER -->
<section class="section build dots-light" id="build">
<div class="sec-head center">
<span class="eyebrow red">Interactive</span>
<h2 class="display">Build your <span class="outline-cream">Loco</span></h2>
<p class="sub" style="color:var(--cream)">Start with a warm tortilla, stack it your way. Tap the toppings — watch your taco grow in real time.</p>
</div>
<div class="build-grid">
<div class="build-preview">
<svg class="build-taco" viewBox="0 0 320 300" role="img" aria-label="Your custom taco preview">
<defs>
<linearGradient id="shellG2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#F7B955"/><stop offset="1" stop-color="#E07F2E"/>
</linearGradient>
<clipPath id="shellClip"><path d="M28 240 A132 132 0 0 1 292 240 Z"/></clipPath>
</defs>
<ellipse cx="160" cy="262" rx="128" ry="20" fill="#3A241A"/>
<ellipse cx="160" cy="258" rx="112" ry="15" fill="#4A2E20"/>
<path d="M28 240 A132 132 0 0 1 292 240 Z" fill="url(#shellG2)"/>
<g clip-path="url(#shellClip)">
<g class="layer on" id="ly-lettuce">
<path d="M40 240 L55 190 L75 218 L95 184 L115 214 L135 182 L155 212 L175 184 L195 214 L215 186 L235 216 L255 190 L280 220 L280 262 L40 262 Z" fill="#7CB342"/>
</g>
<g class="layer" id="ly-protein">
<ellipse cx="120" cy="196" rx="34" ry="14" fill="#8A5427" transform="rotate(-5 120 196)"/>
<ellipse cx="185" cy="192" rx="36" ry="15" fill="#74431F" transform="rotate(4 185 192)"/>
<ellipse cx="152" cy="178" rx="30" ry="12" fill="#96602E"/>
<path d="M106 192 l15 -3 M172 188 l16 3 M142 174 l13 -2" stroke="#5C3416" stroke-width="2.4" stroke-linecap="round"/>
</g>
<g class="layer" id="ly-kimchi">
<ellipse cx="110" cy="168" rx="22" ry="12" fill="#E23A22"/>
<ellipse cx="200" cy="162" rx="24" ry="13" fill="#E23A22"/>
<ellipse cx="155" cy="155" rx="20" ry="11" fill="#D42F16"/>
<circle cx="104" cy="165" r="3.4" fill="#A81B0C"/>
<circle cx="205" cy="158" r="3.6" fill="#A81B0C"/>
<circle cx="158" cy="152" r="3" fill="#A81B0C"/>
</g>
<g class="layer" id="ly-cheese">
<path d="M70 160 L82 146 L94 160 L106 144 L118 158 L130 145 L142 159 L154 146 L166 160 L178 147 L190 161 L202 148 L214 162 L226 150 L238 164 L250 152 L250 174 L70 174 Z" fill="#FFC93C"/>
</g>
<g class="layer" id="ly-jalapeno">
<circle cx="115" cy="188" r="8" fill="none" stroke="#3E8E22" stroke-width="4.5"/>
<circle cx="168" cy="181" r="8" fill="none" stroke="#3E8E22" stroke-width="4.5"/>
<circle cx="215" cy="190" r="8" fill="none" stroke="#3E8E22" stroke-width="4.5"/>
</g>
<g class="layer" id="ly-cilantro">
<ellipse cx="100" cy="178" rx="7" ry="4" fill="#3E7C1F" transform="rotate(-18 100 178)"/>
<ellipse cx="145" cy="168" rx="7" ry="4" fill="#3E7C1F" transform="rotate(12 145 168)"/>
<ellipse cx="190" cy="183" rx="7" ry="4" fill="#3E7C1F" transform="rotate(-8 190 183)"/>
<ellipse cx="228" cy="170" rx="7" ry="4" fill="#3E7C1F" transform="rotate(20 228 170)"/>
</g>
<g class="layer" id="ly-sauce">
<path d="M70 206 q8 -12 16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0" stroke="#FFF6E8" stroke-width="5" fill="none" stroke-linecap="round"/>
</g>
</g>
<path d="M28 240 A132 132 0 0 1 292 240" fill="none" stroke="#FBD38D" stroke-width="7" stroke-linecap="round"/>
<ellipse cx="90" cy="205" rx="14" ry="8" fill="#C96A24" opacity=".55" transform="rotate(-18 90 205)"/>
<ellipse cx="160" cy="185" rx="16" ry="9" fill="#C96A24" opacity=".5"/>
<ellipse cx="232" cy="208" rx="13" ry="7" fill="#C96A24" opacity=".55" transform="rotate(15 232 208)"/>
<g fill="#FFE9B0">
<ellipse cx="70" cy="215" rx="4" ry="2.4" transform="rotate(-24 70 215)"/>
<ellipse cx="110" cy="192" rx="4" ry="2.4" transform="rotate(12 110 192)"/>
<ellipse cx="150" cy="215" rx="4" ry="2.4" transform="rotate(-8 150 215)"/>
<ellipse cx="190" cy="195" rx="4" ry="2.4" transform="rotate(20 190 195)"/>
<ellipse cx="245" cy="222" rx="4" ry="2.4" transform="rotate(-14 245 222)"/>
</g>
</svg>
<div><span class="price-big" id="buildPrice">$4.50</span></div>
<div class="preview-cap">Your loco, live</div>
</div>
<div class="build-controls">
<div class="chips" id="chips">
<button class="chip" type="button" data-layer="protein" data-price="1.75"><span>🥩</span> Bulgogi beef <b>+$1.75</b></button>
<button class="chip" type="button" data-layer="kimchi" data-price="1.00"><span>🥬</span> House kimchi <b>+$1.00</b></button>
<button class="chip" type="button" data-layer="cheese" data-price="1.00"><span>🧀</span> Queso <b>+$1.00</b></button>
<button class="chip" type="button" data-layer="jalapeno" data-price="0.50"><span>🌶️</span> Jalapeño <b>+$0.50</b></button>
<button class="chip" type="button" data-layer="cilantro" data-price="0.25"><span>🌿</span> Cilantro <b>+$0.25</b></button>
<button class="chip" type="button" data-layer="sauce" data-price="0.50"><span>🥄</span> Gochujang aioli <b>+$0.50</b></button>
</div>
<div class="build-btns">
<button class="btn btn-ghost" id="randomBtn" type="button">🎲 Surprise me</button>
<button class="btn btn-red" id="addBtn" type="button">Add to bag 🛒</button>
</div>
<p class="chip-note">Base taco $4.50 includes warm corn tortilla + lettuce. Pay at the window — cash, card, or compliments.</p>
</div>
</div>
</section>
<!-- TRUCK SCHEDULE -->
<section class="section truck dots-dark" id="truck">
<div class="sec-head">
<span class="eyebrow">GPS: always moving</span>
<h2 class="display">Find the <span class="outline-ink">truck</span></h2>
<p class="sub">We roll to a different spot every day. Tap a day to jump to it — or just follow the smell of gochujang. 📍</p>
</div>
<div class="day-pills" id="dayPills"></div>
<div class="sched-card" id="schedCard">
<div class="sched-day display" id="scDay">MON</div>
<div class="sched-info">
<h3 id="scLoc">—</h3>
<p class="sched-time" id="scTime">—</p>
<p class="sched-note" id="scNote">—</p>
</div>
<div class="today-badge" id="scToday">📍 Today!</div>
</div>
</section>
<!-- REVIEWS -->
<section class="reviews dots-dark section" id="reviews">
<div class="sec-head center">
<span class="eyebrow red">Receipts</span>
<h2 class="display">Word on the <span class="outline-cream">street</span></h2>
</div>
<div class="rev-track" id="revTrack"></div>
</section>
<!-- STORY -->
<section class="section" id="story">
<div class="sec-head">
<span class="eyebrow">Our story</span>
<h2 class="display">Fermented. <span class="outline-ink">Fired.</span> Served.</h2>
</div>
<div class="story-grid">
<div>
<p>Kimchi Loco started with one grill, one grandma's kimchi recipe, and a dream. We ferment our kimchi for 48 hours in the truck's tiny fridge, sear the bulgogi to order, and stuff it all into warm corn tortillas.</p>
<p>No freezer. No shortcuts. Just loud flavors, a line worth waiting in, and a horn that honks when the kimchi runs out (it happens).</p>
</div>
<div class="stats">
<div class="stat"><span class="num" data-count="38000">0</span><span class="lbl">Tacos served</span></div>
<div class="stat"><span class="num" data-count="4.9" data-dec="1">0</span><span class="lbl">Avg rating ★</span></div>
<div class="stat"><span class="num" data-count="48">0</span><span class="lbl">Hr kimchi ferment</span></div>
<div class="stat"><span class="num" data-count="7">0</span><span class="lbl">Days a week on the road</span></div>
</div>
</div>
</section>
<!-- CTA -->
<section class="section" id="visit">
<div class="cta-panel dots-dark">
<h2 class="display">Hungry <span class="outline-cream">yet?</span></h2>
<p>Drop your email and we'll send you <b>tonight's truck location</b> every morning. No spam — just tacos. 🌮</p>
<form class="nl-form" id="nlForm" novalidate>
<input type="email" id="nlEmail" placeholder="you@hungry.com" aria-label="Email address">
<button class="btn btn-yellow" type="submit">Sign me up</button>
</form>
<p class="form-msg" id="nlMsg"></p>
<p class="cta-small">Or just follow the smell of gochujang. You'll find us. 📍 Los Angeles, CA</p>
</div>
</section>
<!-- FOOTER -->
<footer>
<div class="foot-grid">
<div class="foot-brand">
<a class="logo" href="#top"><span class="logo-mark">🌮</span> Kimchi Loco</a>
<p>A Korean-Mexican food truck serving fermented, fiery, unforgettable tacos all over Los Angeles.</p>
</div>
<div>
<h4>Hours</h4>
<p>Mon – Sun<br>Times vary by location —<br>check the truck tracker above ☝️</p>
</div>
<div>
<h4>Find us</h4>
<p><a href="#truck">@kimchiloco</a> — everywhere<br><a href="#visit">hello@kimchiloco.com</a><br>Book us for your event! 🎉</p>
</div>
</div>
<div class="foot-bottom">© 2025 Kimchi Loco Food Truck Co. — Made with 🌶️ and 48-hour kimchi in Los Angeles.</div>
</footer>
<div id="toast" role="status"></div>
<script>
const $ = s => document.querySelector(s);
/* ---------- toast ---------- */
const toastEl = $('#toast');
let toastTimer;
function toast(msg){
toastEl.textContent = msg;
toastEl.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove('show'), 2600);
}
/* ---------- hero taco boing ---------- */
const heroTaco = $('#heroTaco');
heroTaco.addEventListener('click', () => {
heroTaco.classList.remove('boing');
void heroTaco.offsetWidth;
heroTaco.classList.add('boing');
toast('🌮 Boing. The taco approves.');
});
heroTaco.addEventListener('animationend', () => heroTaco.classList.remove('boing'));
/* ---------- taco builder ---------- */
const BASE = 4.50;
const priceEl = $('#buildPrice');
const chips = [...document.querySelectorAll('.chip')];
function updatePrice(){
let total = BASE;
document.querySelectorAll('.chip.active').forEach(c => total += parseFloat(c.dataset.price));
priceEl.textContent = '$' + total.toFixed(2);
priceEl.classList.remove('pop');
void priceEl.offsetWidth;
priceEl.classList.add('pop');
}
chips.forEach(chip => {
chip.addEventListener('click', () => {
const on = chip.classList.toggle('active');
document.getElementById('ly-' + chip.dataset.layer).classList.toggle('on', on);
updatePrice();
});
});
$('#randomBtn').addEventListener('click', () => {
chips.forEach(c => {
c.classList.remove('active');
document.getElementById('ly-' + c.dataset.layer).classList.remove('on');
});
const pool = [...chips].sort(() => Math.random() - 0.5);
const n = 3 + Math.floor(Math.random() * 3); // 3–5 toppings
pool.slice(0, n).forEach(c => {
c.classList.add('active');
document.getElementById('ly-' + c.dataset.layer).classList.add('on');
});
updatePrice();
toast('🎲 The chef picked your toppings!');
});
/* ---------- bag / cart ---------- */
let cart = 0;
const cartCount = $('#cartCount');
$('#addBtn').addEventListener('click', () => {
cart++;
cartCount.textContent = cart;
cartCount.classList.remove('bump');
void cartCount.offsetWidth;
cartCount.classList.add('bump');
toast('🌮 Added! ' + cart + (cart === 1 ? ' item' : ' items') + ' in your bag — pay at the window.');
});
$('#cartBtn').addEventListener('click', () => {
toast(cart === 0
? '🛒 Your bag is empty — build a loco above! ⬆️'
: '🛒 ' + cart + (cart === 1 ? ' item' : ' items') + ' in your bag. See you at the truck window!');
});
/* ---------- weekly schedule ---------- */
const SCHED = [
{ d:'MON', loc:'Arts District — 5th & Mateo', time:'5:00 PM – 11:00 PM', note:'First stop of the week. Come hungry.' },
{ d:'TUE', loc:'Silver Lake — Sunset Blvd', time:'5:00 PM – 10:00 PM', note:'Taco Tuesday: 3 tacos for $10 all night.' },
{ d:'WED', loc:'Grand Central Market', time:'11:00 AM – 9:00 PM', note:'Lunch-rush friendly — taco in under 10 min.' },
{ d:'THU', loc:'DTLA — Grand Ave & 2nd', time:'5:00 PM – 12:00 AM', note:'Late-night kimchi hits different.' },
{ d:'FRI', loc:'Smorgasburg LA — ROW DTLA', time:'6:00 PM – 1:00 AM', note:'Biggest line of the week. Worth it.' },
{ d:'SAT', loc:'Abbot Kinney, Venice', time:'12:00 PM – 11:00 PM', note:'Beach day = taco day.' },
{ d:'SUN', loc:'Melrose Trading Post', time:'12:00 PM – 8:00 PM', note:'Chill vibes, hot kimchi.' }
];
const todayIdx = (new Date().getDay() + 6) % 7; // Monday = 0
const pillsWrap = $('#dayPills');
const card = $('#schedCard');
let cur = todayIdx, autoTimer;
SCHED.forEach((s, i) => {
const b = document.createElement('button');
b.type = 'button';
b.className = 'pill' + (i === todayIdx ? ' today' : '');
b.textContent = s.d;
b.addEventListener('click', () => { setDay(i); restartAuto(); });
pillsWrap.appendChild(b);
});
function setDay(i){
cur = i;
[...pillsWrap.children].forEach((p, j) => p.classList.toggle('active', j === i));
$('#scDay').textContent = SCHED[i].d;
$('#scLoc').textContent = '📍 ' + SCHED[i].loc;
$('#scTime').textContent = '🕐 ' + SCHED[i].time;
$('#scNote').textContent = SCHED[i].note;
$('#scToday').style.display = (i === todayIdx) ? 'block' : 'none';
card.classList.remove('swap');
void card.offsetWidth;
card.classList.add('swap');
}
function restartAuto(){
clearInterval(autoTimer);
autoTimer = setInterval(() => setDay((cur + 1) % 7), 4000);
}
setDay(todayIdx);
restartAuto();
/* ---------- reviews marquee ---------- */
const REVS = [
{ q:'The bulgogi + kimchi combo is criminal. I\u2019ve been back three times this week.', n:'Dana R., Silver Lake' },
{ q:'I don\u2019t even like spicy food and the Seoul Fire converted me.', n:'Marcus T., DTLA' },
{ q:'Found them at Smorgasburg at midnight. Best decision of my life.', n:'Jess & Priya' },
{ q:'Kimchi fries >>> regular fries. Fight me.', n:'@taco.tuesday, IG' },
{ q:'The whole block smells like a Seoul street stall — in the best way.', n:'Kevin O., Venice' },
{ q:'3 tacos for $12 and I still had room. Unreal.', n:'Alicia M., Echo Park' }
];
const cardHTML = REVS.map(r =>
'<div class="rev-card"><div class="stars">★★★★★</div><p>\u201C' + r.q + '\u201D</p><span class="rev-name">— ' + r.n + '</span></div>'
).join('');
$('#revTrack').innerHTML = cardHTML + cardHTML;
/* ---------- animated counters ---------- */
function animateNum(el){
const target = parseFloat(el.dataset.count);
const dec = parseInt(el.dataset.dec || '0', 10);
const dur = 1500;
const start = performance.now();
function step(t){
const p = Math.min(1, (t - start) / dur);
const eased = 1 - Math.pow(1 - p, 3);
const val = target * eased;
el.textContent = dec ? val.toFixed(dec) : Math.round(val).toLocaleString('en-US');
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting){ animateNum(e.target); io.unobserve(e.target); }
});
}, { threshold: 0.4 });
document.querySelectorAll('.num').forEach(n => io.observe(n));
/* ---------- newsletter ---------- */
const nlForm = $('#nlForm'), nlEmail = $('#nlEmail'), nlMsg = $('#nlMsg');
nlForm.addEventListener('submit', e => {
e.preventDefault();
const v = nlEmail.value.trim();
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v)) {
nlMsg.textContent = '🔥 You\u2019re in! Tonight\u2019s truck spot lands in your inbox.';
nlMsg.className = 'form-msg ok';
nlEmail.value = '';
toast('🌮 Welcome to the Loco list!');
} else {
nlMsg.textContent = 'Hmm, that email looks a little under-cooked — try again 🌶️';
nlMsg.className = 'form-msg err';
nlForm.classList.add('shake');
setTimeout(() => nlForm.classList.remove('shake'), 450);
}
});
</script>
</body>
</html>Reasoning trace (19,734 tokens)
The user wants a website for a Korean taco food truck called "Kimchi Loco". I need to create a self-contained HTML file with inline CSS/JS, no external resources (except allowed CDN URLs — but safest to avoid external fonts; I can use system fonts or Google Fonts? The rules say "No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task." The task doesn't explicitly allow any CDN, so I should avoid external requests entirely. Use system font stacks, CSS-drawn graphics, SVG inline, emoji maybe.
Requirements:
- One fenced code block with complete file, ```html
- Everything inline
- No localStorage etc.
- Looks right at 1280×800, adapts to 375px, no horizontal scrollbar
- Something meaningful visible on first paint (screenshot ~3s after load)
- Interesting stuff visible within first seconds
- If keyboard used, show "Click or press a key to start" — I can make an interactive element but must have demo mode. Maybe I don't need keyboard at all. But maybe add a fun interactive game? The rule says games must start in self-playing demo mode behind a "Click or press a key to start" message. I don't necessarily need a game. But "makes people really want to come" — a food truck site with menu, location schedule, marquee, animations, hover effects, a "build your taco" interactive maybe.
I could add a small interactive "heat meter" or a spinning wheel? Keep it simple but polished: hero with animated taco illustration (SVG/CSS), marquee banner, menu with cards, location schedule for the week, "find the truck" map-ish, reviews, Instagram-style gallery (CSS drawn), newsletter signup (fake, no storage), footer.
Interactive elements that work on click (mouse works without iframe click? Mouse clicks should work; keyboard needs click first). I can add:
- A "taco builder" where clicking ingredients updates price and a preview — all mouse-driven, visible immediately.
- A weekly schedule with day tabs.
- Animated steam, marquee, floating kimchi.
Let me design:
Brand: Kimchi Loco — Korean-Mexican fusion food truck. Colors: vibrant — kimchi red (#E63312 / #FF3B1F), cabbage green, sesame cream, dark charcoal. Bold typography with system fonts: use "Archivo Black"? Not available offline. Use font stack: 'Arial Black', 'Helvetica Neue', sans-serif with heavy weights; letter-spacing. Could use Google Fonts via CDN? Not explicitly allowed. Safer: system fonts. Use font-weight 900 with Arial/Helvetica; add stretched transforms.
Layout:
1. Top marquee ticker: "KIMCHI LOCO ★ SEOUL MEETS TIJUANA ★ ..." scrolling.
2. Sticky nav: logo, links (Menu, Truck Tracker, Story, Order), CTA button "Find the truck".
3. Hero: big headline "KIMCHI LOCO" with "KOREAN TACOS. ZERO CHILL." subhead, CTA buttons, animated taco SVG with steam, floating ingredients, star burst "EST. 2024". Background red with halftone dots pattern (CSS radial-gradient). Big type, maybe rotated sticker badges.
4. "How it works" / specialties strip: three cards — Kimchi Queso, Bulgogi Burrito... Actually tacos: Bulgogi Taco, Kimchi Carnitas, Gochujang Wings, Spam Musubi? Keep tacos:
- "The Loco" — bulgogi, kimchi, gochujang aioli
- "Kimchi Queso" — crispy pork belly, kimchi, queso
- "Seoul Fire" — spicy chicken, pickled daikon
Prices.
5. Interactive taco builder: click ingredients to add/remove, live price, SVG taco preview that fills with clicked items (layers change). This is engaging and visible. Mouse-driven so fine.
6. Weekly schedule / "Where to find us" — day-of-week tabs (click to switch), showing location + hours + map-pin styled card. Could auto-cycle every few seconds so it's interesting without interaction, with manual click.
7. Reviews marquee or cards with big quotes.
8. Gallery strip of "dishes" drawn with CSS/SVG (emoji-based cards maybe: 🌮 but emoji fine).
9. Big CTA: "Hungry? Come find us." + newsletter input (fake submit shows inline message, no alert).
10. Footer with hours, socials (text links), "© Kimchi Loco".
Animations: CSS keyframes — marquee scroll, steam rising (SVG paths with animation), wiggle on hover, floating badges, price pop. Maybe a subtle grain overlay.
Taco SVG: draw a taco shell (semicircle), with layers for ingredients that appear when selected in builder. I can build a dedicated builder taco: shell + layers (protein, kimchi red, cilantro green, cheese yellow, sauce). Each toggle adds a layer group with a little pop animation.
Schedule data: Mon–Sun with locations: e.g., Mon: Arts District — 5PM–11PM; Tue: ... Auto-rotate highlight every 3s, pause on hover; click to select. Show "Today" based on actual date via JS (getDay) — nice touch.
Also a "heat level" meter on menu items: 🌶️🌶️🌶️.
Responsive: grid with auto-fit minmax, clamp() font sizes, media query for 375px. Ensure no horizontal scroll: overflow-x hidden on body, marquee uses transform.
First paint: hero visible immediately with animations running — good.
Let me write the code carefully.
Key pieces:
CSS variables:
--red: #E8391D; --deep: #C22A12; --ink: #1A1210; --cream: #FFF3E0 / #FFE8C7; --green: #4C9A2A or kimchi green #6BA43A; --yellow: #FFC93C; --sesame: #F5E6C8.
Halftone dots background: background-image: radial-gradient(rgba(0,0,0,.12) 1.5px, transparent 1.5px); background-size: 18px 18px.
Marquee: .marquee-track { display:flex; animation: scroll 20s linear infinite; } with duplicated content width max-content.
Hero taco SVG: I'll draw:
- plate ellipse
- taco shell: path semicircle-ish: M20,120 Q100,20 180,120 ... Actually a taco standing: shell as a half-moon. Let me craft: viewBox 0 0 300 260. Shell: path "M30 200 C 30 90, 270 90, 270 200 Z" filled with gradient orange, with darker toasted spots (circles). Fillings poking above top edge: green lettuce zigzag, red kimchi, etc. Simpler: draw fillings as wavy path along top arc, clipped.
Simpler approach: taco viewed from side, like an open taco: shell is a "U"/bowl shape: path M20,80 Q150,220 280,80 L280,110 Q150,250 20,110 Z? Hmm.
Let me do a classic side-view taco: shell semicircle opening upward:
- Outer shell: path d="M20 190 A130 130 0 0 1 280 190 Z" — that's a half circle with flat bottom at y=190, dome up. Fillings stick out above the dome: draw wavy shapes between x=40..260 around y=70..190 region but clipped to inside? Actually fillings should appear above the shell's top edge (the arc). The arc top is at y=60 (center 150,190 r130 → top at y=60). Fillings drawn as blobs with y from ~40 to ~120, then shell drawn over bottom part. Order: draw fillings first, then shell on top covering lower parts. That works: shell path covers from arc down; fillings peek above.
Add steam: three wavy paths above, animated with translateY and opacity keyframes, stroke cream.
Also sesame seeds: small ellipses on shell.
Builder taco: same shell, layers as groups toggled:
- protein (bulgogi) — brown rounded rect blob
- kimchi — red blobs
- cheese — yellow drip
- cilantro — green leaves
- sauce (gochujang aioli) — white zigzag line
- jalapeño — green rings
Each with id, class .layer, hidden by default (opacity 0, transform scale). Toggle via JS class .on with transition + pop keyframe.
Price: base $4.50 + each ingredient $1.25 etc. Show total with pop animation. Also a "random loco" button that picks random combo. And "Add to order" button that increments a cart count in nav with a little bounce — fun. Cart count displayed in nav CTA "Order (n)".
Schedule: array of 7 days with name, location, area, time, note. Render tabs; auto-advance every 3.5s; clicking sets manual and resets timer. Highlight today with "TODAY" badge using new Date().getDay().
Menu cards: 3 tacos + sides (kimchi fries, elote, horchata). Card hover: lift + rotate slightly, image area with big emoji in a circle with halftone bg, heat peppers, price tag sticker rotated.
Reviews: horizontally scrolling marquee of quote cards (auto-scroll) — duplicate for loop.
Story section: split layout, text + stat counters (tacos served counting up on load via JS? Counters animate when visible — simple: animate on load with requestAnimationFrame; visible in first seconds if placed mid-page... screenshot at 3s shows top of page mostly. Keep hero rich.)
Newsletter: input + button; on submit preventDefault, show "You're on the list! 🌶️" inline message.
Nav: smooth scroll to sections via anchor links (href="#menu" etc.) — fine.
"Click or press a key to start" — only needed if keyboard used. I might add keyboard: pressing arrow keys in builder? Not necessary. I'll make everything mouse-driven, plus maybe keydown listener for "L" easter egg? Skip keyboard entirely; no message needed. But rule says if demo uses keyboard display message. I'll avoid keyboard dependence. Maybe add spacebar to randomize taco? Not needed.
Fonts: system stack. Headings: font-family: "Arial Black", "Archivo Black", "Helvetica Neue", Arial, sans-serif; font-weight 900; letter-spacing tight; text-transform uppercase. Could also use 'Impact' for display: "Impact, 'Arial Black', sans-serif" gives condensed poster feel. I'll use a combo: display uses 'Arial Black' with transform scaleY(1.05)? Keep simple.
Add Google-font-like feel via font-stretch not reliable. Fine.
Let me also add a subtle noise/grain via SVG feTurbulence data URI? Data URIs are inline, allowed (not network). Could use background-image: url("data:image/svg+xml,...") with feTurbulence — that's inline, fine. Keep small.
Halftone + grain overlays give print poster vibe.
Sticker badges: rotated circles with dashed border: "100% KIMCHI", "SEOUL × TJ", "EST. 2025".
Let me now think about sections order and IDs:
- header nav (sticky)
- marquee ticker top (above nav? put ticker at very top, then nav sticky below). Actually sticky nav with ticker above that scrolls away. Simpler: nav sticky top; ticker below hero? Common: ticker at top. I'll put ticker at top (not sticky), nav sticky.
Hmm, sticky nav: position: sticky; top:0; z-index. Ticker above it scrolls away. Good.
Sections:
1. Hero (#top): left text, right taco SVG with steam + floating badges. Big headline lines: "KIMCHI" "LOCO" with outlined text stroke for one word ( -webkit-text-stroke ). Sub: "Korean BBQ meets street tacos. Fermented, fiery, gone by 10pm." CTAs: "See the menu" (scroll), "Find the truck" (scroll). Small stats row: "4.9★ 2,300+ reviews · 3 tacos $12".
2. Ticker divider (red on cream) between sections maybe. One top ticker enough; add second between menu and schedule.
3. #menu: "THE LINEUP" heading with squiggle underline SVG. Grid of cards.
4. #builder: "BUILD YOUR LOCO" — left: taco SVG preview + price; right: ingredient chips list with toggle. Buttons: "Surprise me" (random), "Add to order" → cart count++.
5. #truck: "FIND THE TRUCK" — schedule tabs Mon..Sun + detail card with big location, hours, note; auto-rotating; TODAY badge.
6. Reviews marquee: "WORD ON THE STREET".
7. #story: about snippet with stats (counters animate on load or on intersection — use IntersectionObserver; fallback run after load). Keep simple: animate on load with rAF; numbers: 48h fermented kimchi, 12 toppings, 1 very happy truck. Actually counters: "38,000+ tacos served", "4.9★ average", "7 days a week". Animate count-up when in view via IntersectionObserver (supported in Chrome). Fine.
8. CTA / newsletter (#visit): big red panel "HUNGRY YET?" + email form + address line.
9. Footer: logo, hours, socials text links, small print.
Cart count in nav: "🛒 Order (0)".
Now the SVG taco (hero). Let me define coordinates concretely, viewBox="0 0 320 300":
Plate: ellipse cx=160 cy=262 rx=130 ry=22 fill cream darker (#F2D8A7) + inner ellipse.
Shell (drawn after fillings):
path d="M28 240 A132 132 0 0 1 292 240 Z" fill url(#shellGrad). shellGrad: #F6B24B → #E8873A. Toast spots: ellipses fill #C96A24 opacity .5 at various points on shell. Sesame seeds: small rotated ellipses #FFE9B0.
Shell edge highlight: path along arc stroke #F9CE8E stroke-width 6 fill none: d="M28 240 A132 132 0 0 1 292 240".
Fillings (drawn before shell, peeking above arc top y≈108 (center 160,240 r132 → top y=108)):
- Lettuce: path zigzag across x 60..260 around y 90..130, fill #7CB342: e.g., M55 130 L75 95 L95 125 L115 90 L135 122 L155 88 L175 120 L195 92 L215 124 L235 96 L265 128 L265 150 L55 150 Z — wavy top, bottom flat at 150 (hidden by shell below 108.. well shell covers below arc; at x=55 arc y = 240 - sqrt(132²-(160-55)²)= 240 - sqrt(17424-11025)=240-sqrt(6399)=240-79.99=160. Hmm the arc at x=55 is y≈160, so fillings need to extend down to ~170 to be covered. Let me set filling bottoms at y=180 to be safe; shell covers everything below the arc. Actually shell path covers region inside semicircle: points where (x-160)²+(y-240)² ≤132² and y≤240. At x=55, boundary y from 240 up to 160. So shell covers y≥160 at x=55. Fillings bottom at 180 fine.
Let me recompute arc top: y_top = 240-132 = 108 at x=160. Fillings should poke above ~108 at center, less at edges. So draw filling shapes with tops varying: center high (~70), edges lower (~120). Wavy path:
Lettuce (back layer, tallest): fill #8BC34A:
d="M48 170 L60 120 L82 148 L104 105 L126 140 L148 98 L170 138 L192 100 L214 142 L236 108 L258 146 L272 122 L272 190 L48 190 Z"
Kimchi (red, mid): fill #E23A22 with darker bits:
d="M60 175 L78 140 L100 165 L122 132 L144 162 L166 128 L188 160 L210 134 L232 164 L254 140 L262 170 L262 195 L60 195 Z"
Add small darker red circles on top.
Bulgogi (brown strips): a few rounded rects rotated, fill #7A4A21: e.g., <rect x="90" y="120" width="60" height="16" rx="8" transform="rotate(-8 120 128)"/> etc. Maybe 3 strips.
Cilantro leaves: small green ellipses.
Cheese drip: yellow path along arc? skip in hero.
Sauce drizzle: white zigzag path stroke #FFF6E8 stroke-width 5: d="M70 150 q10 -14 20 0 t20 0 t20 0 ..." maybe skip; keep 3-4 elements.
Steam: group of 3 paths above (y 20..80), stroke #FFF opacity animated:
path d="M120 78 C112 62 128 54 120 38 C112 22 126 16 122 4" — class steam s1; similar at x=160, x=200 with animation-delay. CSS: .steam path { animation: steam 2.6s ease-in-out infinite; opacity:0 } keyframes steam { 0%{opacity:0; transform:translateY(8px)} 30%{opacity:.8} 100%{opacity:0; transform:translateY(-14px)} } transform-box: fill-box needed for SVG transforms in CSS — set transform-origin center; translate works fine without origin. Use transform: translateY on paths — works in modern Chrome with CSS transforms on SVG elements. OK.
Floating badges around hero taco: absolutely positioned divs: circular sticker "HOT! 🌶️" rotating slowly (animation spin 12s), and "FERMENTED 48H" pill. Position over the SVG container.
Builder taco SVG: viewBox 0 0 320 300, similar shell but smaller dome maybe same. Layers groups with class "layer" and data-price. Shell same. Layers (drawn above shell? No—fillings inside should be above shell's top edge, but for builder it's clearer if layers are drawn ON TOP of the shell's upper area, like visible filling in an open taco). Alternative: draw shell, then draw fillings clipped to inside of shell (clipPath = shell path). That looks like filling inside the taco — better for builder. Use <clipPath id="shellClip"><path d="...shell..."/></clipPath>, group clipped, layers are shapes across the upper half.
Layers (each a <g class="layer" id="ly-protein"> etc.):
- Shell drawn first.
- clipped group with layers stacked: bottom lettuce (green wavy band), then protein (brown blobs), kimchi (red blobs), cheese (yellow drips), jalapeño (rings), cilantro (leaves), sauce (white drizzle).
Each layer: opacity 0, transform scale(.6) origin center; .on → opacity 1 scale(1) with a pop transition (cubic-bezier bounce). Use transform-box: fill-box; transform-origin: center; in CSS for SVG groups.
Define shapes across x 40..280, y 110..200 (inside dome: at y=140, allowed |x-160| ≤ sqrt(132²-100²)= sqrt(17424-10000)=86 → x 74..246. Keep shapes within x 70..250, y 120..210 to stay inside. Bottom of shell interior at center y up to 240; fine.)
Lettuce layer: wavy band y 150..205: path M60 205 L75 160 L95 190 L115 155 L135 188 L155 152 L175 186 L195 154 L215 188 L235 158 L255 190 L255 210 L60 210 Z fill #7CB342. Hmm bottom flat 210-215, at x=60 boundary y at x=60: sqrt(132²-100²)=86 → x min 74 at y=140... at y=205: sqrt(17424 - (240-205)²)= sqrt(17424-1225)=127 → x from 33. OK x=60 at y=205 inside. Fine, clip handles it anyway.
Protein (bulgogi): 3 brown rounded blobs: ellipse cx=120 cy=185 rx=34 ry=14 fill #8A5427; ellipse cx=185 cy=180 rx=36 ry=15 fill #74431F; ellipse cx=150 cy=165 rx=30 ry=12 fill #96602E. With char lines: small strokes.
Kimchi: red blobs: ellipse cx=110 cy=160 rx=22 ry=12 fill #E23A22; cx=200 cy=155 rx=24 ry=13; cx=155 cy=150 rx=20 ry=11; darker specks circles fill #B01E0E.
Cheese: yellow drips along top: path M80 150 q6 26 12 0 q6 30 12 0 ... simpler: several teardrop paths fill #FFC93C: e.g., path d="M95 145 c0 18 14 18 14 0 z" hmm. Use circles + drips: ellipse cx=130 cy=148 rx=26 ry=9 fill #FFC93C; plus drips: path d="M110 150 q4 22 10 0 Z" etc. Keep simple: one wavy band near top of filling: path M85 150 L95 138 L105 150 L115 136 ... zigzag band fill #FFC93C from x85..235 at y~140-155.
Jalapeño: 3 rings: circle r=8 stroke #3E8E22 stroke-width 4 fill none at (120,175),(170,168),(210,178).
Cilantro: small green leaf ellipses fill #4E9B2E scattered: (100,168),(145,158),(190,172),(225,160) rx7 ry4 rotated.
Sauce (aioli drizzle): white zigzag: path d="M85 170 q8 -12 16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0" stroke #FFF6E8 stroke-width 5 fill none stroke-linecap round. x from 85 to 85+16*8=213... add more t's to reach ~245.
Order in clipped group (paint order): lettuce, protein, kimchi, cheese, jalapeño, cilantro, sauce.
Ingredients list (right side): buttons/chips with name, price, emoji:
- Bulgogi beef +$1.75 🥩
- Kimchi +$1.00 🌶️ (well kimchi emoji none; use 🥬)
- Queso +$1.00 🧀
- Jalapeño +$0.50 🌶️
- Cilantro +$0.25 🌿
- Gochujang aioli +$0.50 🍶? use 🥄.
Base taco $4.50 (shell + lettuce included? Let's say base includes shell & lettuce; lettuce layer always on). Price = 4.50 + sum. Display "$X.XX" animated.
Each chip: <button class="chip" data-layer="protein" data-price="1.75" data-name="Bulgogi beef">... with .active state (filled red). Click toggles layer + updates price + maybe little bounce on price.
"Surprise me" button: random subset (ensure at least 2 items) — fun.
"Add to order" → cartCount++, nav badge updates with pop animation, button text flashes "Added! ✓".
Schedule data:
const SCHED = [
{d:'Mon', tag:'TODAY?'...} — compute today index = (new Date().getDay()+6)%7 (Mon=0).
Mon: "Arts District — 5th & Mateo", "5PM – 11PM", "First-timer? Try The Loco."
Tue: "Silver Lake Sunset Blvd", "5PM – 10PM", "Taco Tuesday: 3 for $10."
Wed: "Grand Central Market", "11AM – 9PM", "Lunch rush friendly."
Thu: "DTLA Grand Ave", "5PM – 12AM", "Late night kimchi."
Fri: "Smorgasburg LA", "6PM – 1AM", "Biggest crowd of the week."
Sat: "Abbot Kinney, Venice", "12PM – 11PM", "Beach day = taco day."
Sun: "Melrose Trading Post", "12PM – 8PM", "Chill vibes, hot kimchi."
]
Render day pills; active gets red fill + "TODAY" mini badge if index===todayIdx. Detail card updates with fade animation (re-trigger by removing/adding class). Auto-rotate every 4s; on manual click, reset interval.
Reviews: array of quotes; marquee row of cards duplicated; CSS animation translateX -50%.
Newsletter form: on submit, if email includes '@', show success msg, clear input; else shake + message "Enter a real email 🌶️". No alerts.
Counters: IntersectionObserver adds .inview → animate numbers via rAF from 0 to target over 1.2s. Targets: 38000 tacos (format with comma), 4.9 rating (decimal), 7 days, 48h ferment. Implement generic data-count attr, data-decimals.
Nav smooth scrolling: html { scroll-behavior: smooth; } plus scroll-padding-top for sticky nav.
Accessibility-ish: buttons are buttons, aria-pressed maybe.
Responsive:
- Hero: grid 2 cols → 1 col under 900px; taco max-width 420px centered.
- Menu grid: repeat(auto-fit, minmax(240px,1fr)).
- Builder: grid 2 col → 1 col under 860px.
- Schedule pills wrap.
- Font sizes with clamp.
- body overflow-x: hidden; also html.
Colors: hero bg red (#E8391D) with halftone; sections alternate cream (#FFF3E0) and ink (#1A1210) and yellow (#FFC93C) accents. Text ink on cream. Nav cream with ink text? Make nav ink background with cream text and red CTA — bold. Ticker: yellow bg ink text.
Let me write the marquee CSS:
.ticker { overflow:hidden; } .ticker-track { display:inline-flex; white-space:nowrap; animation: tick 22s linear infinite; } content duplicated twice; keyframes tick { to { transform: translateX(-50%); } } — requires track width = 2x content; with inline-flex and two identical spans each 50%. Use two spans with same content, track width max-content. translateX(-50%) moves exactly one copy. Good.
Reviews marquee similar but slower, pause on hover: .rev-track:hover { animation-play-state: paused; }
Menu card structure:
<article class="card">
<div class="card-art"><span class="emoji">🌮</span><span class="heat">🌶️🌶️🌶️</span></div>
<h3>The Loco</h3>
<p>Bulgogi beef, house kimchi, gochujang aioli, sesame.</p>
<div class="card-foot"><span class="price">$5.50</span><span class="tag">BEST SELLER</span></div>
</article>
Items:
1. The Loco — bulgogi, kimchi, gochujang aioli, crispy garlic — $5.50 — 🌶️🌶️🌶️ — BEST SELLER — emoji 🥩
2. Kimchi Queso — crispy pork belly, kimchi, queso fresco, lime crema — $6.00 — 🌶️🌶️ — emoji 🧀
3. Seoul Fire Chicken — double-fried chicken, gochujang glaze, pickled daikon — $5.75 — 🌶️🌶️🌶️🌶️ — emoji 🍗
Sides row: Kimchi Fries $5 🌶️🌶️ 🍟; Elote Cup $4.50 🌶️ 🌽; Horchata $3.50 🥤.
Maybe 3 taco cards + a wide "Sides" strip of 3 small chips.
Section headings style: huge uppercase with outlined word: e.g., "THE <span class="outline">LINEUP</span>". Outline: color transparent; -webkit-text-stroke: 2px var(--ink). On red bg use cream stroke.
Squiggle underline: inline SVG path under heading.
Hero headline:
Line1: "KIMCHI" solid cream
Line2: "LOCO" outline cream stroke + maybe rotated slightly, with a star.
Sub: "Korean BBQ meets street tacos — fermented, fiery, and gone by close."
Buttons: "SEE THE MENU" (yellow bg), "FIND THE TRUCK" (outline cream).
Stats row small: "★ 4.9 (2,300+ reviews)" "3 tacos = $12" "Open 7 days".
Also add a rotating circular text badge? Circular text via SVG textPath: "KIMCHI LOCO • SEOUL TO STREET • " rotating. Nice touch — SVG with path circle and textPath, animation rotate. Place at hero corner overlapping taco. Let me include: <svg class="spin-badge" viewBox="0 0 120 120"><defs><path id="circ" d="M60,60 m-44,0 a44,44 0 1,1 88,0 a44,44 0 1,1 -88,0"/></defs><text><textPath href="#circ">KIMCHI LOCO ★ SEOUL × STREET ★ FRESH DAILY ★ </textPath></text><text x="60" y="66" class="center">🌮</text></svg> with CSS animation spin 14s linear infinite on the svg. font-size ~11, letter-spacing 2. fill cream. Position absolute top-right of hero art.
Now nav:
<header class="nav"> logo: "🌮 KIMCHI LOCO" (logo mark: red square with 🌮? Use styled span). Links: Menu, Build, Truck, Reviews. CTA: <a class="cart" id="cartBtn">🛒 <span id="cartCount">0</span></a> — click scrolls to builder? Make it a button that when clicked shows a mini toast "Checkout at the truck window 😉 — n items ready". Toast: fixed bottom, appears 2.5s. Nice interactive feedback without alert.
Toast element: <div id="toast"></div> with .show class.
Footer: three columns: brand blurb, hours ("Wed–Sun... actually 7 days"), find us ("Follow @kimchiloco"), small print "© 2025 Kimchi Loco Food Truck. Made with gochujang in LA."
Also add "press" — not needed.
JS structure:
- const $ = s => document.querySelector(s);
- Builder logic
- Schedule render + rotation
- Reviews marquee: build from array into track (duplicate innerHTML)
- Counters observer
- Newsletter submit
- Cart + toast
- Ticker content static in HTML duplicated.
Edge: prefers-reduced-motion: reduce animations — add media query to disable big animations. Good practice, cheap.
Now, careful with SVG in HTML: fine inline.
Let me write shell path reused: I'll define the hero taco and builder taco separately (builder needs clipPath).
Hero taco SVG detailed:
<svg class="taco" viewBox="0 0 320 300" role="img" aria-label="Kimchi Loco taco">
<defs>
<linearGradient id="shellG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#F7B955"/><stop offset="1" stop-color="#E07F2E"/>
</linearGradient>
</defs>
<!-- steam -->
<g class="steam" stroke="#FFF6E8" stroke-width="7" fill="none" stroke-linecap="round">
<path class="s1" d="M118 92 C110 76 126 68 118 52 C110 36 124 28 120 12"/>
<path class="s2" d="M162 88 C154 72 170 64 162 48 C154 32 168 24 164 8"/>
<path class="s3" d="M206 92 C198 76 214 68 206 52 C198 36 212 28 208 12"/>
</g>
<!-- plate -->
<ellipse cx="160" cy="262" rx="128" ry="20" fill="#F3D9A4"/>
<ellipse cx="160" cy="258" rx="112" ry="15" fill="#FBE7C0"/>
<!-- fillings behind shell -->
<g>
lettuce path, kimchi path, beef ellipses, cilantro
</g>
<!-- shell -->
<path d="M28 240 A132 132 0 0 1 292 240 Z" fill="url(#shellG)"/>
<path d="M28 240 A132 132 0 0 1 292 240" fill="none" stroke="#FBD38D" stroke-width="7" stroke-linecap="round"/>
<!-- toast spots -->
<ellipse cx="90" cy="205" rx="14" ry="8" fill="#C96A24" opacity=".55" transform="rotate(-18 90 205)"/>
<ellipse cx="160" cy="185" rx="16" ry="9" fill="#C96A24" opacity=".5"/>
<ellipse cx="232" cy="208" rx="13" ry="7" fill="#C96A24" opacity=".55" transform="rotate(15 232 208)"/>
<ellipse cx="125" cy="228" rx="10" ry="6" fill="#C96A24" opacity=".45"/>
<ellipse cx="200" cy="230" rx="11" ry="6" fill="#C96A24" opacity=".45"/>
<!-- sesame -->
<g fill="#FFE9B0">
<ellipse cx="70" cy="215" rx="4" ry="2.4" transform="rotate(-24 70 215)"/>
<ellipse cx="110" cy="192" rx="4" ry="2.4" transform="rotate(12 110 192)"/>
<ellipse cx="150" cy="215" rx="4" ry="2.4" transform="rotate(-8 150 215)"/>
<ellipse cx="190" cy="195" rx="4" ry="2.4" transform="rotate(20 190 195)"/>
<ellipse cx="245" cy="222" rx="4" ry="2.4" transform="rotate(-14 245 222)"/>
<ellipse cx="255" cy="200" rx="3.6" ry="2.2" transform="rotate(30 255 200)"/>
</g>
</svg>
Fillings group (before shell):
lettuce: <path d="M46 178 L58 122 L80 150 L102 106 L124 142 L146 98 L168 138 L190 100 L212 142 L234 108 L256 148 L274 124 L274 200 L46 200 Z" fill="#8BC34A"/>
add darker green strokes? add a second path offset fill #6FA32F slightly lower for depth: maybe skip.
kimchi: <path d="M58 182 L76 142 L98 168 L120 134 L142 164 L164 130 L186 162 L208 136 L230 166 L252 142 L264 172 L264 205 L58 205 Z" fill="#E23A22"/>
kimchi specks: circles fill #A81B0C: (95,160,r4),(150,150,r4.5),(205,158,r4),(235,170,r3.5)
beef: <g fill="#7A4A21"><ellipse cx="120" cy="176" rx="30" ry="13" transform="rotate(-6 120 176)"/><ellipse cx="185" cy="172" rx="32" ry="14" transform="rotate(5 185 172)"/><ellipse cx="152" cy="160" rx="26" ry="11" fill="#8A5427"/></g> plus char lines stroke #5C3416: short lines.
cilantro: small ellipses #3E7C1F at (100,150),(170,145),(220,152),(140,140) rx 6 ry 3.5 rotated.
These get covered by shell below the arc — arc at x=46: y = 240 - sqrt(132² - 114²) = 240 - sqrt(17424-12996)= 240 - sqrt(4428)=240-66.5=173.5. So at x=46 shell starts at y≈173.5; lettuce bottom 200 covered. Good. At center x=160 arc top y=108; fillings tops at ~98-106 visible slightly above. Lettuce top at center 98 < 108 visible.
Steam paths start y=92 down to 12 — above taco, visible since nothing above. Good.
Builder taco SVG:
<svg class="taco build-taco" viewBox="0 0 320 300">
<defs>
<linearGradient id="shellG2" ...same>
<clipPath id="shellClip"><path d="M28 240 A132 132 0 0 1 292 240 Z"/></clipPath>
</defs>
plate ellipses (same)
<path shell fill url(#shellG2)/>
<path arc stroke highlight/>
<g clip-path="url(#shellClip)">
<g class="layer on" id="ly-lettuce"> lettuce band path fill #7CB342: M40 240 L55 190 L75 218 L95 184 L115 214 L135 182 L155 212 L175 184 L195 214 L215 186 L235 216 L255 190 L280 220 L280 260 L40 260 Z </g>
<g class="layer" id="ly-protein"> ellipses brown as above but positioned y 150-200 within clip: (120,190,34,14),(185,186,36,15),(152,172,30,12) + char lines </g>
<g class="layer" id="ly-kimchi"> red ellipses (110,168,22,12),(200,162,24,13),(155,155,20,11) + specks </g>
<g class="layer" id="ly-cheese"> zigzag band: path d="M70 160 L82 146 L94 160 L106 144 L118 158 L130 145 L142 159 L154 146 L166 160 L178 147 L190 161 L202 148 L214 162 L226 150 L238 164 L250 152 L250 172 L70 172 Z" fill #FFC93C </g>
<g class="layer" id="ly-jalapeno"> circles stroke #3E8E22 sw4 fill #B9E48A? rings: fill none stroke; at (115,185),(168,178),(215,188) r 8 </g>
<g class="layer" id="ly-cilantro"> ellipses #3E7C1F (100,175),(145,165),(190,180),(228,168) rx7 ry4 rotate </g>
<g class="layer" id="ly-sauce"> path drizzle stroke #FFF6E8 sw5: d="M70 200 q8 -12 16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0 t16 0" — from x70, 11 segments *16 = 176 → ends 246. y around 200. </g>
</g>
toast spots + sesame on shell (after clip group so visible on shell)
</svg>
Wait — layers inside clip will be clipped to shell interior, which is what we want (filling inside taco). The lettuce always on. Sauce at y~200 might be below much of shell interior but inside clip still visible (interior extends to y=240 at center). OK.
Layer CSS:
.layer { opacity: 0; transform: scale(.5); transform-box: fill-box; transform-origin: center; transition: transform .35s cubic-bezier(.34,1.56,.64,1), opacity .25s; }
.layer.on { opacity: 1; transform: scale(1); }
Chips:
<button class="chip" data-layer="protein" data-price="1.75"><span class="chip-emoji">🥩</span> Bulgogi beef <b>+$1.75</b></button>
etc. .chip.active style red bg cream text.
Price element: <div class="price-big" id="buildPrice">$4.50</div> pop animation on change: re-add class 'pop' (void offsetWidth trick).
Ingredients config:
protein 🥩 Bulgogi beef 1.75 layer ly-protein
kimchi 🥬 House kimchi 1.00 ly-kimchi
cheese 🧀 Queso 1.00 ly-cheese
jalapeno 🌶️ Jalapeño 0.50 ly-jalapeno
cilantro 🌿 Cilantro 0.25 ly-cilantro
sauce 🥄 Gochujang aioli 0.50 ly-sauce
Base 4.50.
Surprise: clear all, pick random 3-5 of 6, apply.
Add to order: cart++, update #cartCount text, toast "Added to your order — see you at the truck! 🌮 (n in bag)".
Schedule markup:
<div class="day-pills" id="dayPills"></div>
<div class="sched-card" id="schedCard">
<div class="sched-day" id="scDay">MON</div>
<div class="sched-info">
<h3 id="scLoc"></h3>
<p id="scTime"></p>
<p class="sched-note" id="scNote"></p>
</div>
<div class="sched-badge" id="scBadge">TODAY</div> (hidden unless today)
</div>
Fade: .sched-card.switching { animation: fadeUp .4s } re-trigger.
JS builds pills: for i 0..6 create button with day letter; click → setDay(i, true). setInterval 4000 → setDay((cur+1)%7). On manual, clearInterval & restart.
Reviews data:
["Best taco I've had in LA. The kimchi on the bulgogi? Criminal. — Dana R.", ...]
Render cards: <div class="rev-card"><p>"..."</p><span>— name</span><span class="stars">★★★★★</span></div>
Track duplicated.
Counters section (#story):
<div class="stats">
<div class="stat"><span class="num" data-count="38000">0</span><span class="lbl">tacos served</span></div>
<div class="stat"><span class="num" data-count="4.9" data-dec="1">0</span><span class="lbl">avg rating ★</span></div>
<div class="stat"><span class="num" data-count="48">0</span><span class="lbl">hr fermented kimchi</span></div>
<div class="stat"><span class="num" data-count="7">0</span><span class="lbl">days a week on the road</span></div>
</div>
Story text: "We ferment our kimchi for 48 hours in the truck's tiny fridge..." etc.
CTA section: big panel: "HUNGRY YET?" + form email + button "Text me the location" (fun: "Get the truck's location texted to you" — but we only have email input; do "Drop your email — we'll send tonight's spot."). On submit show inline msg.
Also add address line: "Usually somewhere in LA — check the tracker above ☝️".
Footer.
Toast CSS: position fixed; bottom 24px; left 50%; translateX(-50%); bg ink; color cream; padding; border-radius; opacity 0; pointer-events none; transition; .show { opacity 1; translateY }. JS timeout.
Now colors & style details:
:root {
--red:#E8391D; --red-dark:#C22A12; --ink:#201310; --cream:#FFF4E3; --paper:#FFF9EF; --yellow:#FFC93C; --green:#5FA832; --orange:#F08A2E;
}
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; background: var(--paper); color: var(--ink); overflow-x:hidden; }
h display font: .display { font-family: 'Arial Black', 'Helvetica Neue', Arial, sans-serif; font-weight:900; letter-spacing:-0.02em; text-transform:uppercase; line-height:.9; }
Hero: background: var(--red) + halftone dots (radial-gradient rgba(0,0,0,.14) 1.6px, transparent 1.7px; size 20px). Color cream. Padding top for nav.
Nav: position sticky top 0; background: var(--ink); color cream; display flex; padding 14px 24px; align center; gap; z-index 50; border-bottom 4px solid var(--yellow)? maybe red. Logo: font display, .logo-mark { background: var(--yellow); color: var(--ink); border-radius: 8px; padding: 2px 8px; transform: rotate(-3deg); display:inline-block }.
Nav links: color cream; text-decoration none; font-weight 700; uppercase; font-size 13px; letter-spacing .08em; hover underline yellow.
Cart button: background var(--red); color cream; border:none; border-radius: 999px; padding 8px 16px; font-weight 800; cursor pointer; #cartCount display inline-block; .bump animation.
Mobile nav: hide links under 720px (display none), keep logo + cart.
Ticker top: background var(--yellow); color ink; font display 14px; padding 8px 0; border-bottom 3px solid ink. Items separated by "★".
Hero layout: .hero { display:grid; grid-template-columns: 1.1fr .9fr; gap: 40px; align-items:center; max-width:1200px; margin:0 auto; padding: 64px 24px 80px; } h1 font-size clamp(56px, 9vw, 120px). .outline { color: transparent; -webkit-text-stroke: 3px var(--cream); } add paint-order? fine. Also fallback: if stroke unsupported, color cream — use @supports? -webkit-text-stroke widely supported in Chrome. Add color: var(--cream) default then @supports (-webkit-text-stroke: 1px black) { .outline { color: transparent; -webkit-text-stroke: 3px var(--cream);} }. Good.
Hero art: position relative; .taco { width: 100%; max-width: 440px; filter: drop-shadow(0 24px 24px rgba(0,0,0,.25)); animation: bob 5s ease-in-out infinite; } keyframes bob { 50% { transform: translateY(-10px) rotate(-1.5deg);} }.
Badges: .sticker { position:absolute; background: var(--yellow); color: var(--ink); border: 3px solid var(--ink); border-radius: 50%; width: 110px; height:110px; display:grid; place-items:center; font-family display; font-size: 13px; text-align:center; box-shadow: 4px 4px 0 var(--ink); } one top-left rotated -10deg "100% KIMCHI 🌶️", one bottom-right spin-badge svg.
Buttons: .btn { font-family display; text-transform uppercase; padding: 16px 26px; border-radius: 999px; border: 3px solid var(--ink); font-size: 15px; cursor:pointer; box-shadow: 5px 5px 0 var(--ink); transition: transform .15s, box-shadow .15s; text-decoration none; display:inline-block; }
.btn:hover { transform: translate(-2px,-2px); box-shadow: 7px 7px 0 var(--ink); }
.btn:active { transform: translate(3px,3px); box-shadow: 0 0 0 var(--ink); }
.btn-yellow { background: var(--yellow); color: var(--ink); }
.btn-ghost { background: transparent; color: var(--cream); border-color: var(--cream); box-shadow: 5px 5px 0 rgba(0,0,0,.35); }
Section base: .section { max-width: 1200px; margin: 0 auto; padding: 80px 24px; }
.sec-head h2 { font-size: clamp(40px, 6vw, 76px); }
Menu section bg paper; cards bg white? Use cream cards with ink border 3px, shadow 6px 6px 0 ink; border-radius 18px; hover translateY(-6px) rotate(-1deg). Card art: height 140px; background halftone red light? background: radial dots on var(--yellow) light; display grid place-items center; font-size 64px; border-bottom 3px solid ink; border-radius top. Heat: position absolute top right font-size 14px. Price sticker: background var(--red); color cream; border-radius 999px; padding 4px 12px; font-weight 900; transform rotate(-4deg).
Tag: background var(--green); color cream; small pill.
Builder section: background var(--ink); color cream; halftone light dots (rgba(255,255,255,.06)). Layout grid 1fr 1fr gap 48. Left: sticky? no. Taco preview centered with price big below: .price-big { font-family display; font-size 56px; color var(--yellow); } .pop animation scale 1.25 → 1.
Chips: display flex wrap gap 10px; .chip { background: transparent; border: 2px solid rgba(255,244,227,.4); color: cream; padding: 10px 14px; border-radius: 999px; cursor: pointer; font-weight: 700; font-size: 14px; display:flex; gap:8px; align-items:center; transition: all .15s; } .chip b { color: var(--yellow); } .chip.active { background: var(--red); border-color: var(--red); color: cream; } .chip.active b { color: var(--yellow); } .chip:hover { border-color: cream; }
Buttons row: "🎲 Surprise me" ghost-yellow, "Add to order 🛒" red.
Truck section: bg var(--yellow)? With ink halftone. Day pills: buttons pill border 3px ink; active bg ink color yellow. Sched card: bg cream border 3px ink shadow; display flex; big day letter display font 64px in red square.
Reviews: bg var(--red); color cream; marquee cards bg cream color ink width 320px flex-shrink 0 border 3px ink shadow.
Story: paper bg; grid 2 col: text + stats grid 2x2. Stats: num display font 44px red; lbl uppercase small.
CTA: section with panel bg var(--red) halftone, border 4px ink, shadow 10px 10px 0 ink, radius 24px, padding 56px, center text. h2 clamp big cream. form: input email + button yellow. input { padding 14px 18px; border-radius 999px; border 3px solid ink; font-size 15px; min-width: min(320px, 100%); } msg p.
Footer: bg ink color cream; padding 48px 24px; grid 3 cols; small.
Responsive breakpoints:
@media (max-width: 900px): hero 1 col, art order -1? Keep text first then taco; builder 1 col; story 1 col; footer 1 col gap.
@media (max-width: 720px): nav links display none; section padding 56px 18px; h1 smaller via clamp already.
Ensure no horizontal scroll: all good; marquee tracks are inside overflow hidden.
Grain overlay: skip or subtle — add body::before with data-uri turbulence at low opacity, pointer-events none, position fixed, z-index 999? Could interfere visually; use opacity .05. It's inline data URI, allowed. Let me include tiny: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='120' height='120' filter='url(%23n)' opacity='0.5'/></svg>") — need URL-encoding of quotes; use single quotes inside and encode # as %23. Keep it.
Actually to be safe with encoding, maybe skip grain. Halftone is enough. I'll include grain carefully — it's fine with utf8 and %23. I'll include it; low risk.
Now JS details:
const ingredients = document.querySelectorAll('.chip');
let base = 4.5;
function currentTotal(){ let t = base; document.querySelectorAll('.chip.active').forEach(c=> t += parseFloat(c.dataset.price)); return t; }
function updatePrice(){ const p = currentTotal(); const el = $('#buildPrice'); el.textContent = '$' + p.toFixed(2); el.classList.remove('pop'); void el.offsetWidth; el.classList.add('pop'); }
chip click: toggle active; corresponding layer id from data-layer → document.getElementById('ly-'+key).classList.toggle('on', active); updatePrice();
Surprise: chips forEach remove active & layers off; const shuffled = [...chips].sort(()=>Math.random()-.5); const n = 3 + Math.floor(Math.random()*3); // 3-5
shuffled.slice(0,n).forEach(activate); updatePrice();
Add to order: cart++; cartCount.textContent = cart; bump anim; toast(`🌮 Added! ${cart} item${cart>1?'s':''} in your bag — pay at the window.`);
toast(msg): el.textContent=msg; el.classList.add('show'); clearTimeout(tId); tId=setTimeout(()=>el.classList.remove('show'),2600);
Schedule:
const SCHED = [...7 entries {d:'MON', loc:'Arts District — 5th & Mateo', time:'5:00 PM – 11:00 PM', note:'...'}...];
const todayIdx = (new Date().getDay() + 6) % 7;
render pills: SCHED.forEach((s,i)=> btn.textContent = s.d; if i===todayIdx add span '•TODAY'? Put small dot. onclick setDay(i,true)).
setDay(i, manual): cur=i; update pills active; fill card fields; badge show if i===todayIdx; retrigger animation on card.
Auto: let timer = setInterval(next, 4000); manual click: clearInterval, restart.
Card fields ids: scDay, scLoc, scTime, scNote, scToday.
Reviews:
const REVS = [ {q, n} x6 ]; build track: track.innerHTML = cards + cards (two copies). Each card: `<div class="rev-card"><div class="stars">★★★★★</div><p>“${q}”</p><span>— ${n}</span></div>`. Animation translateX(-50%) 30s linear infinite; track display:flex; gap:20px; width:max-content; padding... For seamless loop with gap, need the two halves identical including trailing gap — trick: put gap via margin-right on cards instead of flex gap, so -50% aligns. Use .rev-card { margin-right: 20px; } then track width = 2 * half exactly. Good.
Counters:
const nums = document.querySelectorAll('.num');
const io = new IntersectionObserver(entries => { entries.forEach(e => { if(e.isIntersecting){ animate(e.target); io.unobserve(e.target);} }); }, {threshold:.4});
nums.forEach(n=>io.observe(n));
animate(el): target=parseFloat(el.dataset.count); dec=parseInt(el.dataset.dec||0); dur=1400; start=performance.now(); function step(t){ p=Math.min(1,(t-start)/dur); ease = 1-Math.pow(1-p,3); val = target*ease; el.textContent = dec? val.toFixed(dec) : Math.round(val).toLocaleString('en-US'); if(p<1) requestAnimationFrame(step);} requestAnimationFrame(step);
Newsletter:
form.addEventListener('submit', e=>{ e.preventDefault(); const v=input.value.trim(); if(/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v)){ msg.textContent = "🔥 You're in! Tonight's spot lands in your inbox."; msg.className='form-msg ok'; input.value=''; } else { msg.textContent="Hmm, that email looks off — try again 🌶️"; msg.className='form-msg err'; form.classList.add('shake'); setTimeout(()=>form.classList.remove('shake'),400); } });
shake keyframes translateX.
Cart button click → toast showing bag count: "🛒 Your bag: n item(s) — pay at the truck window!"
Also nav link click default anchor smooth scroll works.
Hero CTA anchors href="#menu" "#truck".
Now write all HTML. Also ticker content: "KIMCHI LOCO ★ KOREAN BBQ × STREET TACOS ★ FERMENTED 48 HOURS ★ 3 TACOS $12 ★ FIND THE TRUCK ★" repeated.
Second ticker before truck section? Maybe one between menu and builder with different text on red bg cream text. Sure: "HOT & FRESH ★ NO FREEZER ★ ..." Keep one top ticker + reviews marquee; enough motion. Add a divider squiggle SVG between sections maybe skip.
Section heading style with small eyebrow label: .eyebrow { display:inline-block; background: var(--ink); color: var(--yellow); padding: 6px 14px; border-radius: 999px; font-weight: 800; font-size: 12px; letter-spacing: .12em; text-transform: uppercase; margin-bottom: 14px; }
Headings: "THE LINEUP" (menu), "BUILD YOUR LOCO" (builder), "FIND THE TRUCK" (truck), "WORD ON THE STREET" (reviews), story heading "FERMENTED. FIRED. SERVED." CTA "HUNGRY YET?"
Menu grid: .menu-grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 22px; }
Sides strip: .sides { display:flex; flex-wrap:wrap; gap:14px; margin-top: 26px; } .side { border:3px solid ink; border-radius: 999px; padding: 10px 18px; background: #fff; font-weight:700; box-shadow: 4px 4px 0 ink; } with price.
Card details: name display font 22px; desc 14px; foot flex space-between.
Emoji in art: 🥩🧀🍗; sides: 🍟🌽🥤.
Heat display: 🌶️ repeated.
Let me also add hover wiggle on card art emoji: .card:hover .emoji { animation: wiggle .4s; }.
Now assemble final HTML. Watch out: inside <script>, avoid "</" sequences issues — fine.
Also ensure the page at 1280x800: hero fills nicely; nav sticky. Screenshot at 3s: hero + ticker visible, animations running (steam, bob, spin badge, ticker). Good.
Font: to get a chunkier display look without external fonts, 'Arial Black' is fine on most systems; fallback Impact. Use font-family: Impact, 'Arial Black', 'Helvetica Neue', sans-serif? Impact is condensed and very poster-like — good for headlines. But Impact has no bold weights; fine, it's already heavy. I'll use: --font-display: 'Arial Black', 'Archivo Black', Impact, sans-serif; Actually put Impact first? Impact looks great for street posters. But letterforms of Impact at huge sizes look good. I'll use 'Arial Black' first (wider, modern), Impact fallback.
Hmm, on Linux sandbox Chrome, Arial Black may not exist; fallback to sans-serif bold — set font-weight 900 so DejaVu Sans Bold renders heavy. OK.
Let me write the code now, being careful with all IDs and classes matching.
Also remember: no horizontal scrollbar — check marquee tracks: they're inside .ticker {overflow:hidden}, fine. spin badge absolute within .hero-art {position:relative} — ensure it doesn't overflow viewport on mobile: keep within bounds (right: 0). body overflow-x hidden anyway as belt & suspenders (but requirement says no horizontal scrollbar — overflow-x:hidden on html,body ensures).
Sticky nav + scroll-padding-top: html { scroll-padding-top: 80px; }.
Write out schedule card layout: .sched-card { display:flex; align-items:center; gap: 24px; background: var(--cream); border: 3px solid var(--ink); border-radius: 20px; box-shadow: 8px 8px 0 var(--ink); padding: 24px 28px; margin-top: 22px; position:relative; } .sched-day { font display; font-size: 40px; background: var(--red); color: cream; width: 92px; height: 92px; border-radius: 16px; display:grid; place-items:center; border: 3px solid ink; flex-shrink:0 } h3 font display 26px; time bold; note italic? Add pin: "📍".
TODAY badge: position absolute top -14px right 24px; background: var(--green); color: #fff; padding 4px 12px; border-radius 999px; border 3px solid ink; font-weight 900; font-size 12px; display none → .show display block. Or always in DOM toggled.
Mobile: sched-card flex-wrap.
Pills: .day-pills { display:flex; flex-wrap:wrap; gap:10px; } .pill { font-family display; font-size:14px; padding: 10px 16px; border-radius: 999px; border: 3px solid var(--ink); background: var(--cream); cursor:pointer; box-shadow: 3px 3px 0 var(--ink); transition: .15s; } .pill.active { background: var(--ink); color: var(--yellow); } .pill.today::after { content: ' ●'; color: var(--red); } hmm if active bg ink, red dot ok.
Truck section bg: var(--yellow) with halftone ink dots (rgba(32,19,16,.12)).
Builder bg ink with dots rgba(255,244,227,.07).
Reviews bg red with dots rgba(0,0,0,.15)? cream cards pop.
OK — also the "menu" section maybe bg paper with subtle dots none.
Hero bottom: add a wave divider? Could add an SVG wave at hero bottom into paper. Nice: <svg class="wave" viewBox="0 0 1440 60" preserveAspectRatio="none"><path d="M0 60 L0 30 Q 120 0 240 30 T 480 30 T 720 30 T 960 30 T 1200 30 T 1440 30 L1440 60 Z" fill="var(--paper)"/></svg> positioned at bottom of hero (hero position relative, wave absolute bottom -1px width 100%). CSS var in SVG fill works since inline. Nice touch.
Now, code writing. Also add small "scroll hint"? skip.
One more interactive: clicking hero taco makes it jump? Add click → class 'boing' animation. Fun. taco.addEventListener('click', ...) with keyframe bounce scale. Add cursor pointer + title. Sure, cheap.
Let me count the layout heights so first viewport (1280×800) shows hero nicely: ticker ~36px + nav ~64px + hero padding 56 top → h1 ~2 lines at ~110px each = 220 + sub + buttons + stats ≈ 500-560px total — fits in 800. Good.
Write the final file. Also include <meta viewport>. Title "Kimchi Loco — Korean Tacos Food Truck".
Let me write reviews data (6):
1. "The bulgogi + kimchi combo is criminal. I've been back three times this week." — Dana R., Silver Lake
2. "I don't even like spicy food and Seoul Fire converted me." — Marcus T., DTLA
3. "Found them at Smorgasburg at midnight. Best decision of my life." — Jess & Priya
4. "Kimchi fries >>> regular fries. Fight me." — @taco.tues on IG
5. "The truck smells like a Seoul street stall in the best way." — Kevin O., Venice
6. "3 tacos for $12 and I still had room. Unreal." — Alicia M., Echo Park
Story text: "Kimchi Loco started with one grill, one grandma's kimchi recipe, and a dream. We ferment our kimchi for 48 hours, sear bulgogi to order, and stuff it all into warm corn tortillas. No freezer. No shortcuts. Just loud flavors and a line worth waiting in."
Stats: 38,000+ tacos served / 4.9 avg rating / 48 hr kimchi ferment / 7 days a week.
CTA: "HUNGRY YET?" + "Drop your email and we'll send you tonight's truck location every morning." input + button "SIGN ME UP". msg line. Also small: "Or just follow the smell of gochujang. 📍"
Footer columns: Brand (logo + blurb), "Hours" (Wed–Sun? we do 7 days: "Mon–Sun — times vary by location, see tracker above"), "Find us" (@kimchiloco everywhere, hello@kimchiloco.com). Bottom: "© 2025 Kimchi Loco Food Truck Co. — Made with 🌶️ in Los Angeles."
Now writing everything. Be careful: in JS template literals inside HTML script — fine. Avoid `</script>` inside strings.
Also the ticker duplicated content: write a span twice with same innerHTML; I'll just write the text twice manually in HTML.
Ticker item separator: use " ★ ". Content: "KIMCHI LOCO 🌮 KOREAN BBQ × STREET TACOS ★ FERMENTED 48 HOURS ★ 3 TACOS = $12 ★ FIND THE TRUCK TONIGHT ★ HOT & FRESH ★ " — emoji in ticker fine.
Let me now also handle .chip default: none active except lettuce always on (not a chip). Good.
Edge: builder price pop CSS: .price-big.pop { animation: pop .3s ease; } @keyframes pop { 0%{transform:scale(1.3);} 100%{transform:scale(1);} } — but re-adding class after remove + reflow works.
Cart bump: #cartCount.bump { animation: bump .35s; } keyframes bump { 40% { transform: scale(1.5); } } display inline-block needed.
Toast: #toast { position: fixed; left:50%; bottom:26px; transform: translate(-50%, 20px); background: var(--ink); color: var(--cream); border: 3px solid var(--yellow); padding: 12px 20px; border-radius: 14px; font-weight: 700; opacity: 0; pointer-events:none; transition: all .3s; z-index: 100; max-width: min(90vw, 480px); text-align:center; } #toast.show { opacity:1; transform: translate(-50%, 0); }
Boing keyframe for taco click: .taco.boing { animation: boing .5s ease; } keyframes boing { 0%{transform:scale(1)} 30%{transform:scale(1.08,.92)} 55%{transform:scale(.94,1.06)} 75%{transform:scale(1.03,.98)} 100%{transform:scale(1)} } — but hero taco already has bob animation; conflicting animation property. Put boing on inner wrapper? Wrap hero taco in .taco-wrap with bob, and apply boing to svg itself. Actually bob on svg, boing would override. Solution: apply bob to .hero-art .taco and boing to a wrapper div .taco-bounce. I'll wrap: <div class="taco-wrap"><svg class="taco">...</svg></div>; .taco-wrap { animation: bob ... } .taco-wrap.boing { animation: boing .5s; } — same conflict. Use two nested wrappers: outer bob, inner boing. Fine: <div class="bob"><div class="boing-target" id="heroTaco"><svg class="taco">…</svg></div></div>. Click on svg → add boing to #heroTaco, remove on animationend.
Builder taco click also fun? skip.
Steam animation: .steam path { opacity: 0; animation: steamUp 2.8s ease-in-out infinite; } .s2 { animation-delay: .9s } .s3 { animation-delay: 1.8s } keyframes steamUp { 0% { opacity:0; transform: translateY(10px);} 35% {opacity:.85;} 100% { opacity:0; transform: translateY(-16px);} }
Spin badge: .spin-badge { position:absolute; top:-10px; right:-6px; width:120px; animation: spin 16s linear infinite; } text { font-size: 12.5px; letter-spacing: 2.5px; fill: var(--cream); font-weight: 800; font-family: ... } center emoji text no animation issue (inside svg rotates too — the 🌮 center would rotate; that's fine/funny, or place emoji outside svg as separate absolutely positioned span not rotating. I'll put 🌮 as HTML span centered over the spinning svg, non-rotating.)
Structure: <div class="spin-wrap"><svg class="spin-badge">…circle text…</svg><span class="spin-center">🌮</span></div>. .spin-wrap { position:absolute; top: -8px; right: 0; width: 120px; height: 120px; } .spin-center { position:absolute; inset:0; display:grid; place-items:center; font-size: 30px; }
Sticker "100% KIMCHI": <div class="sticker">100%<br>KIMCHI<br>🌶️</div> position absolute left: -6px; top: 40%; rotate -10deg; animation wiggle slow? give it float animation.
On mobile these absolute elements: keep within art container; art max-width 440 centered; fine.
Now menu card art backgrounds: alternate colors per card via nth-child: yellow, red(light?), green light. .card:nth-child(1) .card-art { background:#FFD98A } etc. Use classes.
Let me finalize and write. I'll be meticulous about matching IDs between HTML/JS.
Also add `scroll-behavior: smooth` and section ids: menu, build, truck, reviews(optional), story, visit.
Nav links: MENU #menu, BUILD #build, TRUCK #truck, STORY #story.
Write CSS organized. Approx sizes fine.
One more check — "No external files, images, fonts or network requests" — all inline, no <img>, no url() except data URI grain (inline data, not network). Should be acceptable; data URIs aren't network requests. I'll keep grain but make sure encoding correct:
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
body::after { content:""; position: fixed; inset:0; background-image: ...; opacity:.05; pointer-events:none; z-index: 200; mix-blend-mode: multiply; } Hmm multiply on fixed overlay might darken; opacity .05 fine. z-index 200 above toast(100)? toast readability fine at .05. OK put z-index 90 below toast. Actually simpler: skip mix-blend.
Halftone helper classes:
.dots-dark { background-image: radial-gradient(rgba(32,19,16,.16) 1.5px, transparent 1.6px); background-size: 18px 18px; }
.dots-light { background-image: radial-gradient(rgba(255,244,227,.14) 1.5px, transparent 1.6px); background-size: 18px 18px; }
Apply along with bg color.
Hero: background-color red + dots-dark class.
Now write final answer. Only the code block, no extra text.
Let me draft the full HTML mentally then output.
Also ensure `button` elements have type="button" where inside form? Only newsletter has form; its button type submit. Others default type=button not required outside forms but fine to add.
Chips data:
- protein 🥩 Bulgogi beef +1.75
- kimchi 🥬 House kimchi +1.00
- cheese 🧀 Queso +1.00
- jalapeno 🌶️ Jalapeño +0.50
- cilantro 🌿 Cilantro +0.25
- sauce 🥄 Gochujang aioli +0.50
data-layer values: protein, kimchi, cheese, jalapeno, cilantro, sauce → layer ids ly-protein etc. Lettuce always on id ly-lettuce class "layer on".
Build price default $4.50.
JS chips: document.querySelectorAll('.chip').forEach(ch => ch.addEventListener('click', () => { const on = ch.classList.toggle('active'); document.getElementById('ly-' + ch.dataset.layer).classList.toggle('on', on); updatePrice(); }));
Surprise:
randomBtn.addEventListener('click', () => {
const chips = [...document.querySelectorAll('.chip')];
chips.forEach(c => { c.classList.remove('active'); document.getElementById('ly-'+c.dataset.layer).classList.remove('on'); });
const pool = chips.sort(() => Math.random() - .5);
const n = 3 + Math.floor(Math.random()*3);
pool.slice(0, n).forEach(c => { c.classList.add('active'); document.getElementById('ly-'+c.dataset.layer).classList.add('on'); });
updatePrice();
toast('🎲 The chef picked your toppings!');
});
addBtn: cart++ etc.
Schedule render:
const SCHED = [
{d:'MON', loc:'Arts District — 5th & Mateo', time:'5:00 PM – 11:00 PM', note:'First stop of the week. Come hungry.'},
{d:'TUE', loc:'Silver Lake — Sunset Blvd', time:'5:00 PM – 10:00 PM', note:'Taco Tuesday: 3 tacos for $10 all night.'},
{d:'WED', loc:'Grand Central Market', time:'11:00 AM – 9:00 PM', note:'Lunch rush friendly — under 10 min a taco.'},
{d:'THU', loc:'DTLA — Grand Ave & 2nd', time:'5:00 PM – 12:00 AM', note:'Late-night kimchi hits different.'},
{d:'FRI', loc:'Smorgasburg LA — ROW DTLA', time:'6:00 PM – 1:00 AM', note:'Biggest line of the week. Worth it.'},
{d:'SAT', loc:'Abbot Kinney, Venice', time:'12:00 PM – 11:00 PM', note:'Beach day = taco day.'},
{d:'SUN', loc:'Melrose Trading Post', time:'12:00 PM – 8:00 PM', note:'Chill vibes, hot kimchi.'}
];
pills container; forEach create button class pill (+ 'today' if idx===todayIdx) textContent s.d; onclick () => { setDay(i); restart(); }.
setDay(i): cur=i; pills children toggle active; scDay.textContent; scLoc; scTime; scNote; scToday.style.display = (i===todayIdx)?'block':'none'; card.classList.remove('swap'); void card.offsetWidth; card.classList.add('swap');
.swap { animation: fadeUp .45s ease; } keyframes fadeUp { from { opacity:0; transform: translateY(12px);} }
auto: let auto = setInterval(()=> setDay((cur+1)%7), 4000); function restart(){ clearInterval(auto); auto = setInterval(...); }
Reviews build:
const track = document.getElementById('revTrack');
const cardHTML = REVS.map(r => `<div class="rev-card"><div class="stars">★★★★★</div><p>“${r.q}”</p><span class="rev-name">— ${r.n}</span></div>`).join('');
track.innerHTML = cardHTML + cardHTML;
CSS .rev-track { display:flex; width:max-content; animation: slide 32s linear infinite; } .rev-card { width: 300px; margin-right: 20px; ... } @keyframes slide { to { transform: translateX(-50%); } } — with margin-right on each card, half width includes margins, so -50% seamless. Good. Hover pause: #reviews:hover .rev-track { animation-play-state: paused; }
Counters as planned.
Newsletter as planned; ids: nlForm, nlEmail, nlMsg.
Cart: cartBtn click → toast(`🛒 ${cart} item${cart===1?'':'s'} in your bag — pay at the window!`); if cart===0 toast 'Your bag is empty — build a loco above! ⬆️'.
Hero taco click boing.
Also smooth scroll: html{scroll-behavior:smooth}.
Check contrast & vibe. Good.
Now output. Make sure exactly one ```html block and nothing else.
Let me write the complete code carefully now.
Additional detail: -webkit-text-stroke on .outline with fallback:
.outline { color: var(--cream); }
@supports ((-webkit-text-stroke: 2px #000)) { .outline { color: transparent; -webkit-text-stroke: 3px var(--cream); } }
For dark sections outline uses ink stroke: .outline-ink similarly with var(--ink). I'll create .outline with currentColor? text-stroke can't use currentColor when color transparent... Actually -webkit-text-stroke-color can be set separately; use classes .stroke-cream / .stroke-ink with @supports.
Simpler: define .outline { -webkit-text-stroke: 3px currentColor; color: transparent; } — currentColor resolves to the *color* property which we set transparent → stroke transparent too. Bad. So use explicit: .outline-cream { color: var(--cream); } @supports (...) { .outline-cream { color: transparent; -webkit-text-stroke: 3px var(--cream);} } and .outline-ink { color: var(--ink); } @supports { color: transparent; -webkit-text-stroke: 3px var(--ink); }.
Headings on red bg use cream; on paper use ink.
Section head layout: eyebrow + h2 + optional sub.
Menu heading: eyebrow "THE GOODS", h2: THE <span class="outline-ink">LINEUP</span>. sub: "Three tacos. Zero regrets. All served on warm corn tortillas with a side of attitude."
Builder heading (on ink bg): eyebrow yellow-on-red? eyebrow style variant: .eyebrow.red { background: var(--red); color: var(--cream); } h2 cream: BUILD YOUR <span class="outline-cream">LOCO</span>. sub: "Start with a warm tortilla ($4.50), stack it your way. The truck sees everything."
Truck heading on yellow: FIND THE <span outline-ink>TRUCK</span>. sub: "We move. Here's where we'll be this week — tap a day to jump."
Reviews: h2 cream on red: WORD ON THE <span outline-cream>STREET</span>.
Story: FERMENTED. <span outline-ink>FIRED.</span> SERVED.
CTA panel: HUNGRY <span outline-cream>YET?</span>
OK writing now. Also add small marquee divider between hero and menu? The wave handles transition. Between sections maybe simple.
Time to write. I'll aim for clean, complete code. Also remember lang="en".
For the wave fill use fill="#FFF9EF" (paper) directly to avoid var issues (vars do work in inline SVG in HTML since CSS custom properties apply, but hardcode to be safe).
Hero art also include a soft radial glow behind taco: .hero-art::before { content:""; position:absolute; inset: 10% 5%; background: radial-gradient(circle, rgba(255,201,60,.5), transparent 65%); border-radius:50%; } nice.
Also stats row hero: .hero-stats { display:flex; gap: 18px; flex-wrap:wrap; margin-top: 26px; font-weight:700; } items with ★.
Let me write the final code.
Also: builder left column: taco + price + buttons? Buttons under chips on right; price near taco. Layout: left: taco svg + price-big + caption "your loco". right: h2? No—heading spans full width above grid. Structure:
<section id="build" class="section build">
<div class="sec-head center"> eyebrow, h2, sub </div>
<div class="build-grid">
<div class="build-preview"> svg + price + label </div>
<div class="build-controls"> chips + buttons </div>
</div>
</section>
.sec-head.center { text-align:center; max-width: 720px; margin: 0 auto 44px; }
Menu sec-head left aligned.
OK. Also .build-grid { display:grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items:center; }
Preview: .build-preview { text-align:center; position:relative; } .build-taco { width: min(100%, 380px); } .price-big below. Add plate shadow.
Controls: .chips { display:flex; flex-wrap:wrap; gap:10px; margin-bottom: 22px; } .build-btns { display:flex; gap: 14px; flex-wrap:wrap; }
Buttons in dark section: .btn-red { background: var(--red); color: var(--cream); border-color: var(--cream)? } Keep border ink? On ink bg, ink border invisible; use border cream for ghost, and red button border ink with shadow cream? Simplify: in dark section, .btn { border-color: var(--cream); box-shadow: 5px 5px 0 rgba(255,244,227,.25); } .btn-red { background: var(--red); color: #fff; border-color: var(--cream); } .btn-yellow2 { background: var(--yellow); color: var(--ink); border-color: var(--ink); } hmm mixed. Just give build buttons: surprise = .btn-ghost-dark (transparent, cream border), add = .btn-red (red bg, cream text, cream border). Fine.
Toast z-index above grain.
Grain: body::after z-index 60; nav z-index 50 — grain over nav slightly, fine (opacity .05). Toast z-index 120 above grain? grain pointer-events none, visual only. ok.
Now finalize schedule auto-rotate and ensure no errors: define all functions before use.
Also IntersectionObserver exists in Chrome — fine.
Potential pitfall: In HTML, SVG <textPath href> — use both xlink:href and href? Modern Chrome supports href. Use href.
Write it.
Counting the shell path: "M28 240 A132 132 0 0 1 292 240 Z" — arc from (28,240) to (292,240) with r=132: distance between points = 264 = 2r → semicircle exactly, sweep flag 1 gives upper arc? With large-arc 0 and sweep 1... For a semicircle, large-arc-flag ambiguous (both arcs are 180°). sweep=1 means clockwise: from (28,240) to (292,240) clockwise goes up over the top. Yes upper dome. Good.
ClipPath same path.
Builder layers coordinates sanity: interior at y=200: half-width = sqrt(132² - 40²) = sqrt(17424-1600)=sqrt(15824)=125.8 → x from 34 to 286. Our shapes within 70..250 fine. At y=150: sqrt(17424-8100)=sqrt(9324)=96.6 → x 63..257. Cheese band x70..250 at y146-172: at y=146, half-width sqrt(17424-8836)=sqrt(8588)=92.7 → x 67..253. Cheese at x=250 y~150 edge: |250-160|=90 < 92.7 ok; x=70: 90 ok. Fine, and clip handles overflow anyway.
Sauce path at y=200 from x=70: 16*11=176 → end 246. Inside. Good.
Jalapeño rings at (115,185),(168,178),(215,188) r8: fine.
Hero fillings: lettuce path top points y as low as 98 at x=146..190 — arc at x=146: sqrt(17424 - 196)=131.3 → boundary y=108.7. Lettuce top 98 < 108.7 → pokes above shell by ~10px. Good. At x=58: boundary sqrt(17424 - 10404)= sqrt(7020)=83.8 → y=156; lettuce at x=58 y=122 → pokes above by 34px. Fine, fillings visible at edges more — looks like a stuffed taco. Good.
Steam at x 118-208, y 8-92: above shell top 108 — visible. Good.
Now output. I'll write ~600 lines. Ensure no stray backticks inside.
One more requirement: "Games must start in self-playing demo mode..." — no game, N/A. "If the demo uses the keyboard, display message" — we don't use keyboard. OK.
Also "Something meaningful visible on first paint" — yes hero.
Write final.