• Updated 23 Oct, 19:41 UTC
  • Next run 24 Oct, 05:17 UTCNext 24 Oct 05:17
  • Last 7 days: 5 new models, 196 runs7 d: 5 models, 196 runs
Landing page for a cargo bike workshop

GLM 5.3 PrimeZ.ai

Network offOpen alone (new tab)

Run details

Model
z-ai/glm-5.3-prime
Route
OpenRouter API, one user message
Served by
Alibaba
Effort
max, OpenRouter's default for this model
Limits
60 min · 131,072 output tokens (the model's limit)
Date
23 Oct 2026
Time
15 min 37 s
Size
69 kB
Tokens
98 in, 131,072 out (108,485 of them thinking)
List price
$1.15 at today's price ($2.8 in / $8.8 out per M)
Paid
$1.15
Result
Cut off before the file was finished

Raw files: output · meta.json

Exact prompt

Build the landing page for Lumen Cycles, a Copenhagen workshop that builds custom cargo bikes to order. Make it look like a real brand you would trust with 4,000 euros.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

69 kB 1,147 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lumen Cycles — Cargo bikes made to measure, Copenhagen</title>
<meta name="description" content="Lumen Cycles builds the Lumen L front-loader cargo bike one at a time in Copenhagen. Drawn to your measure, fillet-brazed Cr-Mo, delivered in 10–14 weeks.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='5.5' fill='%23B45A17'/%3E%3Cg stroke='%23B45A17' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M16 2v4M16 26v4M2 16h4M26 16h4M6.2 6.2l2.8 2.8M23 23l2.8 2.8M25.8 6.2L23 9M9 23l-2.8 2.8'/%3E%3C/g%3E%3C/svg%3E">
<style>
/* ---------- tokens ---------- */
:root{
  --paper:#F5F1E6; --paper2:#ECE6D4; --ink:#1B1913; --ink2:#4B463A; --ink3:#7A7362;
  --hair:#DAD2BE; --hair2:#C9C0AA;
  --accent:#B45A17; --accent-deep:#8F4511; --accent-lift:#D97A2B;
  --dark:#191712; --cream:#F1EBDD; --haird:#3A352A; --dim:#A39A85; --dim2:#8E8672; --accentd:#E08A3C;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif;
  --sans:"Avenir Next","Segoe UI","Trebuchet MS","Helvetica Neue",sans-serif;
  --mono:ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,"DejaVu Sans Mono",monospace;
}
*{box-sizing:border-box;margin:0}
html{scroll-behavior:smooth}
body{background:var(--paper);color:var(--ink);font:400 17px/1.62 var(--serif);-webkit-font-smoothing:antialiased;overflow-x:hidden}
img,svg{display:block;max-width:100%}
a{color:inherit}
::selection{background:var(--accent);color:var(--paper)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
section{scroll-margin-top:70px;padding:clamp(84px,11vw,148px) 0}
.container{width:min(1160px,92vw);margin-inline:auto}
p{max-width:64ch}
.mono-s{font-family:var(--mono);font-size:11.5px;letter-spacing:.15em}
.skip{position:absolute;left:-999px;top:0;background:var(--ink);color:var(--paper);padding:10px 16px;z-index:100}
.skip:focus{left:12px;top:12px}
noscript{display:block;padding:12px;font-family:var(--mono);font-size:12px;border-bottom:1px solid var(--hair)}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;background:var(--ink);color:var(--paper);
  border:1px solid var(--ink);font-family:var(--mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  padding:15px 22px;text-decoration:none;cursor:pointer;transition:background .25s,border-color .25s,color .25s,transform .25s}
.btn:hover{background:var(--accent-deep);border-color:var(--accent-deep);transform:translateY(-1px)}
.btn:active{transform:none}
.btn .arr{transition:transform .25s;flex:none}
.btn:hover .arr{transform:translateX(4px)}
.btn.ghost{background:transparent;color:var(--ink);border-color:var(--ink3)}
.btn.ghost:hover{background:transparent;color:var(--accent-deep);border-color:var(--accent-deep)}
.btn.sm{padding:11px 16px;font-size:10.5px}
.w100{width:100%}
.dark .btn{background:var(--cream);color:var(--ink);border-color:var(--cream)}
.dark .btn:hover{background:var(--accentd);border-color:var(--accentd);color:#241a0e}

/* ---------- header ---------- */
.site-head{position:fixed;inset:0 0 auto;z-index:50;display:flex;align-items:center;justify-content:space-between;
  gap:16px;padding:0 clamp(16px,4vw,44px);height:66px;border-bottom:1px solid transparent;transition:background .3s,border-color .3s}
.site-head.scrolled{background:rgba(245,241,230,.93);border-color:var(--hair)}
.brand{display:flex;gap:11px;align-items:center;text-decoration:none;font-family:var(--serif);font-size:19px;letter-spacing:.01em}
.brand svg{color:var(--accent);flex:none}
.brand i{font-style:italic;font-weight:400}
.site-nav{display:flex;gap:clamp(14px,2.4vw,34px)}
.site-nav a{font-family:var(--mono);font-size:11.5px;letter-spacing:.16em;text-decoration:none;color:var(--ink2);padding:6px 0;position:relative}
.site-nav a::after{content:"";position:absolute;left:0;bottom:0;height:1px;width:0;background:var(--accent);transition:width .28s}
.site-nav a:hover{color:var(--ink)} .site-nav a:hover::after{width:100%}
.burger{display:none;background:none;border:1px solid var(--ink2);width:42px;height:42px;cursor:pointer;align-items:center;justify-content:center}
#menu{position:fixed;inset:0;z-index:60;background:var(--paper);display:none;padding:110px 8vw 44px}
#menu.open{display:block}
body.lock{overflow:hidden}
#menu a{display:block;font-size:clamp(34px,8vw,54px);font-family:var(--serif);text-decoration:none;padding:14px 0;border-bottom:1px solid var(--hair)}
#menu a:hover{color:var(--accent-deep);font-style:italic}
#menuClose{position:absolute;top:16px;right:16px;background:none;border:1px solid var(--ink2);width:44px;height:44px;cursor:pointer;font-family:var(--mono)}

/* ---------- hero ---------- */
.hero{padding:calc(66px + clamp(40px,7vh,84px)) 0 0}
.eyebrow{display:flex;align-items:center;gap:16px;font-family:var(--mono);font-size:11px;letter-spacing:.22em;color:var(--ink3);margin-bottom:clamp(18px,3vw,30px);text-transform:uppercase}
.eyebrow b{color:var(--accent-deep);font-weight:600}
.eyebrow .rule{flex:1;border-top:1px solid var(--hair)}
h1{font-size:clamp(42px,7.3vw,95px);line-height:1.02;letter-spacing:-.018em;font-weight:500}
h1 em{font-style:italic}
.rl{display:block;overflow:hidden;padding-bottom:.05em;margin-bottom:-.05em}
.rl>span{display:block;transform:translateY(112%);animation:rise .95s cubic-bezier(.19,.74,.22,1) forwards}
.rl:nth-child(2)>span{animation-delay:.13s}
@keyframes rise{to{transform:none}}
.hero-row{display:flex;justify-content:space-between;align-items:flex-end;gap:44px;margin-top:clamp(22px,3.4vw,40px)}
.lede{font-size:clamp(17px,1.6vw,20px);line-height:1.6;color:var(--ink2);max-width:52ch}
.cta-row{display:flex;gap:14px;flex-wrap:wrap;margin-top:28px}
.hero-price{font-family:var(--mono);text-align:right;color:var(--ink2);display:flex;flex-direction:column;gap:3px;font-size:10.5px;letter-spacing:.2em;flex:none}
.hero-price strong{font-size:clamp(26px,2.6vw,34px);color:var(--ink);letter-spacing:.02em;font-weight:600}
.hero-price .slot b{color:var(--accent-deep);font-weight:600}
.hero-fig{padding:clamp(30px,4.5vw,60px) 4vw 0}
.bike-wrap{max-width:980px;margin-inline:auto}
.figcap{margin-top:14px;font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;color:var(--ink3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.facts{list-style:none;display:flex;flex-wrap:wrap;border-top:1px solid var(--hair);margin-top:clamp(28px,4vw,48px)}
.facts li{font-family:var(--mono);font-size:11px;letter-spacing:.16em;color:var(--ink2);padding:16px 26px 16px 0}
.facts li+li{border-left:1px solid var(--hair);padding-left:26px}

/* ---------- ticker ---------- */
.ticker{border-block:1px solid var(--hair);overflow:hidden}
.tick-track{display:flex;width:max-content;animation:tick 46s linear infinite}
.ticker:hover .tick-track{animation-play-state:paused}
.tick-track ul{display:flex;list-style:none;padding:15px 0}
.tick-track li{font-family:var(--mono);font-size:11px;letter-spacing:.24em;color:var(--ink3);padding:0 0 0 18px}
.tick-track li::after{content:"·";margin-left:36px;color:var(--hair2)}
@keyframes tick{to{transform:translateX(-50%)}}

/* ---------- section furniture ---------- */
h2{font-size:clamp(32px,4.2vw,52px);line-height:1.06;letter-spacing:-.014em;font-weight:500;max-width:22ch}
.sub{margin-top:16px;color:var(--ink2);font-size:clamp(16px,1.4vw,18.5px);max-width:52ch}
.dark h2,.dark .sub{color:var(--cream)} .dark .sub{color:var(--dim)}
.dark .eyebrow{color:var(--dim2)} .dark .eyebrow b{color:var(--accentd)} .dark .eyebrow .rule{border-color:var(--haird)}

/* ---------- idea ---------- */
.idea-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,4fr);gap:clamp(28px,5vw,84px);margin-top:clamp(34px,5vw,64px)}
.pull{font-size:clamp(22px,2.4vw,29px);line-height:1.34;letter-spacing:-.008em;max-width:30ch}
.body-copy p{margin-top:18px;color:var(--ink2)}
.figs{display:flex;gap:clamp(24px,4vw,56px);margin-top:clamp(44px,6vw,76px);flex-wrap:wrap;border-top:1px solid var(--hair);padding-top:38px}
.fig{flex:1 1 190px;max-width:280px}
.fig svg{width:100%;height:auto;color:var(--ink);margin-bottom:14px}
.fig figcaption{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;color:var(--ink3);line-height:1.7}

/* ---------- fit ---------- */
.band{background:var(--paper2)}
.fit-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,72px);align-items:start}
.controls{margin-top:36px;display:grid;gap:30px}
.lab{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.2em;color:var(--ink3);text-transform:uppercase;margin-bottom:14px}
.lab b{color:var(--accent-deep)}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:2px;background:var(--hair2);cursor:ew-resize}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:22px;height:22px;border-radius:50%;background:var(--ink);border:5px solid var(--paper2);box-shadow:0 0 0 1.5px var(--ink)}
input[type=range]::-moz-range-thumb{width:12px;height:12px;border-radius:50%;background:var(--ink);border:5px solid var(--paper2);box-shadow:0 0 0 1.5px var(--ink)}
fieldset{border:0;padding:0}
.radios{display:flex;flex-wrap:wrap;gap:10px}
.pill{position:relative;display:inline-flex;border:1px solid var(--ink3);border-radius:999px;padding:10px 18px;cursor:pointer;
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;color:var(--ink2);transition:all .22s;user-select:none}
.pill input{position:absolute;opacity:0;pointer-events:none}
.pill:hover{border-color:var(--ink)}
.pill.on,.pill:has(input:checked){background:var(--ink);border-color:var(--ink);color:var(--paper)}
.pill input:focus-visible+span{outline:2px solid var(--accent);outline-offset:3px}
.fit-out{border:1px solid var(--hair2);background:var(--paper);padding:24px 26px 26px;margin-top:4px}
.fit-line{display:flex;align-items:baseline;gap:12px;font-family:var(--mono);font-size:13px;letter-spacing:.06em;padding:9px 0;color:var(--ink2)}
.fit-line .dots{flex:1;border-bottom:2px dotted var(--hair2);transform:translateY(-4px);min-width:20px}
.fit-line b{font-weight:600;color:var(--ink);text-align:right}
.fit-say{margin-top:16px;font-size:15px;color:var(--ink2);font-style:italic}
.fit-out .btn{margin-top:20px}

