• Updated 31 Oct, 14:35 UTC
  • Next run 1 Nov, 05:17 UTCNext 1 Nov 05:17
  • Last 7 days: 4 new models · 448 runs7 d: 4 models · 448 runs
Landing page for a cargo bike workshop

GLM 5.3 FlashZ.ai

Network offOpen alone (new tab)

Run details

Model
z-ai/glm-5.3-flash
Route
OpenRouter API, one user message
Served by
OpenInference
Effort
max, OpenRouter's default for this model
Limits
60 min · 131,072 output tokens (the model's limit)
Date
31 Oct 2026
Time
22 min 43 s
Size
76 kB
Tokens
113 in, 98,011 out (71,774 of them thinking)
List price
$0.014 at today's price ($0.016 in / $0.148 out per M)
Paid
$0.003

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

76 kB 1,299 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 — Custom cargo bikes, built to order in Copenhagen</title>
<meta name="description" content="Lumen Cycles designs, brazes and paints custom cargo bikes to order in Copenhagen. Three frames, one workshop, a six-to-eight-week build.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4' fill='%23D9541E'/%3E%3Cg stroke='%23D9541E' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.2 2.2M16.9 16.9l2.2 2.2M19.1 4.9l-2.2 2.2M7.1 16.9l-2.2 2.2'/%3E%3C/g%3E%3C/svg%3E">
<style>
/* ---------- foundations ---------- */
:root{
  --paper:#F3EDE2; --paper2:#EAE2D1; --ink:#211D16; --ink2:#4B4536; --muted:#8B7F6C;
  --line:rgba(33,29,22,.16); --accent:#D9541E; --accent-deep:#B5430F;
  --dark:#171310; --dark-line:rgba(241,234,218,.18); --dark-text:#F1EAD9; --dark-mut:rgba(241,234,218,.68);
  --serif:"Iowan Old Style","Palatino Linotype","Book Antiqua",Palatino,Georgia,serif;
  --mono:ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,"Liberation Mono",monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;scroll-padding-top:96px;-webkit-text-size-adjust:100%}
body{background:var(--paper);color:var(--ink2);font:400 17px/1.68 var(--serif);-webkit-font-smoothing:antialiased}
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:250;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E")}
::selection{background:var(--accent);color:var(--paper)}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit}
svg{display:block}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.wrap{max-width:1260px;margin:0 auto;padding-inline:clamp(20px,4vw,48px)}
.mono{font-family:var(--mono)}
h1,h2,h3{font-family:var(--serif);color:var(--ink);font-weight:400;letter-spacing:-.01em}

/* kickers & headings */
.kicker{display:flex;align-items:center;gap:10px;font:600 11px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--ink);margin-bottom:24px}
.kicker .lamp-ic{width:15px;height:15px;color:var(--accent);flex:none}
h2{font-size:clamp(2rem,3.8vw,3.2rem);line-height:1.08;max-width:22ch}
.lead{margin-top:18px;max-width:58ch;font-size:1.05rem}
.sec{padding:clamp(76px,9vw,124px) 0}
.sec-head{margin-bottom:clamp(42px,6vw,68px)}

/* buttons & links */
.btn{display:inline-flex;align-items:center;gap:10px;font:600 12px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
  padding:17px 28px;background:var(--ink);color:var(--paper);border:1px solid var(--ink);cursor:pointer;
  transition:background .18s,color .18s,border-color .18s,transform .18s}
.btn:hover{background:var(--accent);border-color:var(--accent);color:#161008;transform:translateY(-1px)}
.btn-sm{padding:12px 20px}
.btn-accent{background:var(--accent);border-color:var(--accent);color:#161008}
.btn-accent:hover{background:var(--dark-text);border-color:var(--dark-text);color:var(--dark)}
.dark .btn-accent{background:var(--accent);color:#161008}
.dark .btn-accent:hover{background:var(--dark-text);color:var(--dark)}
.link-arrow{display:inline-flex;align-items:center;gap:9px;font:600 12px/1 var(--mono);letter-spacing:.15em;text-transform:uppercase;
  color:var(--ink);background:none;border:0;cursor:pointer;padding:4px 0}
.link-arrow .arr{transition:transform .2s}
.link-arrow:hover{color:var(--accent)}
.link-arrow:hover .arr{transform:translateX(5px)}

/* reveal on scroll */
.js .reveal{opacity:0;transform:translateY(20px);transition:opacity .7s ease,transform .7s ease;transition-delay:var(--d,0s)}
.js .reveal.in{opacity:1;transform:none}

/* ---------- header ---------- */
.site-head{position:fixed;top:0;left:0;right:0;z-index:60;transition:background .3s,box-shadow .3s}
.site-head.scrolled{background:rgba(243,237,226,.93);backdrop-filter:blur(10px);box-shadow:0 1px 0 var(--line)}
.site-head .bar{display:flex;align-items:center;justify-content:space-between;gap:20px;max-width:1360px;margin:0 auto;padding:16px clamp(20px,4vw,44px)}
.brand{display:flex;align-items:center;gap:11px}
.brand .lamp-ic{width:21px;height:21px;color:var(--accent);transition:transform .5s cubic-bezier(.6,0,.2,1)}
.brand:hover .lamp-ic{transform:rotate(45deg)}
.brand-name{font:600 1.3rem/1 var(--serif);color:var(--ink);letter-spacing:-.01em}
.brand-name small{font:600 9.5px var(--mono);letter-spacing:.3em;color:var(--muted);display:block;margin-top:3px}
.site-nav{display:flex;gap:clamp(18px,2.6vw,36px);align-items:center}
.site-nav a{font:600 11.5px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ink2);padding:6px 0;position:relative}
.site-nav a::after{content:"";position:absolute;left:0;bottom:0;height:1.5px;width:100%;background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .25s}
.site-nav a:hover{color:var(--ink)}
.site-nav a:hover::after,.site-nav a.cur::after{transform:scaleX(1)}
.site-nav a.cur{color:var(--ink)}
.head-cta{display:inline-flex}
.menu-btn{display:none;width:44px;height:44px;background:none;border:1px solid var(--line);cursor:pointer;position:relative}
.menu-btn span{position:absolute;left:12px;right:12px;height:1.6px;background:var(--ink);transition:.25s}
.menu-btn span:nth-child(1){top:17px}.menu-btn span:nth-child(2){top:26px}
.menu-btn[aria-expanded="true"] span:nth-child(1){top:21px;transform:rotate(45deg)}
.menu-btn[aria-expanded="true"] span:nth-child(2){top:21px;transform:rotate(-45deg)}
.mnav{position:fixed;inset:0;z-index:70;background:var(--paper);display:flex;flex-direction:column;padding:22px clamp(20px,6vw,40px) 40px;
  transform:translateY(-102%);transition:transform .5s cubic-bezier(.7,0,.2,1)}
.mnav.open{transform:none}
.mnav-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8vh}
.mnav-close{width:44px;height:44px;background:none;border:1px solid var(--line);cursor:pointer;font-family:var(--mono);font-size:16px}
.mnav a{font:400 clamp(1.9rem,7vw,2.6rem)/1.25 var(--serif);color:var(--ink);padding:12px 0;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline}
.mnav a small{font:500 10px var(--mono);letter-spacing:.2em;color:var(--muted)}
.mnav-foot{margin-top:auto;font:500 12px/2 var(--mono);letter-spacing:.08em;color:var(--ink2)}

/* ---------- hero ---------- */
.hero{padding:clamp(130px,16vh,180px) 0 clamp(40px,6vh,70px)}
.hero-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(36px,5vw,76px);align-items:center}
.hero h1{font-size:clamp(2.7rem,5.4vw,4.9rem);line-height:1.04;margin-bottom:26px}
.hero h1 em{font-style:italic;color:var(--accent)}
.hero .sub{max-width:46ch;margin-bottom:34px}
.cta-row{display:flex;align-items:center;gap:28px;flex-wrap:wrap}
.slot{display:flex;align-items:center;gap:10px;margin-top:26px;font:500 11px/1 var(--mono);letter-spacing:.18em;color:var(--ink2)}
.slot::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--accent);animation:blink 2.6s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
.hero-copy>*{opacity:0}
.js .hero-copy>*{animation:rise .8s cubic-bezier(.3,.6,.2,1) both}
.js .hero-copy>*:nth-child(2){animation-delay:.12s}
.js .hero-copy>*:nth-child(3){animation-delay:.24s}
.js .hero-copy>*:nth-child(4){animation-delay:.36s}
.js .hero-copy>*:nth-child(5){animation-delay:.48s}
@keyframes rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}

/* drawing sheets */
.sheet{--frame:var(--accent);--box:#E5C893;position:relative;border:1px solid var(--line);background:var(--paper);
  background-image:radial-gradient(rgba(33,29,22,.13) 1px,transparent 1.35px);background-size:22px 22px}
.sheet::after{content:"";position:absolute;inset:9px;border:1px dashed rgba(33,29,22,.22);pointer-events:none}
.corner-note{position:absolute;font:500 9.5px/1 var(--mono);letter-spacing:.18em;color:var(--muted);z-index:2}
.corner-note.tl{top:16px;left:18px}
.corner-note.br{bottom:16px;right:18px}
.sheet svg{width:100%;height:auto;position:relative;z-index:1}
.fig{margin-top:14px;font:500 10.5px/1.6 var(--mono);letter-spacing:.16em;color:var(--muted);text-transform:uppercase}