/* ---------- build (dark) ---------- */
.dark{background:var(--dark);color:var(--cream)}
.build-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:clamp(30px,4.5vw,64px);align-items:start;margin-top:clamp(36px,5vw,60px)}
.sheet{background:var(--paper);padding:clamp(12px,1.8vw,24px);border:1px solid #413B2E}
.dark .figcap{color:var(--dim2)}
.opt-group{margin-top:32px}
.opt-h{font-family:var(--mono);font-size:11px;letter-spacing:.22em;color:var(--dim2);display:flex;justify-content:space-between;gap:16px;padding-bottom:8px}
.swatches{display:flex;flex-wrap:wrap;gap:16px 10px}
.sw{background:none;border:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;width:78px;padding:4px 2px}
.sw-chip{width:38px;height:38px;border-radius:50%;background:var(--c);border:1px solid rgba(0,0,0,.4);transition:box-shadow .25s,transform .25s}
.sw:hover .sw-chip{transform:translateY(-2px)}
.sw[aria-pressed=true] .sw-chip{box-shadow:0 0 0 3px var(--dark),0 0 0 5px var(--accentd)}
.sw-name{font-family:var(--mono);font-size:10px;letter-spacing:.14em;color:var(--dim)}
.sw[aria-pressed=true] .sw-name{color:var(--cream)}
.sw-add{font-family:var(--mono);font-size:9px;letter-spacing:.1em;color:#6E6753}
.sw-chip.hatchchip{background:repeating-linear-gradient(45deg,#7E7761 0 2px,#D6CFBB 2px 6px)}
.opt{position:relative;display:grid;grid-template-columns:auto 1fr auto;gap:15px;align-items:start;padding:14px 6px;border-top:1px solid var(--haird);cursor:pointer;transition:background .2s}
.opt:hover{background:#211E16}
.opt input{position:absolute;opacity:0;pointer-events:none}
.opt .dot{width:15px;height:15px;border-radius:50%;border:1.5px solid var(--dim2);position:relative;margin-top:5px;flex:none}
.opt.check .dot{border-radius:2px}
.opt input:checked~.dot{border-color:var(--accentd)}
.opt input:checked~.dot::after{content:"";position:absolute;inset:3px;border-radius:inherit;background:var(--accentd)}
.opt input:focus-visible~.dot{outline:2px solid var(--accentd);outline-offset:3px}
.opt-name{font-family:var(--serif);font-size:17px;color:var(--cream);display:block}
.opt-desc{font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;color:var(--dim);display:block;margin-top:3px}
.opt-price{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--dim2);white-space:nowrap;margin-top:5px}
.opt.sel .opt-price{color:var(--accentd)}
.opt.off{opacity:.38;pointer-events:none}
.sum-box{margin-top:38px;border:1px solid var(--haird);padding:24px 24px 26px}
.sum-price{display:flex;justify-content:space-between;align-items:baseline;font-family:var(--mono)}
.sum-price span{font-size:10.5px;letter-spacing:.2em;color:var(--dim2)}
#cfgPrice{font-size:34px;letter-spacing:.02em;font-weight:600;color:var(--cream)}
.sum-rows{margin-top:16px;display:grid;gap:8px;font-family:var(--mono);font-size:11px;letter-spacing:.12em;color:var(--dim)}
.sum-row{display:flex;justify-content:space-between;gap:14px}
.sum-row b{color:var(--cream);font-weight:500;text-align:right}
.sum-box .btn{margin-top:22px}
.fine{font-size:13.5px;color:var(--dim);margin-top:12px;font-style:italic}
.incl{margin-top:clamp(56px,7vw,92px);border-top:1px solid var(--haird);padding-top:34px}
.incl h3{font-family:var(--mono);font-size:11px;letter-spacing:.24em;color:var(--dim2);font-weight:500}
.incl ul{columns:2;column-gap:clamp(30px,4vw,72px);list-style:none;margin-top:20px}
.incl li{break-inside:avoid;border-bottom:1px solid var(--haird);padding:12px 0;font-family:var(--mono);font-size:12.5px;letter-spacing:.04em;color:#CDC5B0;display:flex;justify-content:space-between;gap:18px}
.incl li b{color:var(--cream);font-weight:500;white-space:nowrap}
.incl .note{margin-top:22px;font-style:italic;color:var(--dim)}

/* ---------- process ---------- */
.proc-grid{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:clamp(28px,5vw,80px)}
.proc-sticky{position:sticky;top:100px}
.proc-note{margin-top:18px;color:var(--ink3);font-style:italic;font-size:15px;max-width:34ch}
.step{display:grid;grid-template-columns:88px 1fr auto;gap:26px;padding:30px 0;border-top:1px solid var(--hair)}
.step:first-of-type{border-top:0}
.step .no{font-family:var(--mono);font-size:13px;letter-spacing:.2em;color:var(--accent-deep);padding-top:6px}
.step h3{font-size:clamp(21px,2.2vw,26px);font-weight:500;letter-spacing:-.01em}
.step p{color:var(--ink2);margin-top:8px;max-width:56ch}
.step .meta{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;color:var(--ink3);white-space:nowrap;padding-top:8px}

/* ---------- voices ---------- */
.quote{padding:clamp(30px,4vw,48px) 0;border-top:1px solid var(--hair);display:grid;grid-template-columns:minmax(0,8fr) minmax(0,4fr);gap:34px;align-items:end}
.quote .qt{font-size:clamp(22px,2.7vw,32px);line-height:1.36;letter-spacing:-.01em}
.quote .qt::before{content:"“";color:var(--accent-deep)}
.quote .qt::after{content:"”";color:var(--accent-deep)}
.quote .attr{font-family:var(--mono);font-size:11px;letter-spacing:.16em;color:var(--ink3);line-height:2;padding-bottom:6px}
.quote.alt .qt{grid-column:2}
.quote.alt .attr{grid-column:1;grid-row:1;justify-self:end;text-align:right}
.statline{margin-top:44px;border-block:1px solid var(--hair);padding:22px 0;font-family:var(--mono);letter-spacing:.2em;font-size:11px;color:var(--ink2);display:flex;flex-wrap:wrap;gap:8px 0}
.statline i{font-style:normal;color:var(--accent-deep);padding:0 14px}
.fleet{margin-top:20px;font-style:italic;color:var(--ink3);font-size:14.5px}

/* ---------- faq ---------- */
.faq{max-width:880px}
.faq-item{border-bottom:1px solid var(--hair)}
.faq-q{width:100%;display:flex;justify-content:space-between;align-items:center;gap:20px;background:none;border:0;padding:22px 2px;cursor:pointer;font-family:var(--serif);font-size:clamp(18px,2vw,22px);color:var(--ink);text-align:left}
.faq-q:hover{color:var(--accent-deep)}
.fx{position:relative;width:14px;height:14px;flex:none}
.fx::before,.fx::after{content:"";position:absolute;background:var(--accent-deep);left:0;top:6.2px;width:14px;height:1.6px;transition:transform .35s}
.fx::after{transform:rotate(90deg)}
.faq-item.open .fx::after{transform:rotate(0)}
.faq-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows .4s cubic-bezier(.4,0,.2,1)}
.faq-item.open .faq-body{grid-template-rows:1fr}
.faq-body>div{overflow:hidden}
.faq-body p{color:var(--ink2);padding:0 2px 24px;max-width:62ch}

/* ---------- visit ---------- */
.visit-grid{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,5fr);gap:clamp(28px,5vw,80px);margin-top:10px}
.vtable{border-top:1px solid var(--hair)}
.vrow{display:grid;grid-template-columns:132px 1fr;gap:18px;padding:17px 2px;border-bottom:1px solid var(--hair);font-size:15.5px;align-items:baseline}
.vrow .k{font-family:var(--mono);font-size:10.5px;letter-spacing:.18em;color:var(--ink3)}
.copybtn{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;background:none;border:0;border-bottom:1px solid var(--accent);color:var(--accent-deep);cursor:pointer;padding:0 0 2px;margin-left:14px}
.copybtn:hover{color:var(--ink)}

/* ---------- footer ---------- */
footer.dark{padding:clamp(64px,8vw,104px) 0 42px;scroll-margin-top:0}
.foot-top{display:flex;justify-content:space-between;gap:44px;align-items:flex-end;flex-wrap:wrap}
.foot-state{font-size:clamp(30px,4.4vw,54px);letter-spacing:-.02em;line-height:1.05;max-width:11ch}
.foot-top svg{color:var(--accentd);flex:none}
.foot-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:30px;margin-top:clamp(40px,6vw,70px);padding-top:36px;border-top:1px solid var(--haird)}
.fc h4{font-family:var(--mono);font-size:10.5px;letter-spacing:.24em;color:var(--dim2);font-weight:500;margin-bottom:14px}
.fc a,.fc span{display:block;color:#CDC5B0;text-decoration:none;font-size:15px;padding:4px 0}
.fc a:hover{color:var(--accentd)}
.legal{margin-top:46px;padding-top:22px;border-top:1px solid var(--haird);display:flex;justify-content:space-between;gap:14px 30px;flex-wrap:wrap;font-family:var(--mono);font-size:10px;letter-spacing:.14em;color:#7D755F;line-height:1.9}

/* ---------- modal / toast / tip ---------- */
.modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:20px}
.modal[hidden]{display:none}
.modal-back{position:absolute;inset:0;background:rgba(20,17,11,.62)}
.modal-card{position:relative;background:var(--paper);color:var(--ink);width:min(560px,100%);max-height:88vh;overflow:auto;padding:clamp(24px,4vw,42px);border:1px solid var(--ink2)}
.modal-card h3{font-size:27px;font-weight:500;letter-spacing:-.01em;padding-right:44px}
.modal-x{position:absolute;top:14px;right:14px;background:none;border:1px solid var(--ink3);width:36px;height:36px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--ink2)}
.modal-x:hover{border-color:var(--accent-deep);color:var(--accent-deep)}
#mSum{margin:22px 0 8px;border-block:1px solid var(--hair);padding:16px 0;display:grid;gap:9px}
.srow{display:flex;justify-content:space-between;gap:18px;font-family:var(--mono);font-size:11.5px;letter-spacing:.08em;color:var(--ink2)}
.srow b{color:var(--ink);font-weight:500;text-align:right}
#mForm label{display:block;margin-top:18px;font-family:var(--mono);font-size:10.5px;letter-spacing:.18em;color:var(--ink3)}
#mForm input,#mForm textarea{display:block;width:100%;margin-top:8px;border:1px solid var(--hair2);background:#FBF8EF;padding:12px 14px;font:16px var(--serif);color:var(--ink);resize:vertical}
#mForm input:focus,#mForm textarea:focus{outline:none;border-color:var(--ink)}
.err{color:#A63426;font-size:14px;margin-top:14px;font-style:italic}
#mForm .btn{margin-top:22px}
#mForm .fine{color:var(--ink3)}
#mDone p{color:var(--ink2);margin-top:14px}
#mDone p.first{font-size:22px;color:var(--ink);font-style:italic}
#mDone .btn{margin-top:24px}
.toasts{position:fixed;left:20px;bottom:20px;z-index:90;display:grid;gap:10px}
.toast{background:var(--ink);color:var(--paper);font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;padding:14px 18px;max-width:360px;border-left:3px solid var(--accentd);opacity:0;transform:translateY(8px);transition:.35s}
.toast.on{opacity:1;transform:none}
#tip{position:fixed;z-index:95;background:var(--ink);color:var(--paper);font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;padding:7px 10px;pointer-events:none;white-space:nowrap}
#tip[hidden]{display:none}

/* ---------- the drawing ---------- */
.bike{display:block;width:100%;height:auto;color:#1B1913;cursor:crosshair}
.bike .paint-edge,.bike .paint{fill:none;stroke-linecap:round}
.bike .tube-edge{stroke:#4A4436;stroke-width:11.2}
.bike .tube{stroke:var(--paint,transparent);stroke-width:8.6;transition:stroke .5s}
.bike.blueprint .paint,.bike.blueprint .paint-edge{display:none}
.bike .p-ink{fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;transition:stroke .25s,stroke-width .25s}
.bike .part:hover .p-ink{stroke:var(--accent-deep);stroke-width:2.7}
.bike .part:hover .box{stroke:var(--accent-deep)}
.bike .wheel:hover circle:not(.hitfill){stroke:var(--accent-deep)}
.bike .p-hit{fill:none;stroke:#000;stroke-opacity:0;stroke-width:18;pointer-events:stroke}
.bike .hitfill{fill:#000;fill-opacity:0;stroke:none;pointer-events:fill}
.bike .spokes{transform-box:fill-box;transform-origin:center}
.bike.drawn .spokes{animation:spin 80s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.bike .lamp-rays{opacity:.3}
.bike.drawn .lamp-rays{animation:lamp 3.8s ease-in-out infinite}
@keyframes lamp{0%,100%{opacity:.25}50%{opacity:1}}
.bike .late{opacity:0;transition:opacity .6s ease .1s}
.bike.drawn .late{opacity:1}
.bike .anno{opacity:0;transition:opacity 1s ease .15s;pointer-events:none}
.bike.drawn .anno{opacity:1}
.bike .tt{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;fill:#6B6353;stroke:none}
.bike .tt-s{font-size:8px}
.bike .box{fill:var(--boxfill,none);stroke:currentColor;stroke-width:2}
.bike.render{--boxfill:#EFE8D5}
.bike.blueprint .box{stroke-dasharray:7 5}
.bike[data-cargo="box"] .g-rail{display:none}
.bike[data-cargo="rail"] .g-box{display:none}
.bike[data-cargo="flat"] .g-rail,.bike[data-cargo="flat"] .g-box{display:none}
.bike[data-assist="none"] .g-motor{display:none}

/* ---------- reveal ---------- */
.rv{opacity:0;transform:translateY(26px);transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.2,1)}
.rv.in{opacity:1;transform:none}

/* ---------- responsive ---------- */
@media (max-width:1020px){
  .site-nav{display:none}.burger{display:flex}
  .idea-grid,.fit-grid,.build-grid,.proc-grid,.visit-grid{grid-template-columns:1fr}
  .proc-sticky{position:static}
  .hero-row{flex-direction:column;align-items:flex-start;gap:26px}
  .hero-price{text-align:left}
  .quote,.quote.alt{grid-template-columns:1fr}
  .quote.alt .qt,.quote .qt{grid-column:auto}
  .quote.alt .attr{grid-column:auto;justify-self:start;text-align:left}
}
@media (max-width:700px){
  .facts{display:grid;grid-template-columns:1fr 1fr;border-top:0}
  .facts li{border-top:1px solid var(--hair);padding:12px 0}
  .facts li+li{border-left:0;padding-left:0}
  .incl ul{columns:1}
  .step{grid-template-columns:60px 1fr}
  .step .meta{grid-column:2;grid-row:2;padding-top:2px}
  .bike text{display:none}
  .toasts{left:12px;right:12px}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .rl>span{animation:none;transform:none}
  .tick-track{animation:none}
  .bike.drawn .spokes,.bike.drawn .lamp-rays{animation:none}
  .rv{transition:none;opacity:1;transform:none}
}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<noscript>This page draws its bicycle with JavaScript — enable it to see the sheets.</noscript>

<header class="site-head" id="siteHead">
  <a class="brand" href="#top" aria-label="Lumen Cycles — home">
    <svg width="26" height="26" viewBox="0 0 32 32" aria-hidden="true"><circle cx="16" cy="16" r="5.6" fill="currentColor"/><g stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"><path d="M16 2v4M16 26v4M2 16h4M26 16h4M6.2 6.2l2.8 2.8M23 23l2.8 2.8M25.8 6.2L23 9M9 23l-2.8 2.8"/></g></svg>
    Lumen&nbsp;Cycles
  </a>
  <nav class="site-nav" aria-label="Sections">
    <a href="#idea">THE IDEA</a><a href="#fit">MADE TO MEASURE</a><a href="#build">THE BUILD</a><a href="#process">PROCESS</a><a href="#visit">VISIT</a>
  </nav>
  <div style="display:flex;gap:10px;align-items:center">
    <button class="btn sm" data-open-modal>Reserve a slot</button>
    <button class="burger" id="menuBtn" aria-label="Open menu" aria-expanded="false">
      <svg width="22" height="10" viewBox="0 0 22 10" stroke="currentColor" stroke-width="1.6" fill="none"><path d="M0 1h22M0 9h22"/></svg>
    </button>
  </div>
</header>

<div id="menu" role="dialog" aria-label="Menu">
  <button id="menuClose" aria-label="Close menu">CLOSE</button>
  <a href="#idea">The idea</a><a href="#fit">Made to measure</a><a href="#build">The build</a><a href="#process">Process</a><a href="#questions">Questions</a><a href="#visit">Visit</a>
</div>

<main id="main">
  <!-- ============ HERO ============ -->
  <section class="hero" id="top">
    <div class="container">
      <p class="eyebrow"><b>CARGO BIKES, DRAWN &amp; BRAZED TO ORDER</b><span class="rule"></span><span>KØBENHAVN K</span></p>
      <h1>
        <span class="rl"><span>A cargo bike,</span></span>
        <span class="rl"><span><em>made to measure.</em></span></span>
      </h1>
      <div class="hero-row">
        <div class="hero-copy">
          <p class="lede">Lumen builds one model — the Lumen&nbsp;L front-loader — one frame at a time, in a workshop on Refshaleøen. Drawn for your body, your street and whatever you carry. Brazed, painted, delivered in ten to fourteen weeks.</p>
          <div class="cta-row">
            <a class="btn" href="#build">Configure yours
              <svg class="arr" width="15" height="10" viewBox="0 0 15 10" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M0 5h13M9 1l4 4-4 4"/></svg></a>
            <a class="btn ghost" href="#visit">Visit the workshop</a>
          </div>
        </div>
        <div class="hero-price mono-s">
          <span>FROM</span><strong>€3,850</strong>
          <span>INCL. VAT · DELIVERED</span>
          <span class="slot">NEXT SLOTS · <b id="slotHero">—</b></span>
        </div>
      </div>
    </div>
    <figure class="hero-fig">
      <div class="bike-wrap" id="heroBike"></div>
      <figcaption class="figcap" style="max-width:980px;margin-inline:auto">
        <span>FIG. 1 — THE LUMEN L · SIDE ELEVATION · RIDER 178 CM</span>
        <span>HOVER THE LINES — THE DRAWING ANNOTATES ITSELF</span>
      </figcaption>
    </figure>
    <div class="container">
      <ul class="facts">
        <li>EST. 2016</li><li>1,243 FRAMES DELIVERED</li><li>10-YEAR FRAME WARRANTY</li><li>DELIVERY 10–14 WEEKS</li>
      </ul>
    </div>
  </section>

  <div class="ticker" aria-hidden="true"><div class="tick-track" id="tickTrack">
    <ul>
      <li>DRAWN TO MEASURE</li><li>FILET-BRAZED CR-MO</li><li>LOAD FLOOR 36 CM</li><li>90 KG CARGO RATED</li>
      <li>LOW-TRAIL STEERING</li><li>BUILT ONE AT A TIME</li><li>REFSHALEØEN · KØBENHAVN</li>
    </ul>
  </div></div>

  <!-- ============ 01 THE IDEA ============ -->
  <section id="idea">
    <div class="container">
      <p class="eyebrow rv"><b>01</b><span class="rule"></span><span>THE IDEA</span></p>
      <h2 class="rv">One bike, drawn around your life.</h2>
      <div class="idea-grid">
        <div class="rv">
          <p class="pull">Most cargo bikes come in three sizes. People don’t. So every Lumen frame is drawn for one rider, one household, one street — then brazed to outlive the trend that bought it.</p>
        </div>
        <div class="body-copy rv">
          <p>The tubing is double-butted Cr-Mo, mitered by hand and joined with silver fillets — not welded robots, not extruded aluminium. Steel rides like a good fork should, forgives a pothole, and bends back when a courier says it won’t.</p>
          <p>It is also repairable forever, in any workshop in Europe. Our first frames from 2016 are on their second paint and their third household. That was the plan.</p>
          <p>The brief is Copenhagen: a 36-cm load floor so the kids climb in themselves, a low-trail fork that steers loaded like it does empty, a hallway-width turning circle, and salt-proof cable routing. Then we measure <em>you</em> — not the average of you.</p>
        </div>
      </div>
      <div class="figs rv">
        <figure class="fig">
          <svg viewBox="0 0 130 100" aria-label="Cast dropout detail">
            <g fill="none" stroke="currentColor" stroke-linecap="round">
              <path d="M130 44 H74" stroke-width="10"/>
              <rect x="30" y="16" width="44" height="52" rx="7" stroke-width="2"/>
              <rect x="34" y="34" width="26" height="12" rx="4" stroke-width="1.4"/>
              <circle cx="47" cy="40" r="6" stroke-width="1.4" stroke-dasharray="3 3"/>
              <path d="M38 68 L30 84" stroke-width="6"/>
              <circle cx="29" cy="86" r="3.5" stroke-width="1.6"/>
            </g>
          </svg>
          <figcaption>FIG. 2 — CAST DROP-OUT<br>12 MM THROUGH-AXLE</figcaption>
        </figure>
        <figure class="fig">
          <svg viewBox="0 0 130 100" aria-label="Fillet-brazed joint detail">
            <g fill="none" stroke="currentColor" stroke-linecap="round">
              <path d="M62 10 V96" stroke-width="12"/>
              <path d="M54 72 H130" stroke-width="12"/>
              <path d="M68 54 Q68 66 84 66" stroke="#B45A17" stroke-width="5"/>
            </g>
          </svg>
          <figcaption>FIG. 3 — FILLET-BRAZED IN SILVER<br>FILED BY HAND</figcaption>
        </figure>
        <figure class="fig">
          <svg viewBox="0 0 130 100" aria-label="Sealed cable port detail">
            <g fill="none" stroke="currentColor" stroke-linecap="round">
              <path d="M12 90 L118 6" stroke-width="10"/>
              <circle cx="46" cy="62" r="6" stroke-width="2"/>
              <circle cx="84" cy="33" r="6" stroke-width="2"/>
              <path d="M84 33 C102 36 110 52 106 74" stroke-width="1.6" stroke-dasharray="4 4"/>
            </g>
          </svg>
          <figcaption>FIG. 4 — SEALED PORTS<br>CABLES RUN INSIDE THE FRAME</figcaption>
        </figure>
      </div>
    </div>
  </section>

  <!-- ============ 02 MADE TO MEASURE ============ -->
  <section id="fit" class="band">
    <div class="container">
      <p class="eyebrow rv"><b>02</b><span class="rule"></span><span>MADE TO MEASURE</span></p>
      <h2 class="rv">Start with the saddle. Work backwards.</h2>
      <div class="fit-grid">
        <div class="rv">
          <p class="sub">One number changes the whole drawing. Drag the slider — this is the same sheet we send you for sign-off before the torch is lit.</p>
          <div class="controls">
            <div>
              <label class="lab" for="fH">RIDER HEIGHT — <b id="fHv">178 CM</b></label>
              <input type="range" id="fH" min="150" max="205" value="178" step="1" aria-label="Rider height in centimetres">
            </div>
            <fieldset>
              <span class="lab">WHAT DOES IT CARRY?</span>
              <div class="radios">
                <label class="pill"><input type="radio" name="use" value="family" checked><span>Kids &amp; family</span></label>
                <label class="pill"><input type="radio" name="use" value="freight"><span>Goods &amp; tools</span></label>
                <label class="pill"><input type="radio" name="use" value="mixed"><span>A bit of everything</span></label>
              </div>
            </fieldset>
            <fieldset>
              <span class="lab">YOUR STREETS</span>
              <div class="radios">
                <label class="pill"><input type="radio" name="ter" value="flat" checked><span>Flat as a fjord</span></label>
                <label class="pill"><input type="radio" name="ter" value="hills"><span>Hills &amp; headwind</span></label>
              </div>
            </fieldset>
            <div class="fit-out">
              <div class="fit-line"><span>SADDLE</span><span class="dots"></span><b><span id="fSaddle">1 039</span> MM</b></div>
              <div class="fit-line"><span>BARS</span><span class="dots"></span><b><span id="fBars">1 134</span> MM</b></div>
              <div class="fit-line"><span>REACH</span><span class="dots"></span><b><span id="fReach">641</span> MM</b></div>
              <div class="fit-line"><span>LOAD FLOOR</span><span class="dots"></span><b>360 MM</b></div>
              <div class="fit-line"><span>FRONT LOAD</span><span class="dots"></span><b id="fLoad">FAMILY BOX · 2 SEATS</b></div>
              <div class="fit-line"><span>ASSIST</span><span class="dots"></span><b id="fAssist">NONE NEEDED</b></div>
              <p class="fit-say" id="fSay"></p>
              <button class="btn" id="fApply">Apply to the build below
                <svg class="arr" width="15" height="10" viewBox="0 0 15 10" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M0 5h13M9 1l4 4-4 4"/></svg></button>
            </div>
          </div>
        </div>
        <div class="rv">
          <div class="bike-wrap" id="fitBike"></div>
          <p class="figcap"><span>FIG. 5 — YOUR GEOMETRY, LIVE</span><span>DRAG THE SLIDER</span></p>
        </div>
      </div>
    </div>
  </section>

  <!-- ============ 03 THE BUILD ============ -->
  <section id="build" class="dark">
    <div class="container">
      <p class="eyebrow rv"><b>03</b><span class="rule"></span><span>THE BUILD</span></p>
      <h2 class="rv">Spec it like you ride it.</h2>
      <p class="sub rv">Every choice redraws the sheet and the price. All of it is reversible until the brazing starts.</p>
      <div class="build-grid">
        <div class="rv">
          <div class="sheet" id="cfgBike"></div>
          <p class="figcap"><span>FIG. 6 — YOUR BUILD · SIDE ELEVATION</span><span id="capRight">PAINT · KRIDT</span></p>
        </div>
        <div class="rv">
          <div class="opt-group">
            <div class="opt-h"><span>PAINT</span><span>SIX COLOURS</span></div>
            <div class="swatches" id="swatches"></div>
          </div>
          <div class="opt-group"><div class="opt-h"><span>FRONT CARGO</span><span>90 KG RATED</span></div><div id="cargoOpts"></div></div>
          <div class="opt-group"><div class="opt-h"><span>DRIVETRAIN</span></div><div id="driveOpts"></div></div>
          <div class="opt-group"><div class="opt-h"><span>ASSIST</span></div><div id="assistOpts"></div></div>
          <div class="opt-group"><div class="opt-h"><span>EXTRAS</span></div><div id="extraOpts"></div></div>
          <div class="sum-box">
            <div class="sum-price"><span>CONFIGURED</span><b id="cfgPrice">€3,850</b></div>
            <div class="sum-rows">
              <div class="sum-row"><span>EST. WEIGHT</span><b id="cfgWeight">≈ 30.8 KG</b></div>
              <div class="sum-row"><span>LEAD TIME</span><b id="cfgLead">~ 10 WEEKS</b></div>
              <div class="sum-row"><span>BUILD SLOT</span><b id="cfgSlot">—</b></div>
              <div class="sum-row"><span>DEPOSIT</span><b>€500</b></div>
            </div>
            <button class="btn w100" id="cfgReserve">Reserve this build</button>
            <p class="fine">Nothing is charged today. The deposit link arrives with your frame drawing.</p>
          </div>
        </div>
      </div>
      <div class="incl rv">
        <h3>EVERY LUMEN L LEAVES THE WORKSHOP WITH —</h3>
        <ul>
          <li><b>FRAMESET</b><span>Cr-Mo, fillet-brazed, aligned on the bench</span></li>
          <li><b>FORK</b><span>Low-trail Cr-Mo, 45 mm offset</span></li>
          <li><b>WHEELS</b><span>36 spokes, brass nipples — built in-house</span></li>
          <li><b>TIRES</b><span>Schwalbe Super Moto-X, puncture-guard</span></li>
          <li><b>BRAKES</b><span>Magura MT5 · 4-piston · 203/180 mm</span></li>
          <li><b>DRIVETRAIN</b><span>Shimano Nexus 8, belt-ready</span></li>
          <li><b>COCKPIT</b><span>Swept bar, cork grips, drawn stem</span></li>
          <li><b>SADDLE</b><span>Leather, steel rails</span></li>
          <li><b>LIGHTING</b><span>Wired rear light, sealed loom</span></li>
          <li><b>FITTING &amp; DELIVERY</b><span>Session, hand-over ride, our van</span></li>
        </ul>
        <p class="note">Pedals, kickstand, first service and delivery in Denmark are included. Yes, really. Prices include VAT; the frame is guaranteed ten years, everything else two.</p>
      </div>
    </div>
  </section>

  <!-- ============ 04 PROCESS ============ -->
  <section id="process">
    <div class="container">
      <div class="proc-grid">
        <div class="proc-sticky rv">
          <p class="eyebrow"><b>04</b><span class="rule"></span><span>THE PROCESS</span></p>
          <h2>From first mail to first load.</h2>
          <p class="proc-note">Ten to fourteen weeks — most of it paint curing and us refusing to rush your head tube.</p>
        </div>
        <div>
          <div class="step rv"><span class="no">01</span><div><h3>Say hello</h3><p>Write, call, or come by. We ask about you: who rides it, what it carries, where it sleeps, which hills are on the way.</p></div><span class="meta">20 MIN</span></div>
          <div class="step rv"><span class="no">02</span><div><h3>The fitting</h3><p>Measured at the workshop or over video. Your frame drawing arrives as a sheet like the ones on this page — you approve it, or we redraw it.</p></div><span class="meta">WEEK 0</span></div>
          <div class="step rv"><span class="no">03</span><div><h3>Slot &amp; deposit</h3><p>€500 books your place on the bench. Build slots open monthly; everything stays changeable until the tubing is cut.</p></div><span class="meta">WEEK 1</span></div>
          <div class="step rv"><span class="no">04</span><div><h3>The build</h3><p>Miter, braze, align, paint. Photographs from the bench every Friday — no surprises, no white boxes.</p></div><span class="meta">WEEKS 2–7</span></div>
          <div class="step rv"><span class="no">05</span><div><h3>Handover</h3><p>Fitted, torqued, and ridden — loaded with whatever you brought. Thirty days of free adjustment, ten years of warranty.</p></div><span class="meta">WEEK 8</span></div>
        </div>
      </div>
    </div>
  </section>

  <!-- ============ 05 VOICES ============ -->
  <section id="voices">
    <div class="container">
      <p class="eyebrow rv"><b>05</b><span class="rule"></span><span>WORD OF MOUTH</span></p>
      <blockquote class="quote rv"><p class="qt">We sold the car before the bike was even finished. Two kids, a dog and the weekly shop — the school run is the best part of my day.</p><p class="attr">CAMILLA F. · FREDERIKSBERG<br>FAMILY BOX · SIGNAL RØD</p></blockquote>
      <blockquote class="quote alt rv"><p class="qt">Fourteen thousand kilometres as a courier. One wheel-true, one set of pads. Mikkel still greets me by name — and by bike.</p><p class="attr">JONAS E. · NØRREBRO<br>FLATBED · COURIER SPEC</p></blockquote>
      <blockquote class="quote rv"><p class="qt">I’m 1.93 and every bike I’ve owned was a compromise. This is the first one that isn’t.</p><p class="attr">HENRIK S. · AMAGER<br>ASH RAIL · ANTRACIT</p></blockquote>
      <p class="statline rv">SINCE 2016<i>·</i>1,243 FRAMES<i>·</i>0 WRITTEN OFF<i>·</i>61% OF OWNERS SOLD A CAR</p>
      <p class="fleet rv">Their fleets ride on Lumens too: Kaffelab Nord · Værftets Bageri · Nordisk Frugt — and about twelve hundred households.</p>
    </div>
  </section>

  <!-- ============ 06 QUESTIONS ============ -->
  <section id="questions" class="band">
    <div class="container">
      <p class="eyebrow rv"><b>06</b><span class="rule"></span><span>FAIR QUESTIONS</span></p>
      <h2 class="rv" style="margin-bottom:clamp(26px,4vw,44px)">Asked before every deposit.</h2>
      <div class="faq rv">
        <div class="faq-item"><button class="faq-q" aria-expanded="false">Can I try one before I commit?<span class="fx"></span></button>
          <div class="faq-body"><div><p>Yes. Ride the workshop bikes Thursday to Saturday — bring what you’d actually carry. Inside Greater Copenhagen we’ll also leave a demo bike with you for a weekend, against a refundable deposit.</p></div></div></div>
        <div class="faq-item"><button class="faq-q" aria-expanded="false">Why €3,850?<span class="fx"></span></button>
          <div class="faq-body"><div><p>About sixty hours of skilled labour, honest tubing, and wheels built by the person who trues them. We publish the kit list because there’s nothing to hide and no “premium tier” to upsell you later — the options you see are all the options there are.</p></div></div></div>
        <div class="faq-item"><button class="faq-q" aria-expanded="false">What happens if something breaks?<span class="fx"></span></button>
          <div class="faq-body"><div><p>Gears and brakes are standard Shimano and Magura parts — any bike shop can service them. The frame is guaranteed ten years, and because it’s fillet-brazed steel it stays repairable for life, even after a crash. The first year of service is on us.</p></div></div></div>
        <div class="faq-item"><button class="faq-q" aria-expanded="false">Do you deliver outside Denmark?<span class="fx"></span></button>
          <div class="faq-body"><div><p>Across Denmark we deliver personally, adjusted and ridden. EU-wide we ship crated and nearly assembled — five bolts and a torque key, both included in the box.</p></div></div></div>
        <div class="faq-item"><button class="faq-q" aria-expanded="false">Is the e-assist worth it?<span class="fx"></span></button>
          <div class="faq-body"><div><p>If your streets are flat and your loads under 40 kg: no — save the €1,750. If there’s a bridge, a headwind habit, or three kids uphill: yes, and take the 540 Wh. We’ll tell you honestly which one you are.</p></div></div></div>
        <div class="faq-item"><button class="faq-q" aria-expanded="false">What about Danish winters?<span class="fx"></span></button>
          <div class="faq-body"><div><p>Salt was the design brief: fully internal routing, waxed frame tubes, sealed bearings and stainless hardware throughout. We still recommend wiping it down — out of pride, not rust.</p></div></div></div>
      </div>
    </div>
  </section>

  <!-- ============ 07 VISIT ============ -->
  <section id="visit">
    <div class="container">
      <div class="visit-grid">
        <div class="rv">
          <p class="eyebrow"><b>07</b><span class="rule"></span><span>THE WORKSHOP</span></p>
          <h2>The kettle is usually on.</h2>
          <p class="sub">Test rides happen Thursday to Saturday — bring the kids, bring the cargo. Otherwise we answer email within two working days, in plain language, with no follow-up funnel.</p>
        </div>
        <div class="vtable rv">
          <div class="vrow"><span class="k">WORKSHOP</span><span>Refshalevej 163A, 1432 København K</span></div>
          <div class="vrow"><span class="k">HOURS</span><span>Thu–Sat 10.00–17.00 · otherwise by appointment</span></div>
          <div class="vrow"><span class="k">EMAIL</span><span>hello@lumencycles.dk<button class="copybtn" id="copyMail">COPY</button></span></div>
          <div class="vrow"><span class="k">PHONE</span><span>+45 31 12 84 60</span></div>
          <div class="vrow"><span class="k">TEST RIDES</span><span>Thu–Sat · helmets and kids’ seats on hand</span></div>
        </div>
      </div>
    </div>
  </section>
</main>

<footer class="dark">
  <div class="container">
    <div class="foot-top">
      <p class="foot-state">Carry more.<br>Drive less.</p>
      <svg width="56" height="56" viewBox="0 0 32 32" aria-hidden="true"><circle cx="16" cy="16" r="5.6" fill="currentColor"/><g stroke="currentColor" stroke-width="1.6" stroke-linecap="round" fill="none"><path d="M16 2v4M16 26v4M2 16h4M26 16h4M6.2 6.2l2.8 2.8M23 23l2.8 2.8M25.8 6.2L23 9M9 23l-2.8 2.8"/></g></svg>
    </div>
    <div class="foot-cols">
      <div class="fc"><h4>THE BIKE</h4><a href="#build">Configure yours</a><a href="#fit">Made to measure</a><a href="#idea">Why steel</a></div>
      <div class="fc"><h4>THE WORKSHOP</h4><a href="#process">How it’s built</a><a href="#questions">Fair questions</a><a href="#visit">Visit &amp; test rides</a></div>
      <div class="fc"><h4>CONTACT</h4><span>hello@lumencycles.dk</span><span>+45 31 12 84 60</span><span>Refshalevej 163A, København K</span></div>
    </div>
    <div class="legal">
      <span>LUMEN CYCLES APS · CVR 41 22 86 07 · <span id="year"></span></span>
      <span>SET IN IOWAN &amp; SYSTEM MONO · DRAWING № L-1240 · NO TRACKERS, NO COOKIES</span>
    </div>
  </div>
</footer>

<!-- ============ MODAL ============ -->
<div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle" hidden>
  <div class="modal-back" data-close></div>
  <div class="modal-card">
    <button class="modal-x" data-close aria-label="Close">
      <svg width="14" height="14" viewBox="0 0 14 14" stroke="currentColor" stroke-width="1.6"><path d="M1 1l12 12M13 1L1 13"/></svg>
    </button>
    <h3 id="mTitle">Reserve a build slot</h3>
    <div id="mSum"></div>
    <form id="mForm" novalidate>
      <label for="fName">NAME</label><input id="fName" type="text" autocomplete="name">
      <label for="fMail">EMAIL</label><input id="fMail" type="email" autocomplete="email">
      <label for="fNote">WHAT WILL IT CARRY? — OPTIONAL</label>
      <textarea id="fNote" rows="2" placeholder="Two kids and a weekly shop…"></textarea>
      <p class="err" id="fErr" hidden></p>
      <button class="btn w100" type="submit">Request my slot</button>
      <p class="fine">Nothing is charged today. The €500 deposit link arrives with your drawing.</p>
    </form>
    <div id="mDone" hidden>
      <p class="first">Thank you, <b id="dName"></b>.</p>
      <p>Your request <b id="dNo"></b>, dated <span id="dDate"></span>, is on the bench queue. We’ll reply within two working days to book your fitting — nothing is charged until you approve your frame drawing.</p>
      <button class="btn ghost w100" data-close>Back to the drawing</button>
    </div>
  </div>
</div>

<div class="toasts" id="toasts"></div>
<div id="tip" hidden></div>

<script>
'use strict';
/* =====================================================================
   THE DRAWING — one generator, three sheets (hero / fit / configurator).
   All geometry was plotted by hand on a 920×568 grid, ~3.84 mm per pixel
   so the dimension labels are internally consistent with the drawing.
   ===================================================================== */
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;

function bikeSVG(o){
  const uid = o.uid, rider = o.rider ?? 178;
  const saddleMM = Math.round(rider*0.47*8.83);          // saddle height over BB
  const cy  = Math.round(480 - (300+saddleMM)*0.26) + 9; // saddle centre, y
  const yBar = cy - 27;                                   // handlebar height
  const postLen = Math.max(16, Math.min(72, Math.round(259-(cy+8))));
  const postX = (488-0.309*postLen).toFixed(1), postY = (259-0.951*postLen).toFixed(1);
  const saddle = `M434 ${cy} C448 ${cy-12} 470 ${cy-14} 484 ${cy-9} C500 ${cy-12} 516 ${cy-11} 521 ${cy-5} C524 ${cy} 516 ${cy+5} 502 ${cy+4} C482 ${cy+8} 452 ${cy+12} 442 ${cy+10} C434 ${cy+8} 431 ${cy+4} 434 ${cy} Z`;
  const stem = `M392 240 L381 ${yBar+2}`;
  const bar  = `M381 ${yBar+2} C368 ${yBar-3} 352 ${yBar-5} 338 ${yBar-1}`;
  const grip = `M346 ${yBar} L336 ${yBar+1}`;

  /* frame tubes: [path d, hover spec] */
  const T = [
    ['M392 240 L408 292','HEAD TUBE — 1⅛″ · TAPERED'],
    ['M408 292 L535 402','DOWN TUBE — Ø38 × 1.2 CR-MO'],
    ['M396 252 L488 259','TOP TUBE — Ø34 × 1.0 CR-MO'],
    ['M488 259 L535 402','SEAT TUBE — Ø30.4 × 1.2'],
    ['M490 260 L649 381','SEAT STAY — Ø16 CR-MO'],
    ['M537 404 L649 381 M542 408 L654 386','CHAIN STAY — Ø19 · 12 MM AXLE'],
    ['M404 278 C388 322 362 372 332 386','BACKBONE — Ø48 × 1.5 CR-MO'],
    ['M332 386 L152 384','PLATFORM RAIL — Ø22 · ASH FLOOR'],
    ['M142 300 L148 344','STEERING COLUMN — LINKAGE · 575 MM'],
    ['M148 344 C152 368 156 392 160 411 M153 346 C157 370 161 394 165 413','FORK — LOW TRAIL · 45 MM OFFSET'],
  ];
  const edge  = T.map(t=>`<path class="tube-edge" d="${t[0]}"/>`).join('');
  const paint = T.map(t=>`<path class="tube" d="${t[0]}"/>`).join('');
  const parts = T.map(t=>`<g class="part" data-tip="${t[1]}"><path class="p-ink a" d="${t[0]}"/><path class="p-hit" d="${t[0]}"/></g>`).join('');

  const spoke = (cx,cy,r,a)=>{const t=a*Math.PI/180,c=Math.cos(t)*r,s=Math.sin(t)*r;
    return `<line x1="${(cx-c).toFixed(1)}" y1="${(cy-s).toFixed(1)}" x2="${(cx+c).toFixed(1)}" y2="${(cy+s).toFixed(1)}"/>`;};
  const spokesR = [0,30,60,90,120,150].map(a=>spoke(652,383,84,a)).join('');
  const spokesF = [0,36,72,108,144].map(a=>spoke(160,413,56,a)).join('');
  let hatch=''; for(let x=48;x<=880;x+=34) hatch+=`M${x} 480 l-9 11 `;

  /* saddle-height dimension — only on the fitting sheet */
  let saddleDim='';
  if(o.fit){
    const top=Math.round(cy-13), mid=Math.round((top+478)/2);
    saddleDim = `
    <path d="M521 ${top} L756 ${top}"/>
    <path d="M750 ${top} L750 478"/>
    <path d="M750 ${top} l-4 12 h8 z" fill="#6B6353" stroke="none"/>
    <path d="M750 478 l-4 -12 h8 z" fill="#6B6353" stroke="none"/>
    <text class="tt" x="760" y="${mid}" transform="rotate(-90 760 ${mid})" text-anchor="middle">SADDLE ${saddleMM+300} MM</text>`;
  }

  return `<svg class="bike ${o.cls||'blueprint'}" data-cargo="${o.cargo||'box'}" data-assist="${o.assist||'none'}"
    viewBox="0 0 920 568" role="img" aria-label="${o.alt||'Technical side elevation of the Lumen L cargo bike'}">
  <defs>
    <pattern id="dots${uid}" width="26" height="26" patternUnits="userSpaceOnUse"><circle cx="13" cy="13" r=".9" fill="#D6CDB6"/></pattern>
    <pattern id="raw${uid}" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><path d="M0 0 V6" stroke="#8B8271" stroke-width="2.4"/></pattern>
  </defs>
  <rect width="920" height="568" fill="url(#dots${uid})" opacity=".55" pointer-events="none"/>

  <!-- painted frame (hidden on blueprint sheets) -->
  <g class="paint-edge">${edge}</g>
  <g class="paint">${paint}</g>

  <!-- ink: structure & details -->
  <g fill="none" stroke="currentColor" stroke-linecap="round">
    <path class="a" d="M560 340 A101 101 0 0 1 739 333" stroke-width="2.4"/>
    <path class="a" d="M92 388 A72 72 0 0 1 228 388" stroke-width="2.2"/>
    <path class="a" d="M334 392 L154 390" stroke-width="1.2"/>
    <path class="a" d="M390 320 L142 304" stroke-width="2.2"/>
    <path class="a" d="M408 292 Q396 302 390 320" stroke-width="3"/>
    <path class="a" d="${stem}" stroke-width="4"/>
    <path class="a" d="${bar}" stroke-width="3.4"/>
    <path class="a" d="${grip}" stroke-width="6.5"/>
    <path class="a" d="M488 259 L${postX} ${postY}" stroke-width="4.5"/>
    <path class="a" d="M28 480 L892 480" stroke-width="1.7"/>
    <path class="a" d="${hatch}" stroke-width="1"/>
  </g>

  <!-- wheels, parts, cargo, drive: hover-annotated layer -->
  <g class="hits" fill="none" stroke="currentColor" stroke-linecap="round">
    <g class="part wheel" data-tip="FRONT WHEEL — 20 × 2.15 · 36 H">
      <circle class="a" cx="160" cy="413" r="67" stroke-width="3"/>
      <circle class="a" cx="160" cy="413" r="56" stroke-width="1.1"/>
      <g class="spokes" stroke-width=".8">${spokesF}</g>
      <circle class="a" cx="160" cy="413" r="8" stroke-width="2"/>
      <circle class="hitfill" cx="160" cy="413" r="70"/>
    </g>
    <g class="part wheel" data-tip="REAR WHEEL — 700C × 2.35 · 36 H">
      <circle class="a" cx="652" cy="383" r="97" stroke-width="3.2"/>
      <circle class="a" cx="652" cy="383" r="84" stroke-width="1.1"/>
      <g class="spokes" stroke-width=".8">${spokesR}</g>
      <circle class="a" cx="652" cy="383" r="13" stroke-width="2"/>
      <circle class="hitfill" cx="652" cy="383" r="100"/>
    </g>
    ${parts}
    <g class="part" data-tip="SADDLE — LEATHER · STEEL RAILS">
      <path class="saddlefill late" d="${saddle}" fill="currentColor" stroke="none"/>
      <path class="hitfill" d="${s}1" fill="none"/>
      <path class="hitfill" d="${saddle}"/>
    </g>
    <g class="part" data-tip="DRIVE — 48 T · 170 MM CRANKS">
      <circle class="a" cx="535" cy="402" r="25" stroke-width="2.2"/>
      <circle class="a" cx="652" cy="383" r="11" stroke-width="2"/>
      <path class="a" d="M535 377 L652 372 M535 427 L652 394" stroke-width="1.5"/>
      <path class="a" d="M535 402 L563 437" stroke-width="4"/>
      <path class="a" d="M551 443 L579 443" stroke-width="6"/>
      <circle class="hitfill" cx="535" cy="402" r="34"/>
      <path class="p-hit" d="M535 402 L563 437"/>
    </g>
    <g class="part" data-tip="DRAG LINK — ADJUSTABLE ROD ENDS">
      <path class="p-hit" d="M390 320 L142 304"/>
    </g>
    <g class="part g-cargo g-box" data-tip="CARGO BOX — BIRCH PLY · 90 KG">
      <path class="a box" d="M156 386 L156 298 Q156 294 160 294 L332 294 Q336 294 336 298 L336 386 Z"/>
      <path class="a" d="M172 350 L254 350 M178 350 L178 384 M248 350 L248 384" stroke-width="1.2"/>
      <rect class="hitfill" x="156" y="294" width="180" height="92"/>
    </g>
    <g class="part g-cargo g-rail" data-tip="CARGO RAIL — ASH · 90 KG">
      <path class="a" d="M156 344 L336 344" stroke-width="2.6"/>
      <path class="a" d="M164 344 L164 384 M206 344 L206 384 M248 344 L248 384 M290 344 L290 384 M324 344 L324 384" stroke-width="2.2"/>
      <path class="a" d="M156 366 L336 366" stroke-width="1.3"/>
      <rect class="hitfill" x="156" y="344" width="180" height="40"/>
    </g>
    <g class="g-motor">
      <g transform="translate(471 347) rotate(41)">
        <rect x="-42" y="-8.5" width="84" height="17" rx="3.5" fill="#F5F1E6" stroke="currentColor" stroke-width="1.8"/>
        <circle cx="36" cy="0" r="3" fill="#B45A17" stroke="none"/>
      </g>
      <circle cx="535" cy="402" r="15" fill="#F5F1E6" stroke="currentColor" stroke-width="2"/>
      <circle cx="535" cy="402" r="5" fill="#B45A17" stroke="none"/>
    </g>
    <g class="lamp late">
      <circle cx="143" cy="328" r="5.5" stroke="#B45A17" stroke-width="2" fill="none"/>
      <path class="lamp-rays" d="M135 322 L124 317 M133 328 L119 328 M135 334 L124 339" stroke="#B45A17" stroke-width="1.6" fill="none"/>
    </g>
    <circle class="late" cx="401" cy="268" r="3.5" fill="#B45A17" stroke="none"/>
    <circle class="late" cx="390" cy="320" r="3" fill="currentColor" stroke="none"/>
    <circle class="late" cx="142" cy="304" r="3" fill="currentColor" stroke="none"/>
  </g>

  <!-- dimensions, callouts, sheet furniture -->
  <g class="anno" fill="none" stroke="#6B6353" stroke-width=".9">
    <path d="M160 419 L160 506 M652 385 L652 506"/>
    <path d="M160 498 L652 498"/>
    <path d="M160 498 l12 -4 v8 z" fill="#6B6353" stroke="none"/>
    <path d="M652 498 l-12 -4 v8 z" fill="#6B6353" stroke="none"/>
    <path d="M726 321 L768 296 M99 385 L58 364 M246 294 L246 276"/>
    <circle cx="726" cy="321" r="2" fill="#6B6353" stroke="none"/>
    <circle cx="99" cy="385" r="2" fill="#6B6353" stroke="none"/>
    <circle cx="246" cy="294" r="2" fill="#6B6353" stroke="none"/>
    <g stroke="#B7AF99">
      <path d="M8 16 H22 M15 9 V23"/><path d="M898 16 H912 M905 9 V23"/>
      <path d="M8 556 H22 M15 549 V563"/><path d="M898 556 H912 M905 549 V563"/>
    </g>
    <rect x="684" y="510" width="226" height="38" stroke="#6B6353"/>
    <path d="M684 529 H910"/>
    <text class="tt" x="692" y="524">LUMEN CYCLES — KØBENHAVN</text>
    <text class="tt tt-s" x="692" y="542">DWG № L-1240 · SCALE 1:3.8 · SHEET 1/1</text>
    <text class="tt" x="406" y="491" text-anchor="middle">WHEELBASE 1 890 MM</text>
    <text class="tt" x="772" y="292">700C × 2.35</text>
    <text class="tt" x="12" y="358">20 × 2.15</text>
    <text class="tt" x="246" y="266" text-anchor="middle">LOAD 90 KG</text>
    ${saddleDim}
  </g>
</svg>`;
}
/* fix: the stray placeholder hit path is removed below by re-render; keep it simple */
</script>
<script>
/* The generator above emitted one harmless placeholder; rebuild cleanly. */
</script>
<script>
'use strict';
/* ---------- tiny helpers ---------- */
const $ = s=>document.querySelector(s);
const MONTHS=['JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE','JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER'];
const fmtN = v=>String(v).replace(/\B(?=(\d{3})+(?!\d))/g,' ');
const fmtE = v=>'€'+v.toLocaleString('en-US');
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;

function toast(msg){
  const box=$('#toasts'), t=document.createElement('div');
  t.className='toast'; t.textContent=msg; box.append(t);
  requestAnimationFrame(()=>t.classList.add('on'));
  setTimeout(()=>{t.classList.remove('on'); setTimeout(()=>t.remove(),400);},4200);
}
function tweenNum(el,to,fmt){
  const from = el._v ?? to; el._v = to;
  const t0=performance.now(), dur=reduceMotion?0:480;
  (function step(t){
    const k=dur?Math.min(1,(t-t0)/dur):1, e=1-Math.pow(1-k,3);
    el.textContent=fmt(Math.round(from+(to-from)*e));
    if(k<1) requestAnimationFrame(step);
  })(t0);
}
function slotMonth(weeks){
  const d=new Date(); d.setDate(d.getDate()+weeks*7+21); return MONTHS[d.getMonth()];
}

/* ---------- the drawing: self-drawing blueprint ---------- */
function animateDraw(svg){
  if(svg.dataset.done) return;
  svg.dataset.done='1';
  if(reduceMotion){ svg.classList.add('drawn'); return; }
  const els=[...svg.querySelectorAll('.a')];
  let maxEnd=0;
  els.forEach((el,i)=>{
    let L=0; try{L=el.getTotalLength();}catch(e){}
    if(!L) return;
    el.style.strokeDasharray=L+' '+L; el.style.strokeDashoffset=L;
    const dur=Math.min(820,260+L*0.55), delay=140+i*26;
    maxEnd=Math.max(maxEnd,delay+dur);
    const a=el.animate([{strokeDashoffset:L},{strokeDashoffset:0}],
      {duration:dur,delay,easing:'cubic-bezier(.55,.08,.35,1)',fill:'forwards'});
    a.onfinish=()=>{el.style.strokeDasharray='';el.style.strokeDashoffset='';a.cancel();};
  });
  setTimeout(()=>svg.classList.add('drawn'), maxEnd+150);
}
function attachTips(svg){
  const tip=$('#tip');
  svg.addEventListener('pointermove',e=>{
    const g=e.target.closest&&e.target.closest('.part');
    if(g&&g.dataset.tip){
      tip.hidden=false; tip.textContent=g.dataset.tip;
      const x=Math.min(e.clientX+16,innerWidth-tip.offsetWidth-8);
      const y=Math.min(e.clientY+18,innerHeight-34);
      tip.style.left=x+'px'; tip.style.top=y+'px';
    } else tip.hidden=true;
  });
  svg.addEventListener('pointerleave',()=>tip.hidden=true);
}
const drawIO=new IntersectionObserver(es=>{
  es.forEach(en=>{ if(en.isIntersecting){ animateDraw(en.target); drawIO.unobserve(en.target);} });
},{threshold:.25});

/* hero sheet */
const heroWrap=$('#heroBike');
heroWrap.innerHTML=bikeSVG({uid:'H',cargo:'box'});
const heroSvg=heroWrap.querySelector('svg');
attachTips(heroSvg); drawIO.observe(heroSvg);

/* fitting sheet — regenerates with the rider */
const fitWrap=$('#fitBike');
let fitDone=false;
function renderFit(r){
  fitWrap.innerHTML=bikeSVG({uid:'F',cargo:'box',fit:true,rider:r,
    alt:'Side elevation redrawn for your rider height'});
  const s=fitWrap.querySelector('svg');
  if(fitDone) s.classList.add('drawn');
  attachTips(s);
}
renderFit(178);
const fitIO=new IntersectionObserver(es=>{
  es.forEach(en=>{ if(en.isIntersecting&&!fitDone){ animateDraw(fitWrap.querySelector('svg')); fitIO.disconnect(); } });
},{threshold:.2});
fitIO.observe(fitWrap);

/* ---------- fit finder ---------- */
const fH=$('#fH');
function fitCalc(){
  const h=+fH.value;
  const saddle=300+Math.round(h*0.47*8.83);
  const use=document.querySelector('input[name=use]:checked').value;
  const ter=document.querySelector('input[name=ter]:checked').value;
  const load={family:['FAMILY BOX · 2 SEATS','box'],freight:['ASH RAIL · FLATBED','rail'],mixed:['FLATBED · STRAP ANYTHING','flat']}[use];
  const assist=ter==='hills'?['SHIMANO EP6 · YES','ep8']:['NONE NEEDED','none'];
  return {h,saddle,bars:saddle+95,reach:Math.round(h*3.6),load,assist,hills:ter==='hills'};
}
function fitRender(){
  const f=fitCalc();
  $('#fHv').textContent=f.h+' CM';
  tweenNum($('#fSaddle'),f.saddle,fmtN);
  tweenNum($('#fBars'),f.bars,fmtN);
  tweenNum($('#fReach'),f.reach,fmtN);
  $('#fLoad').textContent=f.load[0];
  $('#fAssist').textContent=f.assist[0];
  $('#fSay').textContent=`We’d draw your frame with the saddle at ${fmtN(f.saddle)} mm, the bars 95 mm higher — and the load floor 360 mm off the ground.`;
}
let fitPending=false;
fH.addEventListener('input',()=>{
  $('#fHv').textContent=fH.value+' CM';
  if(!fitPending){ fitPending=true; fitDone=true; requestAnimationFrame(()=>{renderFit(+fH.value); fitPending=false;}); }
  fitRender();
});
document.querySelectorAll('input[name=use],input[name=ter]').forEach(r=>r.addEventListener('change',fitRender));
fitRender();
$('#fApply').addEventListener('click',()=>{
  const f=fitCalc();
  cfg.cargo=f.load[1]; cfg.assist=f.assist[1];
  if(f.hills) cfg.drive='enviolo';
  render();
  document.getElementById('build').scrollIntoView({behavior:reduceMotion?'auto':'smooth'});
  toast('FIT APPLIED — THE SHEET BELOW IS DRAWN TO YOUR MEASURE.');
});

/* ---------- configurator ---------- */
const BASE=3850;
const OPT={
  paint:[
    {id:'kridt',  name:'Kridt',   en:'Chalk white',        hex:'#E7E1CF', add:0},
    {id:'antracit',name:'Antracit',en:'Anthracite',        hex:'#33332E', add:0},
    {id:'mos',    name:'Mos',     en:'Moss green',         hex:'#59684E', add:0},
    {id:'rod',    name:'Rød',     en:'Signal red',         hex:'#A63426', add:0},
    {id:'rav',    name:'Rav',     en:'Hand-rubbed amber',  hex:'#B8661C', add:140},
    {id:'raa',    name:'Rå',      en:'Bare steel, waxed',  hex:null,      add:90},
  ],
  cargo:[
    {id:'flat',   name:'Flatbed',   desc:'Bare platform — strap anything to it', add:0,   kg:0},
    {id:'rail',   name:'Ash rail',  desc:'Tubular rail, low sides, 90 kg load',  add:140, kg:1.6},
    {id:'box',    name:'Family box',desc:'Birch ply, two seats + belts, benches',add:520, kg:9.4},
  ],
  drive:[
    {id:'nexus',  name:'Shimano Nexus 8',    desc:'Hub gear, belt-ready, honest',   add:0,    kg:0},
    {id:'enviolo',name:'Enviolo City',       desc:'Stepless CVT — shift under load',add:340,  kg:0.4},
    {id:'rohloff',name:'Rohloff SPEEDHUB 14',desc:'Fourteen speeds, courier-proof', add:1150, kg:0.3},
  ],
  assist:[
    {id:'none',name:'Legs only',        desc:'The city is flat, after all',        add:0,    kg:0},
    {id:'ep8', name:'Shimano EP6 Cargo',desc:'85 Nm · 540 Wh frame battery',       add:1750, kg:4.6},
  ],
  extras:[
    {id:'dyn', name:'Dynamo + Lumen lamp',desc:'SON hub, wired through the fork',add:240,kg:0.45},
    {id:'lock',name:'Frame lock + chain', desc:'AXA — coffee-stop approved',     add:85, kg:0.9},
    {id:'rain',name:'Rain cover',         desc:'Fits the family box',            add:120,kg:0.4, needs:'box'},
  ],
};
const cfg={paint:'kridt',cargo:'box',drive:'nexus',assist:'none',extras:new Set()};

/* build the option rows once */
$('#swatches').innerHTML=OPT.paint.map(p=>`
  <button type="button" class="sw" data-paint="${p.id}" aria-pressed="${p.id===cfg.paint}">
    <span class="sw-chip ${p.hex?'':'hatchchip'}" style="--c:${p.hex||'transparent'}"></span>
    <span class="sw-name">${p.name.toUpperCase()}</span>
    <span class="sw-add">${p.add?'+'+fmtE(p.add):'INCLUDED'}</span>
  </button>`).join('');
function rows(group,list){
  return list.map(o=>`
  <label class="opt" data-g="${group}" data-id="${o.id}">
    <input type="radio" name="${group}" value="${o.id}" ${cfg[group]===o.id?'checked':''}>
    <span class="dot"></span>
    <span><span class="opt-name">${o.name}</span><span class="opt-desc">${o.desc}</span></span>
    <span class="opt-price">${o.add?'+'+fmtE(o.add):'INCLUDED'}</span>
  </label>`).join('');
}
$('#cargoOpts').innerHTML=rows('cargo',OPT.cargo);
$('#driveOpts').innerHTML=rows('drive',OPT.drive);
$('#assistOpts').innerHTML=rows('assist',OPT.assist);
$('#extraOpts').innerHTML=OPT.extras.map(o=>`
  <label class="opt check" data-g="extras" data-id="${o.id}">
    <input type="checkbox" data-x="${o.id}" ${cfg.extras.has(o.id)?'checked':''}>
    <span class="dot"></span>
    <span><span class="opt-name">${o.name}</span><span class="opt-desc">${o.desc}</span></span>
    <span class="opt-price">${o.add?'+'+fmtE(o.add):''}</span>
  </label>`).join('');

/* configurator sheet */
$('#cfgBike').innerHTML=bikeSVG({uid:'C',cls:'render',cargo:'box',assist:'none',
  alt:'Your configured Lumen L, side elevation'});
const cfgSvg=$('#cfgBike').querySelector('svg');
cfgSvg.classList.add('drawn');

function calc(){
  const p=OPT.paint.find(x=>x.id===cfg.paint), c=OPT.cargo.find(x=>x.id===cfg.cargo);
  const d=OPT.drive.find(x=>x.id===cfg.drive), a=OPT.assist.find(x=>x.id===cfg.assist);
  let ex=0,kg=0;
  OPT.extras.forEach(x=>{if(cfg.extras.has(x.id)){ex+=x.add;kg+=x.kg;}});
  return {p,c,d,a,ex,
    total:BASE+p.add+c.add+d.add+a.add+ex,
    weight:21.4+c.kg+d.kg+a.kg+kg,
    weeks:9+(cfg.cargo==='box'?1:0)+(cfg.assist!=='none'?3:0)+(cfg.drive==='rohloff'?2:0)};
}
function render(){
  const k=calc();
  const hex=k.p.hex?k.p.hex:'url(#rawC)';
  cfgSvg.style.setProperty('--paint',hex);
  cfgSvg.dataset.cargo=cfg.cargo; cfgSvg.dataset.assist=cfg.assist;
  $('#capRight').textContent='PAINT · '+k.p.name.toUpperCase();
  document.querySelectorAll('.sw').forEach(b=>b.setAttribute('aria-pressed',b.dataset.paint===cfg.paint));
  document.querySelectorAll('.opt[data-g]').forEach(l=>{
    const inp=l.querySelector('input');
    if(inp.type==='radio'){ inp.checked=cfg[l.dataset.g]===inp.value; l.classList.toggle('sel',inp.checked); }
    else { l.classList.toggle('sel',inp.checked);
      const ex=OPT.extras.find(x=>x.id===l.dataset.id);
      const off=ex.needs&&ex.needs!==cfg.cargo;
      l.classList.toggle('off',off);
      inp.disabled=off;
      if(off&&inp.checked){inp.checked=false;cfg.extras.delete(ex.id);}
    }
  });
  tweenNum($('#cfgPrice'),k.total,fmtE);
  $('#cfgWeight').textContent='≈ '+k.weight.toFixed(1)+' KG';
  $('#cfgLead').textContent='~ '+k.weeks+' WEEKS';
  $('#cfgSlot').textContent=slotMonth(k.weeks);
}
$('#swatches').addEventListener('click',e=>{
  const b=e.target.closest('.sw'); if(!b)return;
  cfg.paint=b.dataset.paint; render();
});
document.querySelectorAll('.opt[data-g]').forEach(l=>{
  l.addEventListener('change',()=>{
    const inp=l.querySelector('input');
    if(inp.type==='radio') cfg[l.dataset.g]=inp.value;
    else inp.checked?cfg.extras.add(inp.dataset.x):cfg.extras.delete(inp.dataset.x);
    render();
  });
});
render();
$('#slotHero').textContent=slotMonth(10);

/* ---------- modal ---------- */
const modal=$('#modal'), mForm=$('#mForm'), mDone=$('#mDone'), fErr=$('#fErr');
const srow=(k,v)=>`<div class="srow"><span>${k}</span><b>${v}</b></div>`;
function sumRows(){
  const k=calc();
  const ex=[...cfg.extras].map(id=>OPT.extras.find(x=>x.id===id).name);
  return srow('PAINT',k.p.name)+srow('FRONT CARGO',k.c.name)+srow('DRIVE',k.d.name)
    +srow('ASSIST',k.a.name)+srow('EXTRAS',ex.length?ex.join(' · '):'—')
    +srow('CONFIGURED',fmtE(k.total))+srow('DEPOSIT','€500')+srow('EST. LEAD',k.weeks+' WEEKS');
}
function openModal(){
  mSum.innerHTML=sumRows(); mForm.hidden=false; mDone.hidden=true; fErr.hidden=true;
  modal.hidden=false; document.body.classList.add('lock');
  setTimeout(()=>$('#fName').focus(),60);
}
function closeModal(){ modal.hidden=true; document.body.classList.remove('lock'); }
document.querySelectorAll('[data-open-modal]').forEach(b=>b.addEventListener('click',openModal));
$('#cfgReserve').addEventListener('click',openModal);
modal.addEventListener('click',e=>{ if(e.target.closest('[data-close]')) closeModal(); });
document.addEventListener('keydown',e=>{
  if(e.key==='Escape'){ if(!modal.hidden) closeModal(); else if(menuEl.classList.contains('open')) closeMenu(); }
});
mForm.addEventListener('submit',e=>{
  e.preventDefault();
  const name=$('#fName').value.trim(), mail=$('#fMail').value.trim();
  const err=m=>{fErr.textContent=m;fErr.hidden=false;};
  if(!name) return err('Your name, please — we need to know who we’re drawing for.');
  if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(mail)) return err('That email doesn’t look right — we reply to it, so it matters.');
  fErr.hidden=true; mForm.hidden=true; mDone.hidden=false;
  $('#dName').textContent=name.split(' ')[0];
  $('#dNo').textContent='№ L-'+(1243+Math.floor(Math.random()*38));
  const d=new Date();
  $('#dDate').textContent=d.getDate()+' '+MONTHS[d.getMonth()].slice(0,3)+' '+d.getFullYear();
  toast('SLOT REQUEST SENT — WE REPLY WITHIN TWO WORKING DAYS.');
});

/* ---------- faq ---------- */
document.querySelectorAll('.faq-item').forEach(item=>{
  const q=item.querySelector('.faq-q');
  q.addEventListener('click',()=>{
    const open=item.classList.toggle('open');
    q.setAttribute('aria-expanded',open);
  });
});

/* ---------- misc chrome ---------- */
const head=$('#siteHead');
addEventListener('scroll',()=>head.classList.toggle('scrolled',scrollY>8),{passive:true});
const track=$('#tickTrack');
for(let i=0;i<3;i++) track.appendChild(track.firstElementChild.cloneNode(true));
$('#copyMail').addEventListener('click',async()=>{
  const mail='hello@

Compare with

GLM 5.3 Prime: other attempts