/* bikes (shared drawing styles) */
.bike text{font-family:var(--mono);font-size:13.5px;letter-spacing:.06em;fill:var(--ink)}
.bike .tube{stroke:var(--frame);stroke-width:7;stroke-linecap:round;stroke-linejoin:round;fill:none;transition:stroke .25s}
.bike .thin{stroke-width:5}
.bike .ink{stroke:var(--ink);fill:none;stroke-linecap:round;stroke-linejoin:round;transition:stroke .25s}
.bike .tire{stroke-width:10}
.bike .rim{stroke-width:2.5;opacity:.85}
.bike .spoke{stroke-width:1.4;opacity:.55}
.bike .hub{fill:var(--ink);transition:fill .25s}
.bike .chain{stroke-width:2.4;opacity:.9}
.bike.belted .chain{stroke-width:5.5;opacity:1}
.bike .saddle{fill:var(--ink)}
.bike .boxy{fill:var(--box);stroke:var(--ink);stroke-width:3.5;transition:fill .25s,stroke .25s}
.bike .slat{stroke:var(--ink);stroke-width:1.5;opacity:.45}
.bike .boxtext{font-size:15px;letter-spacing:.24em}
.bike .lamp{fill:var(--accent)}
.bike .ray{stroke:var(--accent);stroke-width:2.4;stroke-linecap:round;animation:flicker 7s infinite}
@keyframes flicker{0%,90%,100%{opacity:1}93%{opacity:.25}96%{opacity:.85}}
.bike .shadow{fill:rgba(33,29,22,.09)}
.bike .ground{stroke:var(--ink);stroke-width:1.6;stroke-dasharray:12 9;opacity:.35}
.bike .canopy{stroke:var(--accent);stroke-width:4.5;stroke-linecap:round;fill:var(--accent);fill-opacity:.09}
.bike .far{opacity:.32}
.bike .callout{cursor:default}
.bike .callout polyline{stroke:var(--ink);stroke-width:1.3;fill:none;opacity:.7;transition:.2s}
.bike .callout circle{fill:var(--accent)}
.bike .callout text{font-size:13.5px;letter-spacing:.08em;fill:var(--ink);transition:fill .2s}
.bike .callout.hot text{fill:var(--accent)}
.bike .callout.hot polyline{stroke:var(--accent);opacity:1}
.bike [data-part].hot :is(.tube,.ink,.boxy){stroke:var(--accent)}
.bike [data-part].hot .hub{fill:var(--accent)}
@media (max-width:760px){.callout{display:none}}

/* ---------- ticker ---------- */
.ticker{overflow:hidden;border-block:1px solid var(--line);background:var(--paper)}
.ticker-track{display:flex;width:max-content;animation:tick 52s linear infinite}
.ticker:hover .ticker-track{animation-play-state:paused}
.ticker-seg{white-space:nowrap;font:600 11.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink);padding:15px 0}
.ticker-seg i{font-style:normal;color:var(--accent);padding:0 26px}
@keyframes tick{to{transform:translateX(-50%)}}

/* ---------- machines ---------- */
.mach-grid{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:clamp(30px,4vw,64px);align-items:start}
.model-nav{position:sticky;top:108px}
.model-tab{display:grid;grid-template-columns:auto 1fr;column-gap:18px;text-align:left;width:100%;padding:24px 16px 24px 8px;
  background:none;border:0;border-bottom:1px solid var(--line);border-left:3px solid transparent;cursor:pointer;transition:.2s}
.model-tab .mn-no{grid-row:1/3;align-self:center;font:500 11px var(--mono);letter-spacing:.14em;color:var(--muted);transition:color .2s}
.model-tab .mn-name{font:400 clamp(1.7rem,2.4vw,2.15rem)/1.1 var(--serif);color:var(--ink);transition:color .2s}
.model-tab .mn-sub{font:500 10.5px var(--mono);letter-spacing:.16em;color:var(--muted);margin-top:8px}
.model-tab:hover .mn-name{color:var(--accent)}
.model-tab.on{border-left-color:var(--accent);background:rgba(33,29,22,.035)}
.model-tab.on .mn-no{color:var(--accent)}
.machine-main .sheet{margin-bottom:0}
.machine-lower{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);gap:clamp(28px,4vw,54px);margin-top:clamp(28px,3.5vw,46px)}
#machineBody{transition:opacity .26s,transform .26s}
#machineBody.swap{opacity:0;transform:translateY(8px)}
.specs{border-bottom:1px solid var(--line)}
.spec{display:flex;justify-content:space-between;gap:16px;padding:13px 2px;border-top:1px solid var(--line);
  font:500 12px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.spec b{color:var(--ink);font-weight:600;text-align:right}
.machine-copy h3{font-size:clamp(1.5rem,2.2vw,1.9rem)}
.machine-copy h3 span{font-style:italic;color:var(--muted)}
.machine-copy p{margin:14px 0 24px;max-width:52ch}

/* ---------- configurator (dark) ---------- */
.dark{background:var(--dark);color:var(--dark-mut)}
.dark .kicker,.dark h2{color:var(--dark-text)}
.dark .lead{color:var(--dark-mut)}
.cfg-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(30px,4.5vw,72px);align-items:start}
.opt-label{font:600 10.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--dark-mut);margin-bottom:14px}
.opt-label b{color:var(--dark-text);font-weight:600}
.cfg-group{margin-bottom:34px}
.segs{display:flex;flex-wrap:wrap;gap:10px}
.segs button{display:inline-flex;align-items:center;gap:10px;border:1px solid var(--dark-line);background:none;color:var(--dark-mut);
  font:600 11px/1 var(--mono);letter-spacing:.14em;padding:12px 16px;cursor:pointer;transition:.18s}
.segs button i{width:13px;height:13px;border-radius:2px;display:inline-block;border:1px solid rgba(0,0,0,.25)}
.segs button:hover{border-color:var(--dark-text);color:var(--dark-text)}
.segs button.on{border-color:var(--dark-text);background:rgba(241,234,218,.08);color:var(--dark-text)}
.swatches{display:flex;align-items:center;gap:13px;flex-wrap:wrap}
.swatch{width:34px;height:34px;border-radius:50%;border:1px solid rgba(241,234,218,.35);cursor:pointer;transition:transform .15s;padding:0}
.swatch:hover{transform:scale(1.14)}
.swatch.on{box-shadow:0 0 0 3px var(--dark),0 0 0 4.5px var(--dark-text)}
.paint-name{font:500 11px var(--mono);letter-spacing:.16em;color:var(--dark-text);text-transform:uppercase;margin-left:4px}
.chk-list{border-top:1px solid var(--dark-line)}
.chk{display:flex;align-items:center;gap:14px;padding:13px 2px;border-bottom:1px solid var(--dark-line);cursor:pointer;position:relative}
.chk input{position:absolute;opacity:0;pointer-events:none}
.chk .box{width:18px;height:18px;border:1.5px solid var(--dark-text);flex:none;position:relative;transition:.18s}
.chk .box::after{content:"";position:absolute;left:4.5px;top:1px;width:5px;height:9.5px;border-right:2.2px solid #171310;border-bottom:2.2px solid #171310;transform:rotate(45deg);opacity:0}
.chk input:checked ~ .box{background:var(--accent);border-color:var(--accent)}
.chk input:checked ~ .box::after{opacity:1}
.chk input:focus-visible ~ .box{outline:2px solid var(--accent);outline-offset:2px}
.chk-n{font:400 15px var(--serif);color:var(--dark-text);flex:1}
.chk-p{font:500 11px var(--mono);letter-spacing:.1em;color:var(--dark-mut)}
.dark .sheet{border-color:rgba(241,234,218,.14);box-shadow:0 28px 60px -22px rgba(0,0,0,.6)}
.price-box{margin-top:6px;border-top:1px solid var(--dark-line);padding-top:26px;display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:22px}
.price-big{font:600 clamp(2.1rem,3.4vw,2.9rem)/1 var(--mono);color:var(--dark-text);letter-spacing:-.01em}
.price-sub{font:500 10.5px var(--mono);letter-spacing:.18em;color:var(--dark-mut);margin-top:10px;text-transform:uppercase}
.serial{margin-top:20px;font:500 10.5px/1.8 var(--mono);letter-spacing:.16em;color:var(--dark-mut);text-transform:uppercase}
.serial b{color:var(--accent);font-weight:600}

/* ---------- process ---------- */
.proc-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,4fr);gap:clamp(36px,6vw,96px);align-items:start}
.steps{list-style:none;border-top:1px solid var(--line)}
.step{display:grid;grid-template-columns:96px 1fr auto;gap:26px;padding:30px 8px;border-bottom:1px solid var(--line);position:relative}
.step::after{content:"";position:absolute;left:0;top:-1px;height:1px;width:100%;background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .5s cubic-bezier(.6,0,.2,1)}
.step:hover::after{transform:scaleX(1)}
.step-no{font:600 13px var(--mono);letter-spacing:.18em;color:var(--muted);padding-top:7px;transition:color .25s}
.step:hover .step-no{color:var(--accent)}
.step h3{font-size:1.45rem;margin-bottom:7px}
.step p{max-width:56ch}
.step-meta{font:500 10.5px var(--mono);letter-spacing:.16em;color:var(--muted);padding-top:9px;white-space:nowrap}
.proc-aside{position:sticky;top:120px;border-left:2px solid var(--accent);padding-left:clamp(20px,2.5vw,34px)}
.proc-aside blockquote{font:italic 400 1.45rem/1.45 var(--serif);color:var(--ink)}
.proc-aside figcaption{margin-top:18px;font:500 10.5px/1.8 var(--mono);letter-spacing:.18em;color:var(--muted);text-transform:uppercase}
.proc-aside .warranty{margin-top:30px;padding-top:22px;border-top:1px solid var(--line);font-size:.95rem}

/* ---------- voices ---------- */
.voices{background:var(--paper2);border-block:1px solid var(--line)}
.q-wrap{max-width:880px;margin:0 auto;position:relative;padding:0 clamp(0px,4vw,40px)}
.qmark{position:absolute;top:-30px;left:-4px;font:italic 400 8rem/1 var(--serif);color:var(--accent);user-select:none;pointer-events:none}
.q-box{transition:opacity .26s,transform .26s}
.q-box.fade{opacity:0;transform:translateY(10px)}
.q-box blockquote{font:italic 400 clamp(1.45rem,3.2vw,2.3rem)/1.42 var(--serif);color:var(--ink);letter-spacing:-.01em}
.q-attr{margin-top:26px;font:500 11.5px/1.6 var(--mono);letter-spacing:.18em;color:var(--ink2);text-transform:uppercase}
.q-controls{display:flex;align-items:center;gap:14px;margin-top:40px}
.q-btn{width:46px;height:46px;display:grid;place-items:center;background:none;border:1px solid var(--ink);cursor:pointer;color:var(--ink);transition:.18s}
.q-btn svg{width:16px;height:16px}
.q-btn:hover{background:var(--ink);color:var(--paper)}
.q-idx{margin-left:auto;font:500 11.5px var(--mono);letter-spacing:.2em;color:var(--muted)}

/* ---------- visit ---------- */
.visit-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,72px);align-items:start}
.addr{font-size:clamp(1.35rem,2vw,1.7rem);line-height:1.35;color:var(--ink)}
.visit-hours{margin:28px 0;border-bottom:1px solid var(--line)}
.visit-note{margin:26px 0 30px;max-width:44ch;padding:18px 20px;border:1px solid var(--line);background:var(--paper2);font-size:.95rem}
.contact-line{display:flex;flex-wrap:wrap;gap:8px 28px;margin-bottom:30px}
.contact-line a{font:600 12px/1 var(--mono);letter-spacing:.12em;color:var(--ink);border-bottom:1px solid var(--accent);padding-bottom:3px;transition:color .2s}
.contact-line a:hover{color:var(--accent)}
.map-panel{border:1px solid var(--line);background:var(--paper2)}
.map{width:100%;height:auto}
.map-serif{font:italic 400 17px var(--serif);fill:#6D7C77}
.map-mono{font-family:var(--mono);font-size:10px;letter-spacing:.28em;fill:var(--ink2)}
.map-mono.big{font-size:12px;letter-spacing:.32em;fill:var(--muted)}
.map .halo{transform-box:fill-box;transform-origin:center;animation:halo 2.8s ease-out infinite}
@keyframes halo{0%{transform:scale(.5);opacity:.9}80%{transform:scale(3.4);opacity:0}100%{opacity:0}}

/* ---------- footer ---------- */
.site-foot{background:var(--dark);color:var(--dark-mut);padding:clamp(64px,8vw,100px) 0 34px}
.foot-top{display:flex;flex-wrap:wrap;align-items:baseline;gap:14px 40px;margin-bottom:clamp(44px,6vw,72px)}
.foot-mark{display:flex;align-items:center;gap:14px;font:italic 400 clamp(2.2rem,5vw,3.6rem)/1 var(--serif);color:var(--dark-text)}
.foot-mark .lamp-ic{width:30px;height:30px;color:var(--accent);transition:transform .5s cubic-bezier(.6,0,.2,1)}
.foot-mark:hover .lamp-ic{transform:rotate(45deg)}
.foot-tag{font:italic 400 1.05rem/1.5 var(--serif);max-width:34ch}
.foot-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1fr);gap:clamp(28px,4vw,60px);padding-bottom:clamp(40px,5vw,64px)}
.foot-grid h4{font:600 10.5px/1 var(--mono);letter-spacing:.24em;text-transform:uppercase;color:var(--accent);margin-bottom:18px}
.foot-grid p,.foot-grid a{font:400 15px/2 var(--serif);color:var(--dark-mut)}
.foot-grid a{display:block;width:fit-content;border-bottom:1px solid transparent;transition:.2s}
.foot-grid a:hover{color:var(--dark-text);border-bottom-color:var(--accent)}
.foot-base{border-top:1px solid var(--dark-line);padding-top:24px;display:flex;flex-wrap:wrap;gap:8px 36px;
  font:500 10.5px/1.8 var(--mono);letter-spacing:.12em;color:rgba(241,234,218,.55);text-transform:uppercase}

/* ---------- modal & toast ---------- */
.overlay{position:fixed;inset:0;z-index:120;background:rgba(18,14,9,.58);display:grid;place-items:center;padding:20px;opacity:0;transition:opacity .26s}
.overlay.open{opacity:1}
.overlay[hidden]{display:none}
.modal{background:var(--paper);width:min(540px,100%);max-height:88vh;overflow:auto;border:1px solid var(--ink);
  padding:clamp(26px,5vw,42px);position:relative;transform:translateY(16px);transition:transform .26s}
.overlay.open .modal{transform:none}
.modal-x{position:absolute;top:16px;right:16px;width:40px;height:40px;display:grid;place-items:center;background:none;border:1px solid var(--line);cursor:pointer;transition:.18s}
.modal-x:hover{background:var(--ink);color:var(--paper)}
.modal-x svg{width:14px;height:14px}
.modal h3{font-size:clamp(1.6rem,3vw,2.1rem);margin:8px 0 22px}
.m-summary{border:1px dashed rgba(33,29,22,.35);padding:14px 16px;font:500 11px/1.9 var(--mono);letter-spacing:.12em;color:var(--ink2);text-transform:uppercase;margin-bottom:26px}
.field{display:block;margin-bottom:18px}
.f-label{display:flex;justify-content:space-between;align-items:baseline;font:600 10px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.f-label .opt{font-weight:500;letter-spacing:.12em}
.field input,.field textarea{width:100%;background:#FAF6EC;border:1px solid rgba(33,29,22,.35);padding:13px 14px;
  font:400 16px/1.5 var(--serif);color:var(--ink);border-radius:0;transition:border-color .18s}
.field textarea{min-height:110px;resize:vertical}
.field input:focus,.field textarea:focus{outline:none;border-color:var(--accent)}
.err{display:none;font:500 10.5px var(--mono);letter-spacing:.1em;color:var(--accent);margin-top:7px;text-transform:uppercase}
.field.bad .err{display:block}
.field.bad input{border-color:var(--accent)}
.form-note{margin-top:16px;font:500 10px/1.6 var(--mono);letter-spacing:.14em;color:var(--muted);text-transform:uppercase}
.m-success{text-align:center;padding:26px 6px 8px}
.m-success .lamp-ic{width:44px;height:44px;color:var(--accent);margin:0 auto 20px}
.m-success h3{margin-bottom:12px}
.m-success p{max-width:38ch;margin:0 auto 26px}

.toast{position:fixed;left:50%;bottom:28px;transform:translate(-50%,18px);z-index:140;display:flex;align-items:center;gap:12px;
  background:var(--ink);color:var(--paper);font:500 11.5px/1.5 var(--mono);letter-spacing:.1em;padding:15px 22px;opacity:0;transition:.3s;pointer-events:none}
.toast.show{opacity:1;transform:translate(-50%,0)}
.toast .lamp-ic{width:15px;height:15px;color:var(--accent);flex:none}

/* ---------- responsive ---------- */
@media (max-width:1020px){
  .hero-grid,.mach-grid,.cfg-grid,.proc-grid,.visit-grid{grid-template-columns:1fr}
  .hero{padding-top:120px}
  .model-nav{position:static;display:flex;overflow-x:auto;border-block:1px solid var(--line);margin-bottom:34px}
  .model-tab{border-bottom:0;white-space:nowrap;padding:18px 22px;min-width:max-content}
  .machine-lower{grid-template-columns:1fr}
  .proc-aside{position:static;margin-top:44px}
  .foot-grid{grid-template-columns:1fr 1fr}
}
@media (max-width:860px){
  .site-nav,.head-cta{display:none}
  .menu-btn{display:block}
}
@media (max-width:640px){
  .step{grid-template-columns:52px 1fr;gap:16px}
  .step-meta{grid-column:2;padding-top:0;margin-top:6px}
  .foot-grid{grid-template-columns:1fr}
  .price-box{flex-direction:column;align-items:stretch}
  .price-box .btn{justify-content:center}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .ticker-track,.bike .ray,.map .halo,.slot::before{animation:none}
  .js .reveal,.js .hero-copy>*{opacity:1;transform:none;animation:none;transition:none}
}
</style>
</head>
<body>

<!-- ======= header ======= -->
<header class="site-head" id="siteHead">
  <div class="bar">
    <a class="brand" href="#top" aria-label="Lumen Cycles — home">
      <svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>
      <span class="brand-name">Lumen<small>CYCLES · KBH</small></span>
    </a>
    <nav class="site-nav" aria-label="Primary">
      <a href="#machines">Machines</a>
      <a href="#build">Build</a>
      <a href="#process">Process</a>
      <a href="#visit">Visit</a>
    </nav>
    <button class="btn btn-sm head-cta" type="button" data-open="visit">Book a visit</button>
    <button class="menu-btn" type="button" id="menuBtn" aria-expanded="false" aria-controls="mnav" aria-label="Menu"><span></span><span></span></button>
  </div>
</header>

<div class="mnav" id="mnav" aria-hidden="true">
  <div class="mnav-top">
    <span class="brand-name">Lumen<small>CYCLES · KBH</small></span>
    <button class="mnav-close" id="mnavClose" aria-label="Close menu">✕</button>
  </div>
  <a href="#machines">Machines <small>01</small></a>
  <a href="#build">Build <small>02</small></a>
  <a href="#process">Process <small>03</small></a>
  <a href="#visit">Visit <small>05</small></a>
  <p class="mnav-foot">Refshalevej 163A · 1432 København K<br>hello@lumencycles.dk · +45 31 62 88 40</p>
</div>

<main id="top">

  <!-- ======= hero ======= -->
  <section class="hero">
    <div class="wrap hero-grid">
      <div class="hero-copy">
        <p class="kicker"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>Custom cargo bikes · est. 2016 · København</p>
        <h1>A bike drawn around <em>your days</em>.</h1>
        <p class="sub">Lumen builds cargo bikes to order in an old shipyard hall on Refshaleøen. Geometry, box and paint are decided with you — then brazed by hand and ridden 200 km through Copenhagen before handover.</p>
        <div class="cta-row">
          <a class="btn" href="#build">Configure your build</a>
          <a class="link-arrow" href="#visit">Book a workshop visit <span class="arr">→</span></a>
        </div>
        <p class="slot">NEXT FRAME SLOT · EARLY MARCH 2026</p>
      </div>
      <div class="hero-stage">
        <div class="sheet">
          <span class="corner-note tl mono">LUMEN CYCLES · KØBENHAVN</span>
          <span class="corner-note br mono">REF. L-2024-117 · REV. C</span>
          <div id="heroStage"></div>
        </div>
        <p class="fig">FIG. 00 — FRAKT · FRONT LOADER · DRAWN AT THE WORKSHOP, SCALE 1:8 · HOVER THE NOTES</p>
      </div>
    </div>
  </section>

  <!-- ======= ticker ======= -->
  <div class="ticker" aria-hidden="true">
    <div class="ticker-track">
      <span class="ticker-seg">Hand-brazed CroMo frames<i>·</i>Built to order — no stock bikes<i>·</i>6–8 weeks, one bike at a time<i>·</i>Up to 340 kg gross load<i>·</i>8-year frame warranty<i>·</i>200 km tested before handover<i>·</i>Bosch Cargo Line mid-drive<i>·</i></span>
      <span class="ticker-seg">Hand-brazed CroMo frames<i>·</i>Built to order — no stock bikes<i>·</i>6–8 weeks, one bike at a time<i>·</i>Up to 340 kg gross load<i>·</i>8-year frame warranty<i>·</i>200 km tested before handover<i>·</i>Bosch Cargo Line mid-drive<i>·</i></span>
    </div>
  </div>

  <!-- ======= machines ======= -->
  <section class="sec" id="machines">
    <div class="wrap">
      <header class="sec-head reveal">
        <p class="kicker"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>01 · The machines</p>
        <h2>Three frames. Every one of them yours.</h2>
        <p class="lead">We keep no stock. Each bike starts as a clean sheet in the workshop — these are the three starting points, and the versions people build from them.</p>
      </header>

      <div class="mach-grid">
        <div class="model-nav reveal" role="tablist" aria-label="Models" id="modelNav">
          <button class="model-tab" type="button" role="tab" data-model="lang" aria-selected="false">
            <span class="mn-no">N°1</span><span class="mn-name">Lang</span><span class="mn-sub">LONGTAIL · FROM €3.900</span>
          </button>
          <button class="model-tab" type="button" role="tab" data-model="frakt" aria-selected="false">
            <span class="mn-no">N°2</span><span class="mn-name">Frakt</span><span class="mn-sub">FRONT LOADER · FROM €4.400</span>
          </button>
          <button class="model-tab" type="button" role="tab" data-model="vogn" aria-selected="false">
            <span class="mn-no">N°3</span><span class="mn-name">Vogn</span><span class="mn-sub">CARGO TRIKE · FROM €4.900</span>
          </button>
        </div>

        <div class="machine-main reveal" style="--d:.1s">
          <div class="sheet">
            <span class="corner-note tl mono">LUMEN CYCLES · KØBENHAVN</span>
            <span class="corner-note br mono" id="mCorner">SHEET 01 / 03</span>
            <div id="mStage"></div>
          </div>
          <p class="fig" id="mFig">FIG. 01 — LANG · LONGTAIL · SCALE 1:8</p>
          <div id="machineBody">
            <div class="machine-lower">
              <div class="specs" id="mSpecs"></div>
              <div class="machine-copy">
                <h3 id="mTitle">Lang <span>— the everyday longtail.</span></h3>
                <p id="mDesc"></p>
                <button class="link-arrow" type="button" id="cfgLink" data-config="lang">Configure this model <span class="arr">→</span></button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>

  <!-- ======= configurator ======= -->
  <section class="sec dark" id="build">
    <div class="wrap">
      <header class="sec-head reveal">
        <p class="kicker"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>02 · Compose yours</p>
        <h2>Draw yours on paper first.</h2>
        <p class="lead">Pick a frame, a colour and the details. The drawing updates as you go — and the configuration travels with your request. The price you see is the price we honour.</p>
      </header>

      <div class="cfg-grid">
        <div class="cfg-controls reveal">
          <div class="cfg-group">
            <p class="opt-label">Frame</p>
            <div class="segs" id="cModels">
              <button type="button" data-model="lang">LANG · LONGTAIL</button>
              <button type="button" data-model="frakt" class="on">FRAKT · FRONT LOADER</button>
              <button type="button" data-model="vogn">VOGN · TRIKE</button>
            </div>
          </div>
          <div class="cfg-group">
            <p class="opt-label">Paint — <b id="paintName">Signal Amber</b></p>
            <div class="swatches" id="cfgPaint">
              <button type="button" class="swatch on" style="background:#D9541E" aria-label="Signal Amber"></button>
              <button type="button" class="swatch" style="background:#26211A" aria-label="Ink Black"></button>
              <button type="button" class="swatch" style="background:#5C6B52" aria-label="Moss Green"></button>
              <button type="button" class="swatch" style="background:#8A3B2C" aria-label="Oxide Red"></button>
              <button type="button" class="swatch" style="background:#B9BCB4" aria-label="Fog Grey"></button>
              <span class="paint-name" id="paintNameTag">Signal Amber</span>
            </div>
          </div>
          <div class="cfg-group">
            <p class="opt-label">Box / deck</p>
            <div class="segs" id="cfgWood">
              <button type="button" data-wood="0" class="on"><i style="background:#E5C993"></i>BIRCH PLY</button>
              <button type="button" data-wood="1"><i style="background:#8B6A4E"></i>SMOKED OAK</button>
              <button type="button" data-wood="2"><i style="background:#BFC3BE"></i>ALUMINIUM</button>
            </div>
          </div>
          <div class="cfg-group">
            <p class="opt-label">Details</p>
            <div class="chk-list" id="cfgExtras">
              <label class="chk"><input type="checkbox" value="belt"><span class="box"></span><span class="chk-n">Gates carbon belt drive</span><span class="chk-p">+ €390</span></label>
              <label class="chk"><input type="checkbox" value="canopy"><span class="box"></span><span class="chk-n">Rain canopy, sailcloth</span><span class="chk-p">+ €240</span></label>
              <label class="chk"><input type="checkbox" value="battery"><span class="box"></span><span class="chk-n">Second battery, 500 Wh</span><span class="chk-p">+ €420</span></label>
              <label class="chk"><input type="checkbox" value="kids"><span class="box"></span><span class="chk-n">Twin child seats + rails</span><span class="chk-p">+ €190</span></label>
            </div>
          </div>
        </div>

        <div class="reveal" style="--d:.12s">
          <div class="sheet">
            <span class="corner-note tl mono">LUMEN CYCLES · KØBENHAVN</span>
            <span class="corner-note br mono">SHEET 04 · REV. C — YOURS</span>
            <div id="cStage"></div>
          </div>
          <div class="price-box">
            <div>
              <p class="opt-label" style="margin-bottom:8px">Estimated build price</p>
              <p class="price-big" id="cPrice">€4.400</p>
              <p class="price-sub" id="cSummary">FRONT LOADER · SIGNAL AMBER · BIRCH PLY</p>
            </div>
            <button class="btn btn-accent" type="button" id="reqBtn">Request this build</button>
          </div>
          <p class="serial">Your build will be <b>N° 248</b> — the drawing sheet is already started.<br>Final quote follows your fitting session · VAT included · EU shipping quoted separately.</p>
        </div>
      </div>
    </div>
  </section>

  <!-- ======= process ======= -->
  <section class="sec" id="process">
    <div class="wrap">
      <header class="sec-head reveal">
        <p class="kicker"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>03 · How a build happens</p>
        <h2>Six weeks, five conversations.</h2>
        <p class="lead">Nothing here is automated. Every step involves a person at Lumen and a decision that is yours.</p>
      </header>

      <div class="proc-grid">
        <ol class="steps reveal">
          <li class="step"><span class="step-no">01</span>
            <div><h3>The conversation</h3><p>Coffee at the workshop, or a video call. We map your routes, your cargo and your cellar stairs before anything is drawn.</p></div>
            <span class="step-meta">45 MIN</span></li>
          <li class="step"><span class="step-no">02</span>
            <div><h3>The fitting</h3><p>Measurements, saddle height, test laps on our mule bikes. Your geometry gets drafted by hand — not pulled from a folder.</p></div>
            <span class="step-meta">ONE AFTERNOON</span></li>
          <li class="step"><span class="step-no">03</span>
            <div><h3>Brazing &amp; paint</h3><p>Fillet-brazed 4130 CroMo, cleaned, primered and powder-coated in the colour you chose. You're welcome to watch the torch work.</p></div>
            <span class="step-meta">WEEK 3–4</span></li>
          <li class="step"><span class="step-no">04</span>
            <div><h3>Assembly &amp; the shakedown</h3><p>Wheels built by hand, belt tensioned, brakes bedded in. Then we ride your bike 200 km through Copenhagen before you ever see it.</p></div>
            <span class="step-meta">WEEK 5–6</span></li>
          <li class="step"><span class="step-no">05</span>
            <div><h3>The handover ride</h3><p>You leave with your bike, a fitted helmet and twenty minutes on how to care for it. First year of servicing is on us.</p></div>
            <span class="step-meta">ONE HOUR</span></li>
        </ol>

        <aside class="proc-aside reveal" style="--d:.12s">
          <figure>
            <blockquote>"A cargo bike is furniture you ride. It should be built like furniture — to measure, by hand, to last decades."</blockquote>
            <figcaption>— Rasmus Kyed<br>Founder &amp; frame builder</figcaption>
          </figure>
          <p class="warranty">Every Lumen frame carries an <strong>8-year warranty</strong>, transferable when you sell the bike — which, in our experience, almost nobody does.</p>
        </aside>
      </div>
    </div>
  </section>

  <!-- ======= voices ======= -->
  <section class="sec voices" id="voices">
    <div class="wrap">
      <header class="sec-head reveal" style="text-align:center">
        <p class="kicker" style="justify-content:center"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>04 · From the saddle</p>
        <h2 style="margin-inline:auto">Copenhagen has opinions.</h2>
      </header>
      <div class="q-wrap reveal">
        <span class="qmark" aria-hidden="true">“</span>
        <div class="q-box" id="quoteBox">
          <blockquote id="qText">“Two kids, a week of groceries and a ladder. It climbs the bridge without me noticing any of it.”</blockquote>
          <p class="q-attr" id="qAttr">Mette F. · Nørrebro · Lumen Lang, 2021</p>
        </div>
        <div class="q-controls">
          <button class="q-btn" type="button" id="qPrev" aria-label="Previous story"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 5l-7 7 7 7"/></svg></button>
          <button class="q-btn" type="button" id="qNext" aria-label="Next story"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5l7 7-7 7"/></svg></button>
          <span class="q-idx" id="qIdx">01 / 04</span>
        </div>
      </div>
    </div>
  </section>

  <!-- ======= visit ======= -->
  <section class="sec" id="visit">
    <div class="wrap visit-grid">
      <div class="reveal">
        <p class="kicker"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>05 · The workshop</p>
        <h2>Come and sit on one.</h2>
        <p class="lead">The kettle is usually on. Lift a frame, argue about colours, take a mule bike for a lap around the harbour.</p>
        <p class="addr" style="margin-top:30px">Refshalevej 163A<br>1432 København K</p>
        <div class="visit-hours">
          <div class="spec"><span>Tue – Fri</span><b>10:00 – 17:30</b></div>
          <div class="spec"><span>Saturday</span><b>10:00 – 14:00</b></div>
          <div class="spec"><span>Sun – Mon</span><b>Closed · by appointment</b></div>
        </div>
        <div class="contact-line">
          <a href="mailto:hello@lumencycles.dk">HELLO@LUMENCYCLES.DK</a>
          <a href="tel:+4531628840">+45 31 62 88 40</a>
        </div>
        <button class="btn" type="button" id="visitBook">Book a visit</button>
        <p class="visit-note">Good to know: we keep four mule bikes for test rides. If cargo is the point, bring the cargo — a child seat, the week's shopping, whatever you actually carry.</p>
      </div>
      <div class="map-panel reveal" style="--d:.12s">
        <svg class="map" viewBox="0 0 640 460" role="img" aria-label="Hand-drawn map of Refshaleøen, Copenhagen, with the Lumen workshop marked">
          <rect width="640" height="460" fill="#EAE2D1"/>
          <path d="M0 0 H640 V152 C560 178 486 148 400 170 C310 194 226 158 140 184 C88 199 40 188 0 202 Z" fill="#C9D3CF"/>
          <g stroke="#211D16" stroke-width="2" opacity=".4" stroke-linecap="round">
            <path d="M152 186 L144 136 M166 184 L160 138"/>
            <path d="M332 170 L324 120 M348 168 L342 122"/>
            <path d="M512 158 L504 108 M528 156 L522 110"/>
          </g>
          <g stroke="#5F6E69" stroke-width="1.4" opacity=".55" stroke-linecap="round">
            <path d="M70 58 h36 M138 92 h28 M262 52 h30 M428 66 h26 M486 118 h24 M566 60 h28 M330 92 h22"/>
          </g>
          <text x="46" y="94" class="map-serif">Copenhagen Harbour</text>
          <g stroke="#211D16" fill="none" opacity=".5">
            <path d="M30 300 H612" stroke-width="1.8"/>
            <path d="M30 378 H612" stroke-width="1.2"/>
            <path d="M132 262 V440 M298 258 V440 M466 256 V440" stroke-width="1.2"/>
          </g>
          <g fill="none" stroke="#211D16" opacity=".22">
            <rect x="152" y="316" width="118" height="44"/>
            <rect x="322" y="314" width="118" height="46"/>
            <rect x="490" y="316" width="98" height="44"/>
            <rect x="160" y="394" width="110" height="34"/>
            <rect x="330" y="394" width="120" height="34"/>
          </g>
          <text x="176" y="284" class="map-mono big">REFSHALEVEJ</text>
          <text x="486" y="440" class="map-mono big">AMAGER →</text>
          <text x="60" y="436" class="map-mono">← CITY CENTRE</text>
          <g>
            <circle class="halo" cx="382" cy="336" r="7" fill="none" stroke="#D9541E" stroke-width="2"/>
            <circle cx="382" cy="336" r="6" fill="#D9541E"/>
            <path d="M382 344 V382" stroke="#211D16" stroke-width="1.4" opacity=".6"/>
            <text x="382" y="398" text-anchor="middle" class="map-mono" style="font-size:11px;letter-spacing:.22em">LUMEN CYCLES</text>
          </g>
          <g transform="translate(586,58)" stroke="#211D16" fill="none" opacity=".6">
            <circle r="15" stroke-width="1.2"/>
            <path d="M0 8 V-8 M-4 -3 L0 -9 L4 -3" stroke-width="1.4"/>
          </g>
          <text x="586" y="34" text-anchor="middle" class="map-mono">N</text>
          <g transform="translate(40,430)" stroke="#211D16" opacity=".6">
            <path d="M0 0 H70" stroke-width="1.4"/>
            <path d="M0 -4 V4 M70 -4 V4" stroke-width="1.4"/>
          </g>
          <text x="40" y="422" class="map-mono" style="letter-spacing:.18em">250 M</text>
        </svg>
      </div>
    </div>
  </section>
</main>

<!-- ======= footer ======= -->
<footer class="site-foot">
  <div class="wrap">
    <div class="foot-top">
      <p class="foot-mark"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>Lumen Cycles</p>
      <p class="foot-tag">Custom cargo bikes, brazed and built to order in Copenhagen since 2016.</p>
    </div>
    <div class="foot-grid">
      <div>
        <h4>Workshop</h4>
        <p>Refshalevej 163A<br>1432 København K<br>Tue–Fri 10:00–17:30 · Sat 10:00–14:00</p>
      </div>
      <div>
        <h4>Pages</h4>
        <a href="#machines">The machines</a>
        <a href="#build">Compose yours</a>
        <a href="#process">How a build happens</a>
        <a href="#visit">Visit us</a>
      </div>
      <div>
        <h4>Contact</h4>
        <a href="mailto:hello@lumencycles.dk">hello@lumencycles.dk</a>
        <a href="tel:+4531628840">+45 31 62 88 40</a>
        <button class="link-arrow" type="button" data-open="visit" style="color:var(--dark-text)">Book a visit <span class="arr">→</span></button>
      </div>
    </div>
    <div class="foot-base">
      <span>© <span id="year">2025</span> Lumen Cycles ApS · CVR 41 22 87 03</span>
      <span>Prices include Danish VAT (25 %)</span>
      <span>Built by hand on Refshaleøen</span>
    </div>
  </div>
</footer>

<!-- ======= modal ======= -->
<div class="overlay" id="overlay" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle">
    <button class="modal-x" type="button" id="mClose" aria-label="Close dialog">
      <svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M1 1l12 12M13 1L1 13"/></svg>
    </button>
    <p class="kicker" style="margin-bottom:6px" id="mKicker">BUILD REQUEST</p>
    <h3 id="mTitle">Request this build</h3>
    <p class="m-summary" id="mSummary"></p>
    <form id="mForm" novalidate>
      <label class="field"><span class="f-label">Name</span>
        <input id="fName" name="name" autocomplete="name">
        <span class="err">Please add your name.</span></label>
      <label class="field"><span class="f-label">Email</span>
        <input id="fEmail" name="email" type="email" autocomplete="email">
        <span class="err">That email doesn't look right.</span></label>
      <label class="field"><span class="f-label">Phone <span class="opt">OPTIONAL</span></span>
        <input id="fPhone" name="phone" type="tel" autocomplete="tel"></label>
      <label class="field"><span class="f-label">Message</span>
        <textarea id="fMsg" name="message"></textarea></label>
      <button class="btn btn-accent" type="submit" id="mSubmit" style="width:100%;justify-content:center">Send request</button>
      <p class="form-note">We reply within two working days · from hello@lumencycles.dk</p>
    </form>
    <div class="m-success" id="mSuccess" hidden>
      <svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>
      <h3 id="sTitle">Request received.</h3>
      <p id="sText"></p>
      <button class="btn" type="button" id="sClose">Close</button>
    </div>
  </div>
</div>

<div class="toast" id="toast" role="status" aria-live="polite">
  <svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>
  <span id="toastMsg"></span>
</div>

<!-- ======= bike drawing templates ======= -->
<template id="t-frakt">
<svg class="bike" viewBox="0 0 960 620" role="img" aria-label="Technical side drawing of the Lumen Frakt front-loader cargo bike">
  <ellipse class="shadow" cx="690" cy="523" rx="106" ry="7" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="215" cy="523" rx="72" ry="6" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="556" cy="524" rx="28" ry="4" fill="rgba(33,29,22,.09)"/>
  <path class="ink" d="M30 521 H 930" stroke-dasharray="12 9" opacity=".35"/>
  <g class="wheel part" data-part="rear" data-cx="690" data-cy="415" data-r="105" data-spokes="14">
    <circle class="ink tire d" cx="690" cy="415" r="105" stroke-width="10"/>
    <circle class="ink rim d" cx="690" cy="415" r="92" stroke-width="2.5" opacity=".85"/>
    <circle class="hub f" cx="690" cy="415" r="7"/>
  </g>
  <g class="wheel part" data-part="front" data-cx="215" data-cy="448" data-r="72" data-spokes="12">
    <circle class="ink tire d" cx="215" cy="448" r="72" stroke-width="9"/>
    <circle class="ink rim d" cx="215" cy="448" r="62" stroke-width="2.2" opacity=".85"/>
    <circle class="hub f" cx="215" cy="448" r="6"/>
  </g>
  <g class="drive">
    <circle class="ink ring d" cx="540" cy="420" r="26" stroke-width="4"/>
    <path class="ink d" d="M540 420 L574 452" stroke-width="5"/>
    <path class="ink d" d="M560 452 H594" stroke-width="7"/>
    <path class="ink d" d="M540 420 L506 388" stroke-width="5"/>
    <path class="ink d" d="M492 388 H526" stroke-width="7"/>
    <circle class="ink cog d" cx="690" cy="415" r="13" stroke-width="3"/>
    <path class="ink chain d" d="M548 396 L691 403" stroke-width="2.4"/>
    <path class="ink chain d" d="M533 443 L688 428" stroke-width="2.4"/>
  </g>
  <path class="canopy" style="display:none" d="M238 268 Q 379 176 520 268 Z"/>
  <g class="frame part" data-part="frame">
    <path class="tube d" d="M540 420 L690 415"/>
    <path class="tube d" d="M540 420 L378 312"/>
    <path class="tube d" d="M380 315 L356 248" stroke-width="8"/>
    <path class="tube d" d="M563 278 L358 250"/>
    <path class="tube d" d="M540 420 L566 262"/>
    <path class="tube d" d="M690 415 L561 290"/>
    <path class="tube d" d="M380 315 L215 448"/>
    <path class="tube d" d="M356 248 L334 190"/>
    <path class="tube d" d="M304 196 Q 334 168 362 186" stroke-width="6"/>
    <path class="tube d" d="M256 384 H 506" stroke-width="5"/>
    <path class="tube d" d="M260 386 L234 430" stroke-width="4.5"/>
    <path class="tube d" d="M500 386 L513 402" stroke-width="4.5"/>
    <path class="ink d" d="M540 424 L554 514" stroke-width="4.5"/>
    <path class="ink d" d="M548 518 H570" stroke-width="4.5"/>
    <path class="ink d" d="M566 262 L572 238" stroke-width="5"/>
  </g>
  <g class="box part" data-part="box">
    <rect class="boxy f" x="250" y="272" width="258" height="108" rx="10"/>
    <path class="slat f" d="M292 279 V373 M330 279 V373 M438 279 V373 M476 279 V373"/>
    <text class="boxtext f" x="384" y="341" text-anchor="middle" font-size="15" letter-spacing="3">LUMEN · KBH</text>
  </g>
  <path class="saddle f" d="M540 236 Q 566 226 596 234 Q 566 244 540 236 Z"/>
  <g class="lampg">
    <rect class="lamp f" x="328" y="232" width="17" height="13" rx="4"/>
    <path class="ray d" d="M326 234 L310 229"/>
    <path class="ray d" d="M326 238 L308 238"/>
    <path class="ray d" d="M326 242 L310 247"/>
  </g>
</svg>
</template>

<template id="t-lang">
<svg class="bike" viewBox="0 0 960 620" role="img" aria-label="Technical side drawing of the Lumen Lang longtail cargo bike">
  <ellipse class="shadow" cx="698" cy="523" rx="113" ry="7" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="200" cy="523" rx="89" ry="6" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="462" cy="524" rx="26" ry="4" fill="rgba(33,29,22,.09)"/>
  <path class="ink" d="M30 521 H 930" stroke-dasharray="12 9" opacity=".35"/>
  <g class="wheel part" data-part="rear" data-cx="698" data-cy="408" data-r="112" data-spokes="14">
    <circle class="ink tire d" cx="698" cy="408" r="112" stroke-width="10"/>
    <circle class="ink rim d" cx="698" cy="408" r="99" stroke-width="2.5" opacity=".85"/>
    <circle class="hub f" cx="698" cy="408" r="7"/>
  </g>
  <g class="wheel part" data-part="front" data-cx="200" data-cy="432" data-r="88" data-spokes="12">
    <circle class="ink tire d" cx="200" cy="432" r="88" stroke-width="10"/>
    <circle class="ink rim d" cx="200" cy="432" r="78" stroke-width="2.5" opacity=".85"/>
    <circle class="hub f" cx="200" cy="432" r="6"/>
  </g>
  <g class="drive">
    <circle class="ink ring d" cx="450" cy="420" r="26" stroke-width="4"/>
    <path class="ink d" d="M450 420 L484 452" stroke-width="5"/>
    <path class="ink d" d="M470 452 H504" stroke-width="7"/>
    <path class="ink d" d="M450 420 L416 388" stroke-width="5"/>
    <path class="ink d" d="M402 388 H436" stroke-width="7"/>
    <circle class="ink cog d" cx="698" cy="408" r="13" stroke-width="3"/>
    <path class="ink chain d" d="M458 395 L699 396" stroke-width="2.4"/>
    <path class="ink chain d" d="M443 444 L697 421" stroke-width="2.4"/>
  </g>
  <path class="canopy" style="display:none" d="M560 288 Q 614 150 668 290 Z"/>
  <g class="frame part" data-part="frame">
    <path class="tube d" d="M450 420 L698 408"/>
    <path class="tube d" d="M450 420 L292 330"/>
    <path class="tube d" d="M296 334 L274 266" stroke-width="8"/>
    <path class="tube d" d="M477 272 L278 268"/>
    <path class="tube d" d="M450 420 L480 256"/>
    <path class="tube d" d="M698 408 L472 296"/>
    <path class="tube d" d="M296 332 L202 430"/>
    <path class="tube d" d="M274 266 L254 194"/>
    <path class="tube d" d="M224 200 Q 254 172 282 188" stroke-width="6"/>
    <path class="ink d" d="M450 424 L464 512" stroke-width="4.5"/>
    <path class="ink d" d="M456 516 H478" stroke-width="4.5"/>
    <path class="ink d" d="M480 256 L483 236" stroke-width="5"/>
    <path class="ink d" d="M498 282 L842 296" stroke-width="11"/>
    <path class="deck d" d="M501 282 L839 296" stroke-width="5"/>
    <path class="ink d" d="M474 300 L498 286" stroke-width="4.5"/>
    <path class="ink d" d="M838 296 L745 428" stroke-width="4.5"/>
  </g>
  <g class="kidseat" style="display:none">
    <path class="ink d" d="M592 287 L664 291" stroke-width="7"/>
    <path class="ink d" d="M662 289 L668 232" stroke-width="5"/>
    <circle class="hub f" cx="668" cy="228" r="4.5"/>
    <path class="ink d" d="M592 290 L589 307 M634 291 L631 308" stroke-width="4"/>
  </g>
  <path class="saddle f" d="M458 232 Q 486 222 516 232 Q 487 242 458 232 Z"/>
  <g class="lampg">
    <rect class="lamp f" x="246" y="250" width="17" height="13" rx="4"/>
    <path class="ray d" d="M244 253 L228 248"/>
    <path class="ray d" d="M244 257 L226 257"/>
    <path class="ray d" d="M244 261 L228 266"/>
  </g>
</svg>
</template>

<template id="t-vogn">
<svg class="bike" viewBox="0 0 960 620" role="img" aria-label="Technical side drawing of the Lumen Vogn cargo trike">
  <ellipse class="shadow" cx="720" cy="523" rx="111" ry="7" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="205" cy="523" rx="76" ry="6" fill="rgba(33,29,22,.09)"/>
  <ellipse class="shadow" cx="558" cy="524" rx="26" ry="4" fill="rgba(33,29,22,.09)"/>
  <path class="ink" d="M30 521 H 930" stroke-dasharray="12 9" opacity=".35"/>
  <g class="far" opacity=".32">
    <circle class="ink tire d" cx="248" cy="442" r="75" stroke-width="9"/>
    <circle class="ink rim d" cx="248" cy="442" r="66" stroke-width="2" opacity=".85"/>
    <circle class="hub f" cx="248" cy="442" r="5"/>
    <path class="tube d" d="M368 308 L250 440" stroke-width="5"/>
  </g>
  <g class="wheel part" data-part="rear" data-cx="720" data-cy="410" data-r="110" data-spokes="14">
    <circle class="ink tire d" cx="720" cy="410" r="110" stroke-width="10"/>
    <circle class="ink rim d" cx="720" cy="410" r="97" stroke-width="2.5" opacity=".85"/>
    <circle class="hub f" cx="720" cy="410" r="7"/>
  </g>
  <g class="wheel part" data-part="front" data-cx="205" data-cy="445" data-r="75" data-spokes="12">
    <circle class="ink tire d" cx="205" cy="445" r="75" stroke-width="9"/>
    <circle class="ink rim d" cx="205" cy="445" r="66" stroke-width="2.2" opacity=".85"/>
    <circle class="hub f" cx="205" cy="445" r="5.5"/>
  </g>
  <g class="drive">
    <circle class="ink ring d" cx="540" cy="430" r="26" stroke-width="4"/>
    <path class="ink d" d="M540 430 L574 462" stroke-width="5"/>
    <path class="ink d" d="M560 462 H594" stroke-width="7"/>
    <path class="ink d" d="M540 430 L506 398" stroke-width="5"/>
    <path class="ink d" d="M492 398 H526" stroke-width="7"/>
    <circle class="ink cog d" cx="720" cy="410" r="13" stroke-width="3"/>
    <path class="ink chain d" d="M548 405 L721 412" stroke-width="2.4"/>
    <path class="ink chain d" d="M533 453 L719 432" stroke-width="2.4"/>
  </g>
  <path class="canopy" style="display:none" d="M240 264 Q 382 158 524 264 Z"/>
  <g class="frame part" data-part="frame">
    <path class="tube d" d="M540 430 L720 410"/>
    <path class="tube d" d="M540 430 L570 276"/>
    <path class="tube d" d="M565 300 L352 262"/>
    <path class="tube d" d="M540 430 L368 318"/>
    <path class="tube d" d="M374 314 L350 240" stroke-width="8"/>
    <path class="tube d" d="M372 314 L207 443"/>
    <path class="tube d" d="M350 240 L330 184"/>
    <path class="tube d" d="M300 192 Q 330 164 358 180" stroke-width="6"/>
    <path class="tube d" d="M258 384 H 506" stroke-width="5"/>
    <path class="tube d" d="M262 386 L237 424" stroke-width="4.5"/>
    <path class="tube d" d="M500 386 L513 410" stroke-width="4.5"/>
    <path class="ink d" d="M567 276 L560 256" stroke-width="5"/>
  </g>
  <g class="box part" data-part="box">
    <rect class="boxy f" x="252" y="268" width="260" height="112" rx="10"/>
    <path class="slat f" d="M296 275 V373 M336 275 V373 M448 275 V373 M488 275 V373"/>
    <text class="boxtext f" x="392" y="339" text-anchor="middle" font-size="15" letter-spacing="3">LUMEN · KBH</text>
  </g>
  <path class="saddle f" d="M516 250 Q 546 240 578 250 Q 547 260 516 250 Z"/>
  <g class="lampg">
    <rect class="lamp f" x="320" y="228" width="17" height="13" rx="4"/>
    <path class="ray d" d="M318 231 L302 226"/>
    <path class="ray d" d="M318 235 L300 235"/>
    <path class="ray d" d="M318 239 L302 244"/>
  </g>
</svg>
</template>

<script>
(() => {
'use strict';
document.documentElement.classList.add('js');
const $  = (s, r=document) => r.querySelector(s);
const $$ = (s, r=document) => Array.from(r.querySelectorAll(s));
const NS = 'http://www.w3.org/2000/svg';
const RM = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const money = n => '€' + n.toLocaleString('da-DK');
$('#year').textContent = new Date().getFullYear();

/* ---------- catalogue data ---------- */
const MODELS = {
  lang: { no:'N°1', name:'Lang', type:'Longtail', tag:'the everyday longtail', tpl:'t-lang', base:3900,
    fig:'FIG. 01 — LANG · LONGTAIL · SCALE 1:8', sheet:'SHEET 01 / 03',
    desc:'A normal bike stretched twenty-five centimetres, so it still rides like a bike — not a van. Two child seats, six bags of shopping, a flat-pack wardrobe if you are feeling brave. The most popular Lumen, and our first recommendation for daycare runs.',
    specs:[['Wheelbase','2,050 mm'],['Deck load','100 kg'],['Max gross weight','220 kg'],['Own weight','24.5 kg'],['Gears','Pinion C1.12 / Nexus 8'],['From','€3.900']] },
  frakt: { no:'N°2', name:'Frakt', type:'Front loader', tag:'the load you can see', tpl:'t-frakt', base:4400,
    fig:'FIG. 02 — FRAKT · FRONT LOADER · SCALE 1:8', sheet:'SHEET 02 / 03',
    desc:'The box sits low, between your arms, where you can watch it — and the steering stays feather-light even fully packed. The trick is in the head angle, and we tune it with you at the fitting. The courier favourite.',
    specs:[['Wheelbase','1,850 mm'],['Box volume','105 L'],['Max gross weight','295 kg'],['Own weight','26 kg'],['Gears','Gates belt / 11-speed'],['From','€4.400']] },
  vogn: { no:'N°3', name:'Vogn', type:'Cargo trike', tag:'the workshop on wheels', tpl:'t-vogn', base:4900,
    fig:'FIG. 03 — VOGN · CARGO TRIKE · SCALE 1:8', sheet:'SHEET 03 / 03',
    desc:'Three wheels and a box the size of a washing-machine drum. It stands on its own while you load it, tracks dead straight with eighty kilos aboard, and still fits in a cargo lift. Built for tradespeople who are done with vans.',
    specs:[['Wheelbase','1,760 mm'],['Box volume','120 L'],['Max gross weight','340 kg'],['Own weight','29 kg'],['Gears','Enviolo stepless'],['From','€4.900']] }
};
const PAINTS = [
  {n:'Signal Amber', c:'#D9541E'}, {n:'Ink Black', c:'#26211A'}, {n:'Moss Green', c:'#5A6B4E'},
  {n:'Oxide Red', c:'#8A3B2A'}, {n:'Fog Grey', c:'#B9BCB4'}
];
const WOODS = [
  {n:'Birch ply', c:'#E5C993'}, {n:'Smoked oak', c:'#8B6A4C'}, {n:'Aluminium', c:'#BFC3BE'}
];
const EXTRAS = [
  {id:'belt',   n:'Gates carbon belt drive',   short:'BELT DRIVE', p:390},
  {id:'canopy', n:'Rain canopy',               short:'RAIN CANOPY', p:240},
  {id:'battery',n:'Second battery, 500 Wh',    short:'2ND BATTERY', p:420},
  {id:'kids',   n:'Twin child seats + rails',  short:'CHILD SEATS', p:190}
];

/* ---------- build & animate a drawing ---------- */
function makeBike(tplId){
  const svg = $('#'+tplId).content.firstElementChild.cloneNode(true);
  $$('.wheel', svg).forEach(w => {
    const cx=+w.dataset.cx, cy=+w.dataset.cy, r=+w.dataset.r, n=+w.dataset.spokes||12;
    for(let i=0;i<n;i++){
      const a=(Math.PI*2*i)/n + (i%2)*0.12;
      const l=document.createElementNS(NS,'line');
      l.setAttribute('x1',(cx+Math.cos(a)*r*0.14).toFixed(1));
      l.setAttribute('y1',(cy+Math.sin(a)*r*0.14).toFixed(1));
      l.setAttribute('x2',(cx+Math.cos(a)*(r-8)).toFixed(1));
      l.setAttribute('y2',(cy+Math.sin(a)*(r-8)).toFixed(1));
      l.setAttribute('class','ink spoke d');
      w.appendChild(l);
    }
  });
  return svg;
}
function drawBike(svg, {stagger=26, base=180} = {}){
  if(RM) return;
  const ds = $$('.d', svg), fs = $$('.f', svg).filter(el => !el.classList.contains('canopy'));
  ds.forEach((el, i) => {
    let len = 220;
    try { len = el.getTotalLength(); } catch(e){}
    const dash = len + 2;
    el.style.strokeDasharray  = dash + ' ' + dash;
    el.style.strokeDashoffset = dash;
    el.animate(
      [{strokeDashoffset: dash},{strokeDashoffset: 0}],
      {duration: Math.min(1150, Math.max(240, len*1.05)), delay: base + i*stagger,
       easing: 'cubic-bezier(.45,.05,.35,1)', fill: 'both'});
  });
  fs.forEach((el, i) => {
    el.animate([{opacity:0},{opacity:1}],
      {duration: 380, delay: base + ds.length*stagger + 140 + i*55, easing:'ease-out', fill:'both'});
  });
}
function toggleCanopy(svg, on){
  const c = svg.querySelector('.canopy');
  if(!c) return;
  c.getAnimations().forEach(a => a.cancel());
  if(on){
    c.style.display = 'inline';
    if(RM) return;
    const len = c.getTotalLength();
    c.style.strokeDasharray = len + ' ' + len;
    c.animate([{strokeDashoffset: len, fillOpacity: 0},{strokeDashoffset: 0, fillOpacity: .09}],
      {duration: 750, easing:'cubic-bezier(.45,.05,.35,1)', fill:'both'});
  } else {
    c.style.display = 'none';
  }
}

/* ---------- hero drawing + annotation callouts ---------- */
{
  const svg = makeBike('t-frakt');
  $('#heroStage').appendChild(svg);
  const DATA = [
    {t:'FILLET-BRAZED 4130 CROMO', part:'frame', x:60,  y:300, pts:[[252,296],[300,296],[452,361]]},
    {t:'105 L BIRCH-PLY BOX',      part:'box',   x:600, y:96,  pts:[[594,92],[552,92],[472,270]]},
    {t:'HUB MOTOR · 85 NM',        part:'rear',  x:700, y:574, pts:[[742,560],[742,468],[694,420]]},
    {t:'20″ FRONT / 28″ REAR',     part:'front', x:36,  y:574, pts:[[44,560],[44,470],[148,470]]}
  ];
  DATA.forEach(c => {
    const g = document.createElementNS(NS,'g');
    g.setAttribute('class','callout f');
    g.dataset.target = c.part;
    const pl = document.createElementNS(NS,'polyline');
    pl.setAttribute('points', c.pts.map(p => p.join(',')).join(' '));
    const dot = document.createElementNS(NS,'circle');
    const end = c.pts[c.pts.length-1];
    dot.setAttribute('cx', end[0]); dot.setAttribute('cy', end[1]); dot.setAttribute('r','3.5');
    const tx = document.createElementNS(NS,'text');
    tx.setAttribute('x', c.x); tx.setAttribute('y', c.y);
    tx.textContent = c.t;
    g.append(pl, dot, tx);
    svg.appendChild(g);
  });
  $$('.callout', svg).forEach(c => {
    const t = svg.querySelector('[data-part="' + c.dataset.target + '"]');
    if(!t) return;
    const on  = () => { t.classList.add('hot'); c.classList.add('hot'); };
    const off = () => { t.classList.remove('hot'); c.classList.remove('hot'); };
    c.addEventListener('mouseenter', on);  c.addEventListener('mouseleave', off);
    t.addEventListener('mouseenter', on);  t.addEventListener('mouseleave', off);
  });
  drawBike(svg, {stagger:26, base:280});
}

/* ---------- machines section ---------- */
const mStage = $('#mStage'), mFig = $('#mFig'), mCorner = $('#mCorner'),
      mTitle = $('#mTitle'), mDesc = $('#mDesc'), mSpecs = $('#mSpecs'),
      mBody  = $('#machineBody');
function renderMachine(id, animate = true){
  const m = MODELS[id];
  $$('#modelNav .model-tab').forEach(b => {
    const on = b.dataset.model === id;
    b.classList.toggle('on', on);
    b.setAttribute('aria-selected', on);
  });
  mStage.querySelectorAll('svg').forEach(s => s.remove());
  const svg = makeBike(m.tpl);
  mStage.appendChild(svg);
  if(animate) drawBike(svg, {stagger:14, base:80});
  mFig.textContent = m.fig;
  mCorner.textContent = m.sheet;
  mBody.classList.add('swap');
  setTimeout(() => {
    mTitle.innerHTML = m.name + ' <span>— ' + m.tag + '.</span>';
    mDesc.textContent = m.desc;
    mSpecs.innerHTML = m.specs.map(([k,v]) => '<div class="spec"><span>' + k + '</span><b>' + v + '</b></div>').join('');
    $('#cfgLink').dataset.config = id;
    mBody.classList.remove('swap');
  }, animate ? 190 : 0);
}
$$('#modelNav .model-tab').forEach(b =>
  b.addEventListener('click', () => renderMachine(b.dataset.model)));
renderMachine('lang', false);

/* ---------- configurator ---------- */
const state = { model:'frakt', paint:0, wood:0, extras:new Set() };
const cStage = $('#cStage'), cPrice = $('#cPrice'), cSum = $('#cSummary'),
      paintName = $('#paintName'), paintTag = $('#paintNameTag');
let shown = 0, total = 0, priceRaf = null;
function tweenPrice(to){
  total = to;
  const from = shown || to, t0 = performance.now();
  if(priceRaf) cancelAnimationFrame(priceRaf);
  const step = t => {
    const k = Math.min(1, (t - t0) / 480), e = 1 - Math.pow(1 - k, 3);
    shown = Math.round(from + (to - from) * e);
    cPrice.textContent = money(shown);
    if(k < 1) priceRaf = requestAnimationFrame(step);
  };
  priceRaf = requestAnimationFrame(step);
}
function renderConfigurator(rebuild = false){
  const m = MODELS[state.model];
  if(rebuild || !cStage.querySelector('svg')){
    cStage.querySelectorAll('svg').forEach(s => s.remove());
    const svg = makeBike(m.tpl);
    cStage.appendChild(svg);
    drawBike(svg, {stagger:12, base:60});
  }
  const svg = cStage.querySelector('svg');
  svg.style.setProperty('--frame', PAINTS[state.paint].c);
  svg.style.setProperty('--box',   WOODS[state.wood].c);
  svg.classList.toggle('belted', state.extras.has('belt'));
  const kids = svg.querySelector('.kidseat');
  if(kids) kids.style.display = state.extras.has('kids') ? 'inline' : 'none';
  toggleCanopy(svg, state.extras.has('canopy'));
  let price = m.base;
  state.extras.forEach(id => price += EXTRAS.find(e => e.id === id).p);
  tweenPrice(price);
  const bits = [m.type.toUpperCase(), PAINTS[state.paint].n.toUpperCase(), WOODS[state.wood].n.toUpperCase()];
  EXTRAS.forEach(e => { if(state.extras.has(e.id)) bits.push(e.short); });
  cSum.textContent = bits.join('   ·   ');
  paintName.textContent = PAINTS[state.paint].n;
  paintTag.textContent  = PAINTS[state.paint].n;
  $$('#cModels button').forEach(b => b.classList.toggle('on', b.dataset.model === state.model));
  $$('#cfgWood button').forEach(b => b.classList.toggle('on', +b.dataset.wood === state.wood));
  $$('#cfgPaint .swatch').forEach((s, i) => s.classList.toggle('on', i === state.paint));
}
$$('#cModels button').forEach(b => b.addEventListener('click', () => {
  if(state.model !== b.dataset.model){ state.model = b.dataset.model; renderConfigurator(true); }
}));
$$('#cfgPaint .swatch').forEach((s, i) => s.addEventListener('click', () => {
  state.paint = i; renderConfigurator();
}));
$$('#cfgWood button').forEach(b => b.addEventListener('click', () => {
  state.wood = +b.dataset.wood; renderConfigurator();
}));
$$('#cfgExtras input').forEach(inp => inp.addEventListener('change', () => {
  inp.checked ? state.extras.add(inp.dataset.extra) : state.extras.delete(inp.dataset.extra);
  renderConfigurator();
}));
renderConfigurator();

$$('[data-config]').forEach(b => b.addEventListener('click', () => {
  state.model = b.dataset.config;
  renderConfigurator(true);
  $('#build').scrollIntoView({behavior: RM ? 'auto' : 'smooth'});
}));

/* ---------- modal + toast ---------- */
const overlay = $('#overlay'), mnav = $('#mnav');
let lastFocus = null;
function openModal(mode){
  lastFocus = document.activeElement;
  const build = mode === 'build';
  $('#mKicker').textContent = build ? 'BUILD REQUEST' : 'WORKSHOP VISIT';
  $('#mTitle').textContent  = build ? 'Request this build' : 'Book a workshop visit';
  const sum = $('#mSummary');
  if(build){
    sum.hidden = false;
    sum.textContent = cSum.textContent + '   ·   ' + cPrice.textContent;
    $('#fMsg').value = composeMessage();
  } else {
    sum.hidden = true;
    $('#fMsg').value = '';
  }
  $('#mForm').hidden = false;
  $('#mSuccess').hidden = true;
  $('#mSubmit').disabled = false;
  $('#mSubmit').textContent = 'Send request';
  $$('.field', overlay).forEach(f => f.classList.remove('bad'));
  overlay.hidden = false;
  requestAnimationFrame(() => overlay.classList.add('open'));
  document.documentElement.style.overflow = 'hidden';
  setTimeout(() => $('#fName').focus(), 200);
}
function closeModal(){
  overlay.classList.remove('open');
  document.documentElement.style.overflow = '';
  setTimeout(() => { overlay.hidden = true; }, 280);
  if(lastFocus) lastFocus.focus();
}
function composeMessage(){
  const m = MODELS[state.model];
  const extras = [...state.extras].map(id => EXTRAS.find(e => e.id === id).n.toLowerCase());
  return 'Hej Lumen — I\u2019d like to build a ' + m.name + ' (' + m.type.toLowerCase() + ') in ' +
    PAINTS[state.paint].n.toLowerCase() + ' with a ' + WOODS[state.wood].n.toLowerCase() + ' box' +
    (extras.length ? ', plus ' + extras.join(', ') : '') +
    '. Estimated price ' + money(total) + '.';
}
$$('[data-open]').forEach(b => b.addEventListener('click', () => openModal(b.dataset.open)));
$('#reqBtn').addEventListener('click', () => openModal('build'));
$('#visitBook').addEventListener('click', () => openModal('visit'));
$('#mClose').addEventListener('click', closeModal);
$('#sClose').addEventListener('click', closeModal);
overlay.addEventListener('click', e => { if(e.target === overlay) closeModal(); });
document.addEventListener('keydown', e => {
  if(e.key === 'Escape'){
    if(!overlay.hidden) closeModal();
    if(mnav.classList.contains('open')) toggleMenu(false);
  }
});
function bad(input, on){
  input.closest('.field').classList.toggle('bad', on);
}
$('#mForm').addEventListener('submit', e => {
  e.preventDefault();
  const name = $('#fName'), email = '#fEmail' && $('#fEmail');
  let ok = true;
  if(!name.value.trim()){ bad(name, true); ok = false; }
  if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)){ bad(email, true); ok = false; }
  if(!ok) return;
  const btn = $('#mSubmit');
  btn.disabled = true;
  btn.textContent = 'SENDING…';
  setTimeout(() => {
    $('#mForm').hidden = true;
    $('#mSuccess').hidden = false;
    $('#sTitle').textContent = 'Tak — request received.';
    $('#sText').textContent = 'We\u2019ll reply within two working days from hello@lumencycles.dk. Your configuration travels with the message, so the workshop can start drafting straight away.';
    toast('Request sent — talk soon.');
  }, 900);
});

const toastEl = $('#toast');
let toastT = null;
function toast(msg){
  $('#toastMsg').textContent = msg;
  toastEl.classList.add('show');
  clearTimeout(toastT);
  toastT = setTimeout(() => toastEl.classList.remove('show'), 4200);
}

/* ---------- voices ---------- */
const QUOTES = [
  {q:'Two kids, a week of groceries and a ladder. It climbs the bridge without me noticing any of it.', a:'Mette F. · Nørrebro · Lumen Lang, 2021'},
  {q:'It paid for itself against van hire in eight months. The box swallows tool chests whole.', a:'Jonas K. · Vesterbro · Lumen Frakt, 2023'},
  {q:'They spent two hours moving my saddle four millimetres. That\u2019s the whole review.', a:'Anders L. · Islands Brygge · Lumen Vogn, 2024'},
  {q:'Our Frakt has done 9,000 km of daycare runs. The paint has one stone chip. One.', a:'Signe & Mikkel · Østerbro · Lumen Frakt, 2019'}
];
const qBox = $('#quoteBox'), qText = $('#qText'), qAttr = $('#qAttr'), qIdx = $('#qIdx');
let qi = 0, qTimer = null;
function renderQuote(i){
  qi = (i + QUOTES.length) % QUOTES.length;
  qBox.classList.add('fade');
  setTimeout(() => {
    qText.textContent = '\u201C' + QUOTES[qi].q + '\u201D';
    qAttr.textContent = QUOTES[qi].a;
    qIdx.textContent = String(qi+1).padStart(2,'0') + ' / ' + String(QUOTES.length).padStart(2,'0');
    qBox.classList.remove('fade');
  }, 260);
}
function autoQuotes(){ clearInterval(qTimer); qTimer = setInterval(() => renderQuote(qi+1), 7000); }
$('#qPrev').addEventListener('click', () => { renderQuote(qi-1); autoQuotes(); });
$('#qNext').addEventListener('click', () => { renderQuote(qi+1); autoQuotes(); });
$('#voices').addEventListener('mouseenter', () => clearInterval(qTimer));
$('#voices').addEventListener('mouseleave', autoQuotes);
autoQuotes();

/* ---------- header, menu, reveal, nav state ---------- */
const head = $('#siteHead');
addEventListener('scroll', () => head.classList.toggle('scrolled', scrollY > 24), {passive:true});
function toggleMenu(open){
  mnav.classList.toggle('open', open);
  $('#menuBtn').setAttribute('aria-expanded', open);
  document.documentElement.style.overflow = open ? 'hidden' : '';
}
$('#menuBtn').addEventListener('click', () => toggleMenu(!mnav.classList.contains('open')));
$('#mnavClose').addEventListener('click', () => toggleMenu(false));
$$('.mnav a').forEach(a => a.addEventListener('click', () => toggleMenu(false)));

const io = new IntersectionObserver(es => es.forEach(en => {
  if(en.isIntersecting){ en.target.classList.add('in'); io.unobserve(en.target); }
}), {threshold:.12});
$$('.reveal').forEach(el => io.observe(el));

const nio = new IntersectionObserver(es => es.forEach(en => {
  const link = $('.site-nav a[href="#' + en.target.id + '"]');
  if(link) link.classList.toggle('cur', en.isIntersecting);
}), {rootMargin:'-40% 0px -55% 0px'});
['machines','build','process','voices','visit'].forEach(id => {
  const s = document.getElementById(id);
  if(s) nio.observe(s);
});
})();
</script>

<!-- ======= modal ======= -->
<div class="overlay" id="overlay" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle">
    <button class="modal-x" id="mClose" type="button" aria-label="Close">✕</button>
    <p class="kicker" style="margin-bottom:14px"><svg class="lamp-ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg><span id="mKicker">BUILD REQUEST</span></p>
    <h3 id="mTitle" style="font-size:1.7rem;margin-bottom:20px">Request this build</h3>
    <div class="m-summary mono" id="mSummary"></div>
    <form id="mForm" novalidate>
      <label class="field"><span class="f-label">Name</span>
        <input id="fName" type="text" autocomplete="name">
        <span class="err">Please add your name.</span></label>
      <label class="field"><span class="f-label">Email</span>
        <input id="fEmail" type="email" autocomplete="email">
        <span class="err">That email doesn't look right.</span></label>
      <label class="field"><span class="f-label">Message</span>
        <textarea id="fMsg" rows="4"></textarea></label>
      <button class="btn btn-accent" id="mSubmit" type="submit" style="width:100%;justify-content:center">Send request</button>
      <p class="form-note mono" style="margin-top:14px">WE REPLY WITHIN TWO WORKING DAYS · NO NEWSLETTERS, EVER</p>
    </form>
    <div class="m-success" id="mSuccess" hidden>
      <svg viewBox="0 0 24 24" width="42" height="42" style="margin:0 auto 18px;color:var(--accent)" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4M5.2 5.2l2.4 2.4M16.4 16.4l2.4 2.4M18.8 5.2l-2.4 2.4M7.6 16.4l-2.4 2.4"/></g></svg>
      <h3 id="sTitle" style="font-size:1.5rem;margin-bottom:12px">Tak — request received.</h3>
      <p id="sText"></p>
      <button class="btn" id="sClose" type="button" style="margin-top:8px">Close</button>
    </div>
  </div>
</div>

<div class="toast" id="toast" role="status">
  <svg viewBox="0 0 24 24" width="14" height="14" style="color:var(--accent);flex:none" aria-hidden="true"><circle cx="12" cy="12" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M12 2.4v3.4M12 18.2v3.4M2.4 12h3.4M18.2 12h3.4"/></g></svg>
  <span id="toastMsg"></span>
</div>

</body>
</html>

Compare with

GLM 5.3 Flash: other attempts