@font-face{font-family:Fredoka;src:url("fonts/fredoka-600.ttf") format("truetype");font-weight:600;font-style:normal;font-display:swap}@font-face{font-family:Fredoka;src:url("fonts/fredoka-700.ttf") format("truetype");font-weight:700;font-style:normal;font-display:swap}@font-face{font-family:Rubik;src:url("fonts/rubik-400.ttf") format("truetype");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:Rubik;src:url("fonts/rubik-500.ttf") format("truetype");font-weight:500;font-style:normal;font-display:swap}@font-face{font-family:Rubik;src:url("fonts/rubik-700.ttf") format("truetype");font-weight:700;font-style:normal;font-display:swap}:root{--orange:#fa6d0f;--deep:#be4e04;--ink:#26231f;--green:#63b23a;--blue:#477eff;--cream:#fffaf1;--muted:#716b62;--line:#e8e0d4;--display:Fredoka,ui-rounded,"Arial Rounded MT Bold",Arial,sans-serif;--body:Rubik,"Avenir Next",Avenir,Arial,sans-serif}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font-family:var(--body);color:var(--ink);background:var(--cream)}a:focus-visible{outline:3px solid var(--blue);outline-offset:4px;border-radius:4px}.hero{height:100svh;min-height:min(700px,max(100svh,560px));position:relative;overflow:hidden;background:#bee3f2}.hill{position:absolute;width:125%;left:-12%;border-radius:50% 50% 0 0}.far{height:55%;bottom:8%;background:#8fd457;transform:rotate(-4deg)}.mid{height:46%;bottom:-2%;left:-2%;background:#79c446;transform:rotate(5deg)}.near{height:34%;bottom:-12%;background:var(--green);transform:rotate(-2deg)}.cloud{position:absolute;width:105px;height:29px;border-radius:40px;background:white;top:14%;left:42%;animation:cloudFloat 7s ease-in-out infinite}.cloud:before,.cloud:after{content:"";position:absolute;background:white;border-radius:50%;bottom:0;width:48px;height:48px;left:17px}.cloud:after{width:37px;height:37px;left:55px}.cloud-b{left:auto;right:5%;top:9%;transform:scale(.65);animation:cloudFloatSmall 9s ease-in-out -2s infinite}header{position:relative;z-index:10;width:min(1240px,calc(100% - 64px));height:96px;margin:auto;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{--mark:1.034em;--gap:7px;display:flex;align-items:flex-start;gap:0;color:var(--ink);text-decoration:none;font:700 41.6px/1 var(--display);text-shadow:0 4px 0 rgba(53,122,34,.18)}.brand img{width:var(--mark);height:var(--mark);margin-top:.167em;margin-left:calc(var(--mark) * -.2499);margin-right:calc(var(--gap) - var(--mark) * .2117);filter:drop-shadow(0 4px 0 rgba(53,122,34,.18))}.layout{position:relative;z-index:5;width:min(1240px,calc(100% - 64px));height:calc(100% - 96px);margin:auto;display:grid;grid-template-columns:.88fr 1.12fr;align-items:center}.copy{padding-bottom:7vh}.copy h1{font:700 clamp(61px,6.7vw,94px)/.85 var(--display);letter-spacing:-4px;margin:0 0 26px}.copy h1 em{font-style:normal;color:var(--orange);text-shadow:0 5px 0 rgba(190,78,4,.15)}.copy>p{max-width:500px;color:#3b4d2f;font-size:16px;line-height:1.7}.science-link{color:inherit;font-weight:500;white-space:nowrap;text-decoration:underline;text-decoration-color:var(--orange);text-decoration-thickness:2px;text-underline-offset:3px}.science-link:hover{color:var(--deep);text-decoration-color:var(--deep)}.download{position:relative;isolation:isolate;overflow:hidden;display:inline-flex;align-items:center;gap:18px;margin-top:27px;padding:10px 12px 10px 19px;border-radius:16px;background:var(--orange);box-shadow:0 6px 0 var(--deep),0 0 0 0 rgba(250,109,15,.24);color:white;text-decoration:none;font-size:14px;font-weight:700;animation:ctaFocus 3.2s ease-in-out infinite}.download:after{content:"";position:absolute;z-index:-1;top:-30%;bottom:-30%;width:38px;left:-65px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.34),transparent);transform:skewX(-18deg);animation:ctaShine 4.2s ease-in-out infinite}.stores{display:flex;flex-wrap:wrap;gap:12px;margin-top:27px}.stores .download{margin-top:0;padding:16px 22px}.store-mark{display:flex;align-items:center;gap:8px}.store-mark>img{width:22px;height:22px}.store-mark i{font-style:normal;text-align:left;line-height:1}.store-mark small,.store-mark b{display:block}.store-mark small{font-size:7px;letter-spacing:.5px;opacity:.85}.store-mark b{font-size:12px;margin-top:2px;white-space:nowrap}.download:hover{animation-play-state:paused;transform:translateY(2px);box-shadow:0 4px 0 var(--deep)}.showcase{height:100%;position:relative;display:grid;place-items:end center}.phone{position:relative;width:min(350px,37vw);height:min(730px,calc(100vh - 112px));background:#26231f;border:9px solid #26231f;border-radius:47px 47px 0 0;box-shadow:23px 28px 0 rgba(53,122,34,.34),35px 45px 65px rgba(38,35,31,.25);overflow:hidden;transform:rotate(1.5deg)}.phone img{position:absolute;inset:17px 0 0;width:100%;height:calc(100% - 17px);object-fit:cover;object-position:top;border-radius:30px 30px 0 0}.speaker{position:absolute;z-index:3;top:5px;left:50%;transform:translateX(-50%);width:78px;height:7px;border-radius:8px;background:#57534c}.feature{--r:0deg;--line:70px;position:absolute;z-index:4;display:flex;align-items:center;gap:10px;background:white;border:1px solid #e3dfd8;border-radius:16px;padding:11px 14px;box-shadow:0 6px 0 #e3dfd8;animation:pillFloat 4s ease-in-out infinite;transform:rotate(var(--r))}.feature:after{content:"";position:absolute;z-index:-1;top:50%;height:2px;width:var(--line);background:rgba(53,122,34,.45);transform:translateY(-50%)}.feature:before{content:"";position:absolute;z-index:2;top:50%;width:7px;height:7px;border:2px solid white;border-radius:50%;background:#63b23a;transform:translateY(-50%);box-shadow:0 0 0 1px rgba(53,122,34,.35)}.feature p{margin:0;white-space:nowrap}.feature small,.feature strong{display:block}.feature small{font-size:7px;letter-spacing:.85px;color:#a8a299;font-weight:700}.feature strong{font:600 13px var(--display);margin-top:3px}.icon{display:grid;place-items:center;width:36px;height:36px;border-radius:11px;color:white;font-style:normal;font-weight:700}.icon svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.orange{background:var(--orange)}.green{background:#1fc173}.blue{background:var(--blue)}.yellow{background:#ffc914}.coral{background:#fc9a5b}.mock{position:relative;width:max-content;margin:0 auto;--bite:14px}.mock .feature{top:var(--y)}.mock .feature.r{left:calc(100% - var(--bite))}.mock .feature.l{right:calc(100% - var(--bite))}.feature.r:after{right:100%}.feature.r:before{right:calc(100% + var(--line) - 4px)}.feature.l:after{left:100%}.feature.l:before{left:calc(100% + var(--line) - 4px)}/* ...which the rule near the top of this file switches off. Kept so that turning the connector back on needs no second edit. */.scenery{position:absolute;inset:0;z-index:2;pointer-events:none}.prop{position:absolute;display:block;height:auto;width:calc(var(--w) * var(--s,1) * 1%);bottom:calc(var(--b) * 1%);transform:translateX(-50%) rotate(calc(var(--t) * 1deg));transform-origin:50% 100%;filter:var(--haze,) drop-shadow(0 3px 0 rgba(53,122,34,.14))}.d-back{--haze:saturate(.42) brightness(1.14);opacity:.8}.d-far{--haze:saturate(.72) brightness(1.06);opacity:.93}.d-back:after,.d-far:after{opacity:.5}.a-sway{animation:propSway var(--dur,9s) ease-in-out var(--delay,0s) infinite}.a-hop{animation:propHop var(--dur,5.5s) ease-in-out var(--delay,0s) infinite}.a-circle{animation:propCircle var(--dur,9s) ease-in-out var(--delay,0s) infinite}.a-bend{animation:propBend var(--dur,7s) ease-in-out var(--delay,0s) infinite}.a-crawl{animation:propCrawl var(--dur,46s) linear var(--delay,0s) infinite}
@keyframes cloudFloat{0%,100%{transform:translate3d(0,0,0)}50%{transform:translate3d(16px,-7px,0)}}@keyframes cloudFloatSmall{0%,100%{transform:scale(.65) translate3d(0,0,0)}50%{transform:scale(.65) translate3d(-20px,-9px,0)}}@keyframes pillFloat{0%,100%{transform:rotate(var(--r)) translateY(0)}50%{transform:rotate(var(--r)) translateY(-7px)}}@keyframes ctaFocus{0%,100%{box-shadow:0 6px 0 var(--deep),0 0 0 0 rgba(250,109,15,.28)}50%{box-shadow:0 6px 0 var(--deep),0 0 0 9px rgba(250,109,15,0)}}@keyframes ctaShine{0%,58%{left:-65px}82%,100%{left:120%}}@keyframes propSway{0%,100%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg))}50%{transform:translateX(-50%) rotate(calc((var(--t) + 1.6) * 1deg))}}@keyframes propHop{0%,72%,100%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateY(0)}80%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateY(-18%)}88%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateY(-4%)}}@keyframes propCircle{0%,100%{transform:translateX(-50%) translate(0,0)}25%{transform:translateX(-50%) translate(330%,-200%)}50%{transform:translateX(-50%) translate(600%,70%)}75%{transform:translateX(-50%) translate(270%,270%)}}@keyframes propBend{0%,100%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg))}32%{transform:translateX(-50%) rotate(calc((var(--t) + var(--bend,3)) * 1deg))}66%{transform:translateX(-50%) rotate(calc((var(--t) - var(--bend,3) * .55) * 1deg))}}@keyframes propCrawl{0%,9%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(0)}17%,26%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(65%)}34%,43%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(130%)}49%,53%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(195%)}55%,62%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(195%) scaleX(-1)}70%,79%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(130%) scaleX(-1)}87%,92%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(65%) scaleX(-1)}96%,100%{transform:translateX(-50%) rotate(calc(var(--t) * 1deg)) translateX(0) scaleX(-1)}}@media(prefers-reduced-motion:reduce){.cloud,.cloud-b,.feature,.download,.download:after,.prop{animation:none}}
@media(max-width:800px){.hero{--head:clamp(54px,7.6svh,76px);height:100vh;height:100svh;min-height:520px}header{width:calc(100% - 32px);height:auto;min-height:var(--head);flex-wrap:wrap;gap:0}.brand{font-size:clamp(26px,6.4vw,31px);--gap:6px;height:var(--head);padding-top:calc((var(--head) - .167em - var(--mark))/2)}.layout{width:calc(100% - 32px);height:calc(100% - var(--head));min-height:0;grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr);text-align:center;align-items:start}.copy{padding:0 0 clamp(6px,1.6svh,16px);align-self:start}.copy h1{font-size:min(12vw,6.4svh,58px);letter-spacing:-2px;margin:0 0 clamp(7px,1.5svh,18px)}.copy>p{font-size:clamp(12.5px,3.4vw,14px);line-height:1.5;max-width:40ch;margin-inline:auto}.stores{margin-top:clamp(9px,2.1svh,19px);gap:9px;justify-content:center}.stores .download{padding:14px 18px}.store-mark{gap:7px}.store-mark>img{width:22px;height:22px}.store-mark b{font-size:12.5px}.showcase{position:relative;left:auto;right:auto;bottom:auto;height:100%;min-height:0;place-items:end center}.phone{width:auto;height:100%;aspect-ratio:var(--shot-ar);max-width:min(50vw,215px);max-height:calc(min(50vw,215px)/var(--shot-ar));border-width:7px;border-radius:36px 36px 0 0}.speaker{width:58px;height:6px}.cloud-a{left:70%;top:20%}.scenery{--s:2.3}.prop.m-off{display:none}}
@media(max-width:430px){.copy h1{font-size:min(13vw,6.6svh,51px)}.copy>p{max-width:32ch}.phone{max-width:min(52vw,205px)}}

/* Layered sky: clouds cross the full viewport at varied speeds and heights. */
.cloud-stream{position:absolute;z-index:1;inset:0;overflow:hidden;pointer-events:none}.cloud-stream .cloud{--scale:1;--boost:1;--size:calc(var(--scale) * var(--boost));--duration:34s;--delay:0s;--y:12%;position:absolute;top:var(--y);left:-190px;right:auto;width:105px;height:29px;opacity:.92;filter:drop-shadow(0 4px 0 rgba(53,122,34,.06));animation:cloudTravel var(--duration) linear var(--delay) infinite}.cloud-1{--scale:.55!important;--duration:31s!important;--delay:-20s!important;--y:7%!important;opacity:.78!important}.cloud-2{--scale:.9!important;--duration:39s!important;--delay:-4s!important;--y:14%!important}.cloud-3{--scale:.42!important;--duration:27s!important;--delay:-13s!important;--y:23%!important;opacity:.68!important}.cloud-4{--scale:1.12!important;--duration:48s!important;--delay:-35s!important;--y:31%!important}.cloud-5{--scale:.68!important;--duration:35s!important;--delay:-27s!important;--y:40%!important;opacity:.82!important}.cloud-6{--scale:.48!important;--duration:30s!important;--delay:-7s!important;--y:50%!important;opacity:.7!important}.cloud-7{--scale:.8!important;--duration:43s!important;--delay:-18s!important;--y:18%!important;opacity:.86!important}.cloud-8{--scale:.36!important;--duration:25s!important;--delay:-23s!important;--y:36%!important;opacity:.62!important}.cloud-9{--scale:.6!important;--duration:37s!important;--delay:-10s!important;--y:27%!important;opacity:.76!important}@keyframes cloudTravel{0%{transform:translate3d(0,0,0) scale(var(--size))}25%{transform:translate3d(calc(25vw + 95px),-7px,0) scale(var(--size))}50%{transform:translate3d(calc(50vw + 190px),3px,0) scale(var(--size))}75%{transform:translate3d(calc(75vw + 285px),-5px,0) scale(var(--size))}100%{transform:translate3d(calc(100vw + 380px),0,0) scale(var(--size))}}

/* Callouts float freely without connector lines or anchor dots. */
.feature:before,.feature:after{display:none!important}

@media(prefers-reduced-motion:reduce){.cloud-stream .cloud{animation:none;left:18%}.cloud-stream .cloud:nth-child(2n){left:68%}.cloud-stream .cloud:nth-child(n+5){display:none}.root-hero .cloud-stream .cloud{left:18%;transform:scale(var(--size))}.root-hero .cloud-stream .cloud:nth-child(2n){left:58%}}

/* The landscape masks clouds whenever their path reaches the green area. */
.cloud-stream{z-index:0}.hill{z-index:1}

/* App screenshots cross-fade inside the phone without shifting its frame. */
.phone-screen{position:absolute;inset:17px 0 0;border-radius:30px 30px 0 0;overflow:hidden;background:#f7f3ea}.phone .screen{inset:0;width:100%;height:100%;object-fit:cover;object-position:top;border-radius:0;opacity:0;transform:scale(1.012);transition:opacity .42s ease,transform .62s ease}.phone .screen.active{opacity:1;transform:scale(1)}
@media(prefers-reduced-motion:reduce){.phone .screen{transition:none;transform:none}}
/* The phone shrinks on small screens, so its bezel and inner radius follow. */
@media(max-width:800px){.phone-screen{inset:13px 0 0;border-radius:25px 25px 0 0}}
/* From tablet up the whole device is on screen: a closed frame (all four
   corners, a bottom bezel with its home bar) lifted clear of the hero's
   edge. Below 801px it keeps rising out of the bottom, cropped by design.
   The height drives the width so a short window narrows the phone instead
   of squashing it — but through the SHOTS' OWN shape, not a fixed
   aspect-ratio: `--shot-ar` is the screenshot aspect build.py measured for
   this page, and the width is what makes the screen box exactly that
   shape, chrome subtracted (18px of border across, 53px of border and
   bezel down). A hardcoded 350/730 was 0.479 against shots of 0.471, and
   `object-fit:cover` paid the difference by eating 8-10% off the BOTTOM of
   every screenshot — the drill's Check button, the tab bar, the home
   indicator. It cannot be one number for every page either: German's
   shots come off the review desk at 702x1520 and a capture walk's are
   702x1489, so each language's phone wears its own. */
@media(min-width:801px){.showcase{padding-bottom:clamp(24px,4.4svh,52px);--phone-h:min(740px,calc(100svh - 166px));--phone-w:calc((var(--phone-h) - 53px)*var(--shot-ar) + 18px)}.phone{width:var(--phone-w);height:var(--phone-h);border-radius:44px}.phone-screen{inset:16px 0 19px;border-radius:29px}.phone:after{content:"";position:absolute;z-index:3;bottom:6px;left:50%;transform:translateX(-50%);width:34%;height:5px;border-radius:3px;background:#57534c}}
/* A short landscape screen cannot fit the composition; let it scroll instead. */
@media(max-width:800px) and (max-height:460px){.hero{height:auto;min-height:0}.layout{height:auto;grid-template-rows:auto}.showcase{height:420px}.copy{padding-block:18px}}

/* Quiet footer, deliberately outside the hero so it never competes with the CTA. */
.site-footer{min-height:118px;padding:30px max(32px,calc((100% - 1240px)/2));display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:34px;background:var(--orange);font-size:12px}.footer-brand{display:flex;align-items:center;gap:8px;color:rgba(255,255,255,.52);font:700 23px var(--display);text-decoration:none}.site-footer nav{display:flex;justify-content:center;flex-wrap:wrap;gap:10px 22px}.site-footer nav a{color:#fff;font-weight:500;text-transform:uppercase;letter-spacing:.07em;text-decoration:none}.site-footer nav a:hover{color:rgba(255,255,255,.72)}.site-footer>p{justify-self:center;margin:0;padding:9px 15px;border-radius:30px;background:#fff;box-shadow:0 4px 0 rgba(140,56,2,.3);color:var(--ink);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap}.site-footer a:focus-visible{outline-color:#fff}

/* Legal pages share the brand, but leave the content to do the work. */
.legal-page{background:var(--cream)}speeku-legal-header{display:block;min-height:88px}.legal-header{height:88px;width:min(1100px,calc(100% - 48px));justify-content:space-between;border-bottom:1px solid var(--line)}.legal-header .download{margin:0;animation:none;padding:12px 18px;box-shadow:0 4px 0 var(--deep)}.legal-main{width:min(760px,calc(100% - 48px));margin:0 auto;padding:72px 0 96px}.legal-eyebrow{margin:0 0 12px;color:#357a22;font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}.legal-main h1{font:700 clamp(44px,7vw,68px)/1 var(--display);letter-spacing:-2px;margin:0}.legal-updated{margin:16px 0 48px;color:var(--muted);font-size:14px}.legal-lead{font-size:20px;line-height:1.6;color:#3e4c35}.legal-main h2{font:700 24px/1.25 var(--display);margin:48px 0 14px}.legal-main h3{font:700 17px/1.4 var(--display);margin:28px 0 8px}.legal-main p,.legal-main li{font-size:16px;line-height:1.75;color:#4f4a43}.legal-main ul{padding-left:22px}.legal-main a{color:#317824;text-underline-offset:3px}.privacy-promise{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:36px 0 50px}.privacy-promise div{padding:20px;border:1px solid #dce9d2;border-radius:16px;background:#f4faef}.privacy-promise strong{display:block;font:700 16px var(--display);color:#357a22;margin-bottom:6px}.privacy-promise span{font-size:13px;line-height:1.5;color:#5b6754}.policy-table{width:100%;border-collapse:collapse;margin:24px 0}.policy-table th,.policy-table td{text-align:left;vertical-align:top;padding:15px 14px;border-bottom:1px solid var(--line);font-size:14px;line-height:1.55}.policy-table th{color:#357a22;font-size:11px;letter-spacing:.06em;text-transform:uppercase}.legal-note{padding:18px 20px;border-left:4px solid var(--orange);border-radius:0 12px 12px 0;background:#fff1e7}.legal-note p{margin:0}.legal-footer{min-height:auto}.legal-contact{display:inline-flex;margin-top:10px;padding:12px 16px;border-radius:12px;background:#eff7e9;font-weight:700;text-decoration:none}

@media(max-width:700px){.site-footer{grid-template-columns:1fr;text-align:center;gap:20px}.footer-brand{justify-content:center}.site-footer>p{white-space:normal}.legal-header{width:calc(100% - 32px);height:76px}.legal-header .brand{font-size:26px}.legal-header .download{font-size:12px;padding:10px 13px}.legal-main{width:calc(100% - 36px);padding:48px 0 72px}.privacy-promise{grid-template-columns:1fr}.policy-table{display:block;overflow-x:auto}.legal-footer .footer-brand{display:none}}

/* Acknowledgements: one expandable row per credit, its licence text inside. */
.ack-list{margin:22px 0 44px;border:1px solid var(--line);border-radius:16px;background:#fff;overflow:hidden}.ack-item{border-top:1px solid var(--line)}.ack-item:first-child{border-top:0}.ack-item>summary{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:5px 14px;padding:16px 46px 16px 20px;cursor:pointer;list-style:none}.ack-item>summary::-webkit-details-marker{display:none}.ack-item>summary:hover{background:#fffaf1}.ack-item>summary:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}.ack-item>summary:after{content:"";position:absolute;right:21px;top:50%;width:9px;height:9px;border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:translateY(-70%) rotate(45deg);transition:transform .18s ease}.ack-item[open]>summary:after{transform:translateY(-25%) rotate(225deg)}.ack-name{font:600 16px var(--display);color:var(--ink)}.ack-by{flex:1;min-width:130px;font-size:13px;color:var(--muted)}.ack-lic{padding:3px 10px;border-radius:20px;background:#fff1e7;color:var(--deep);font-size:11px;font-weight:500;white-space:nowrap}.ack-body{padding:0 20px 20px}.legal-main .ack-link{margin:0 0 12px;font-size:13px;word-break:break-word}.ack-text{margin:0;max-height:330px;overflow:auto;padding:16px 18px;border:1px solid var(--line);border-radius:12px;background:var(--cream);font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.65;color:#4f4a43;white-space:pre-wrap}
@media(prefers-reduced-motion:reduce){.ack-item>summary:after{transition:none}}
@media(max-width:700px){.ack-item>summary{padding:14px 40px 14px 16px}.ack-by{flex-basis:100%;min-width:0}.ack-body{padding:0 16px 16px}.ack-text{max-height:280px;font-size:11.5px;padding:13px 14px}}

/* Backed by science: the method page. Panel-shaped rather than essay-shaped:
   each section is a short headline, one line, and a graphic. Every chart is
   fluid HTML/CSS rather than a fixed-width drawing, so a phone reflows one
   instead of scrolling it sideways; the only SVG is the growth line's path,
   drawn in a stretched viewBox with a non-scaling stroke, its labels HTML
   around it. Chart colours are the brand hues re-stepped to clear 3:1 on the
   card and to stay apart under protanopia and deuteranopia.
   A CHART CARRIES ONLY ITS TITLE AND ITS AXES: a figcaption saying what is
   plotted and over what data, one label per axis, nothing else. The prose
   that argues from a chart (what it means, which paper it comes from) lives
   in the panel's copy beside it, so the reading order is argument then
   evidence rather than a caption nobody reads twice. */
.science-page{background:var(--cream)}
.sci{width:min(1100px,calc(100% - 48px));margin:0 auto;padding-bottom:72px}
.eyebrow{margin:0 0 14px;color:#357a22;font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}

/* Hero: headline left, the ladder right. */
.sci-hero{display:grid;grid-template-columns:1fr .92fr;align-items:center;gap:48px;padding:72px 0 64px}
.sci-hero h1{margin:0;font:700 clamp(44px,5.6vw,74px)/.98 var(--display);letter-spacing:-2.5px;color:var(--ink)}
.sci-hero h1 em{font-style:normal;color:var(--orange);text-shadow:0 5px 0 rgba(190,78,4,.14)}
.hero-lead{margin:22px 0 0;max-width:30em;font-size:18px;line-height:1.6;color:#4f4a43}
.hero-lead+.hero-lead{margin-top:14px;font-size:16.5px}
.hero-meta{margin:14px 0 0;font-size:14px;color:var(--muted)}
.hero-art{text-align:center}
.ladder ol{margin:0;padding:0;list-style:none}
.lad-cols{display:flex;align-items:stretch;gap:14px;height:280px}
/* Each icon rides on top of its own bar, so the strip above the tallest rung
   is reserved as padding: the four bars are then % of ONE track (equal steps)
   and no bar can push its icon out of the box. */
.lad-cols li{position:relative;flex:1;height:100%;padding-top:58px;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;align-items:center}
.lad-cols i{position:relative;display:block;width:100%;border-radius:14px}
.lad-cols img{position:absolute;left:50%;bottom:calc(100% + 12px);transform:translateX(-50%)}
.lad-cols b{position:absolute;top:12px;left:0;right:0;text-align:center;color:#fff;font-size:15px;font-weight:700}
.lad-labs{display:flex;gap:14px;margin-top:12px}
.lad-labs li{flex:1;text-align:center;font-size:14px;font-weight:500;color:var(--ink)}

/* The number band. */
.band{padding:0 0 8px}
.band-title{margin:0 0 16px;font:700 clamp(22px,2.4vw,28px)/1.2 var(--display);letter-spacing:-.6px;color:var(--ink)}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
.stat{padding:20px 18px;border:1px solid var(--line);border-radius:16px;background:#fff}
.stat img{display:block;margin-bottom:12px}
.stat strong{display:block;font:600 32px/1 var(--body);letter-spacing:-1px;color:var(--orange)}
.stat-l{display:block;margin-top:8px;font-size:13.5px;font-weight:500;color:var(--ink)}
.stat-s{display:block;margin-top:4px;font-size:12px;color:var(--muted)}

/* Panels: a centred header, then one graphic. */
.panel{padding:76px 0 0}
.panel+.panel{margin-top:76px;border-top:1px solid var(--line)}
.p-head{max-width:660px;margin:0 auto 34px;text-align:center}
.refs-head{max-width:none;margin-bottom:8px;text-align:left}
/* The panel icon floats on the page's own ground: no card, no frame. */
.p-icon{display:block;width:104px;height:104px;margin:0 auto 16px}
.p-head h2{margin:0;font:700 clamp(28px,3.4vw,40px)/1.12 var(--display);letter-spacing:-1px;color:var(--ink)}
.p-head p{margin:16px 0 0;font-size:16.5px;line-height:1.65;color:#4f4a43}
/* A panel that argues in paragraphs reads left-aligned; only its head is centred. */
.p-head.copy-left p{text-align:left}

/* Split panels: copy one side, the graphic the other. */
.panel.split{display:grid;grid-template-columns:.86fr 1.14fr;align-items:center;gap:56px}
.panel.split.flip .s-copy{order:2}
.panel.split h2{margin:0;font:700 clamp(28px,3.2vw,38px)/1.12 var(--display);letter-spacing:-1px;color:var(--ink)}
.panel.split .s-copy>p{margin:16px 0 0;font-size:16.5px;line-height:1.65;color:#4f4a43}
.s-art{min-width:0}

/* Figure frame: the title, left-aligned above the plot. */
.fig{margin:0;padding:24px 26px 20px;border:1px solid var(--line);border-radius:20px;background:#fff}
.fig figcaption{margin-bottom:20px;text-align:left}
.fig figcaption strong{display:block;font:600 17.5px/1.35 var(--display);color:var(--ink)}
.fig figcaption span{display:block;margin-top:5px;font-size:13px;line-height:1.5;color:var(--muted)}
.fig ol{margin:0;padding:0;list-style:none}

/* Axis titles: the y one above its own scale, the x one under its labels. */
.fig .c-ytitle{margin:0 0 9px;text-align:left;font-size:11.5px;line-height:1.35;color:var(--muted)}
.fig .c-xtitle{margin:14px 0 0;padding-left:46px;text-align:center;font-size:11.5px;line-height:1.4;color:var(--muted)}

/* Shared: a gridline and its y-axis label. */
.c-plot,.l-plot{position:relative;height:230px;padding-left:46px}
.g{position:absolute;left:46px;right:0;height:0;border-top:1px solid #eee4d6}
.g b{position:absolute;right:calc(100% + 10px);top:-8px;font-size:11.5px;font-weight:400;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}

/* Columns. */
.c-bars{position:absolute;inset:0 0 0 46px;display:flex;align-items:flex-end}
.c-bars li{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;height:100%}
.c-bars i{width:min(26px,58%);border-radius:4px 4px 0 0}
.c-bars .v{margin-bottom:7px;font-size:12.5px;font-weight:500;color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap}
.fig .c-labs{display:flex;margin:10px 0 0;padding-left:46px}
.c-labs li{flex:1;min-width:0;padding:0 3px;text-align:center;font-size:12.5px;color:var(--ink)}
.c-labs li img{display:block;margin:0 auto 8px}
.c-labs li span{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--muted)}

/* Screenshots: the drills themselves, side by side. */
.shots{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
.shots figure{margin:0}
.shots img{display:block;width:100%;height:auto;border:1px solid var(--line);border-radius:16px;background:#fff}
.shots figcaption{margin-top:12px;font-size:13px;font-weight:500;color:var(--ink)}

/* Meters. */
.mchart li{padding:14px 0 18px}
.mchart li+li{border-top:1px solid #f3ece1}
.m-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.m-head b{font-size:15px;font-weight:400;color:var(--ink)}
.p-head a,.panel.split .s-copy a{color:#317824;text-underline-offset:3px}
.m-head em{font-style:normal;font-size:13.5px;font-weight:500;color:var(--ink);white-space:nowrap;font-variant-numeric:tabular-nums}
.m-track{display:block;height:14px;margin-top:11px;border-radius:7px;background:#f6ead9}
.m-track i{display:block;height:100%;border-radius:7px;background:#dc690a}

/* The growth line. Everything placed by PERCENTAGE lives inside .l-area (the
   plot area minus the axis gutter) because a percentage in .l-plot resolves
   against the gutter too, which slid the labels, the A1/A2 divider and the end
   dot a gutter's width left of the data they mark. */
.l-plot{height:250px}
.l-area{position:absolute;inset:0 0 0 46px}
.l-path{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.l-split{position:absolute;top:0;bottom:0;width:1px;background:rgba(38,35,31,.2)}
.l-split b{position:absolute;top:6px;font-size:11.5px;font-weight:400;color:var(--muted)}
.l-split .a{right:9px}
.l-split .b{left:9px}
.l-dot{position:absolute;width:10px;height:10px;margin:0 0 -5px -8px;border-radius:50%;background:#dc690a;box-shadow:0 0 0 2px #fff}
.l-end{position:absolute;right:0;font-size:12.5px;font-weight:500;color:var(--ink);white-space:nowrap}
.fig .l-labs{position:relative;height:18px;margin:10px 0 0 46px}
.l-labs li{position:absolute;transform:translateX(-50%);font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* The reading list. */
.sci ol.papers{margin:0;padding:0;list-style:none}
.papers li{padding:20px 0;border-top:1px solid var(--line)}
.papers li:first-child{border-top:0}
.paper-cite{margin:0;font-size:12px;font-weight:500;letter-spacing:.02em;color:var(--muted)}
.paper-title{margin:6px 0 7px;font:600 15.5px/1.4 var(--display)}
.paper-title a{color:#317824;text-underline-offset:3px}
.paper-what{margin:0;font-size:14px;line-height:1.55;color:#5c574f}

/* Closing. */
.sci-cta{margin-top:80px;padding:60px 40px;border-radius:26px;background:var(--orange);text-align:center;color:#fff}
.sci-cta h2{margin:0;font:700 clamp(26px,3.4vw,40px)/1.15 var(--display);letter-spacing:-1px}
.sci-cta p{margin:20px auto 0;max-width:50em;font-size:16px;line-height:1.65;color:rgba(255,255,255,.92)}
.sci-cta .download{margin-top:30px;padding:15px 26px;background:#fff;color:var(--deep);box-shadow:0 6px 0 rgba(140,56,2,.35);animation:none}
.sci-cta .download:hover{transform:translateY(2px);box-shadow:0 4px 0 rgba(140,56,2,.35)}

/* Tablet: a split panel goes 50/50 as soon as the page is narrower than its
   own 1100px measure. The desktop 46:54 leaves a chart too thin to read on an
   iPad, and the chart is the half that cannot reflow; the copy beside it can. */
@media(max-width:1100px){
  .sci{width:min(1100px,calc(100% - 36px))}
  .panel{padding-top:64px}.panel+.panel{margin-top:64px}
  .panel.split{grid-template-columns:1fr 1fr;gap:34px}
  .stat-row{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
  .fig{padding:20px 18px 16px}
  .c-plot,.l-plot{height:208px}
  .shots{grid-template-columns:repeat(2,1fr);gap:16px}
}
@media(max-width:900px){
  .sci-hero{grid-template-columns:1fr;gap:34px;padding:52px 0 46px;text-align:center}
  .hero-lead{margin-inline:auto}
  .ladder{max-width:460px;margin:0 auto}
}
/* Phone: one column, copy above its chart. */
@media(max-width:760px){
  .sci{width:calc(100% - 32px)}
  .panel.split{grid-template-columns:1fr;gap:26px;text-align:center}
  .panel.split.flip .s-copy{order:0}
  .panel.split .s-copy{max-width:660px;margin:0 auto}
  .panel{padding-top:54px}.panel+.panel{margin-top:54px}
  .p-head{margin-bottom:26px}
  .p-head p{font-size:15px}
  .fig{padding:18px 16px 16px;border-radius:16px}
  .stat{padding:16px 14px}.stat strong{font-size:26px}
  .c-plot,.l-plot{padding-left:38px;height:190px}
  .g{left:38px}.g b{right:calc(100% + 7px);font-size:10.5px}
  .c-bars{left:38px}.c-bars i{width:min(20px,64%)}
  .c-bars .v{font-size:10.5px;margin-bottom:5px}
  .fig .c-labs,.fig .c-xtitle{padding-left:38px}
  .fig .l-labs{margin-left:38px}
  .l-area{left:38px}
  .fig figcaption{margin-bottom:16px}
  .fig figcaption strong{font-size:16px}
  .fig figcaption span{font-size:12.5px}
  .fig .c-ytitle{font-size:10.5px;margin-bottom:7px}
  .c-labs li{font-size:11px;padding:0 2px}
  .c-labs li img{width:30px;height:30px;margin-bottom:6px}
  .c-labs li span{font-size:9.5px}
  .l-labs li{font-size:10.5px}
  .shots{grid-template-columns:repeat(2,1fr);gap:14px}
  .p-icon{width:76px;height:76px;margin-bottom:12px}
  .band-title{margin-bottom:12px}
  .lad-cols{height:210px;gap:8px}
  .lad-cols li{padding-top:46px}
  .lad-cols img{width:34px;height:34px;bottom:calc(100% + 8px)}
  .lad-labs{gap:8px}
  .lad-labs li{font-size:12.5px}
  .sci-cta{margin-top:56px;padding:44px 22px;border-radius:22px}
}

/* The language tabs IN the header — one per language APP, written by
   build.py. Active = the page you are on; the row exists so three separate
   apps stop looking like one app with a setting. They ride the topbar's
   right end, opposite the mark, inside the header's own width — so they
   cost the hero (100svh) no height at all and never reach the scrollbar. */
.langs{position:relative;z-index:9;display:flex;justify-content:flex-end;align-items:center;gap:12px}
.langs a{display:inline-flex;align-items:center;gap:9px;padding:7px 15px 7px 8px;border-radius:30px;background:#fff;border:1px solid #e3dfd8;box-shadow:0 4px 0 #e3dfd8;color:var(--ink);text-decoration:none;font:600 15px var(--display)}
.langs a img{width:30px;height:30px;border-radius:50%}
.langs a:hover{transform:translateY(1px);box-shadow:0 3px 0 #e3dfd8}
.langs a:focus-visible{outline:3px solid var(--blue);outline-offset:3px}
.langs a.on{background:var(--orange);border-color:var(--deep);box-shadow:0 4px 0 var(--deep);color:#fff}
.langs a.on:hover{box-shadow:0 3px 0 var(--deep)}
/* Nothing to pay out of the hero at desktop — the tabs sit inside the 96px
   header, so .layout keeps its calc(100% - 96px) and --phone-h reserves
   header + the showcase's bottom padding and no more.
   Below 800px the header WRAPS instead: the brand keeps the first row at
   var(--head) and the tabs take a full-width 46px row under it, centred,
   which is where they have always been drawn on a phone. There is no room
   for both on one line — "European Portuguese" alone is a third of it. */
@media(max-width:800px){.langs{flex:0 0 100%;height:46px;gap:7px;justify-content:center}.langs a{gap:6px;padding:5px 10px 5px 6px;font-size:11.5px;border-radius:22px;box-shadow:0 3px 0 #e3dfd8}.langs a.on{box-shadow:0 3px 0 var(--deep)}.langs a img{width:22px;height:22px;border-radius:50%}.layout{height:calc(100% - var(--head) - 46px)}}

/* ═══════════════════════════════════════════════════════════════════════
   THE LONG PAGE: EVERY SECTION IS A SCENE.

   Below the hero, a language page is a stack of full-screen grounds rather
   than one cream sheet, and it reads as a walk through the app's own
   landscape: sky, hillside, paper, night, sky again. Three rules hold it:

     · a scene is exactly ONE SCREEN tall and centres its content. Nothing
       below may grow past `100svh` — website/check.mjs measures every
       scene at three viewports and fails the build when one does.
     · a scene BULGES into the one above it on ONE CURVE — `--seam`, the
       same path at every seam — so the colour change IS the section break.
       No rules, no dividers.
     · the colour order alternates saturated and quiet so nothing shouts
       twice running, and the page closes on the sky it opened on:
       sky · GREEN · cream · ORANGE · paper · mint · sky · NIGHT ·
       cream · FOREST · paper · sky.

   styles.css above paints for ONE cream ground; a scene repaints through
   its own tokens (--ground/--fg/--fg-soft/--eye/--em). Every override in
   here deliberately out-ranks the rule it replaces — `.scene .panel.split
   .s-copy>p` is four classes to that one's three — because a colour whose
   only definition sits in the older rule silently repaints a headline on
   a ground it was never meant for. That fault shipped twice while this
   page was being drawn: once flattening a pull quote to body grey, once
   painting an orange word on an orange card. */

/* THE SEAM, and there is only one of it: a symmetric cubic that leaves both
   edges horizontal, rises to a single apex in the middle, and comes back
   down horizontal. Every scene wears this same path, stretched to whatever
   the window is wide (`preserveAspectRatio='none'`), so no seam on the page
   has a different shape from any other.

   It replaced `border-radius: 50% 50% 0 0 / 62px 62px 0 0`, which drew a
   pair of ellipse quadrants. That is one shape too, but an ellipse's
   curvature is NOT constant: with a 720px horizontal radius and a 62px
   vertical one it lies almost flat across the middle and then turns hard
   into each corner, so every seam looked like it changed angle as it
   approached the edge of the window. A cubic with horizontal end tangents
   has no such turn.

   The scene is masked to it rather than clipped by it, because a mask can
   be two layers: the curve across the top, and a plain rectangle for
   everything below — overlapping by a pixel, or the join renders as a
   hairline of the scene behind. */
:root{
  --seam-h:90px;
  --seam:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 90' preserveAspectRatio='none'%3E%3Cpath d='M0 90C360 90 360 0 720 0s360 90 720 90z' fill='black'/%3E%3C/svg%3E");
}

.wrap{position:relative;z-index:2;width:min(1100px,calc(100% - 48px));margin:0 auto}

.scene{
  --ground:var(--cream);--fg:var(--ink);--fg-soft:#4f4a43;
  --eye:#357a22;--em:var(--orange);
  /* what a card's hard shadow is drawn IN. Every other stacked object on
     this site sits on a real colour — the download button on --deep, the
     drill chips on --deep, the hero phone on a green — and the stat cards
     were the one place reaching for neutral black at 12%, which on a
     saturated ground reads as haze rather than as depth. A scene that
     never carries a card still declares it, so the token means the same
     thing everywhere. */
  --sink:rgba(38,35,31,.14);
  position:relative;min-height:100svh;display:flex;align-items:center;
  background:var(--ground);color:var(--fg);
  /* THE SEAM. The scene's own top edge is an ellipse — a wide, shallow arc
     on the hero's hill curve — and a negative top margin lifts it over the
     scene above. It is the element's OWN border-radius rather than an
     oversized pseudo-element laid on top: a `left:-9%; right:-9%` bar drew
     the same arc and pushed 130px of horizontal scroll onto every page it
     was on. A box cannot overflow itself. */
  margin-top:calc(var(--seam-h) * -1);
  padding:calc(var(--seam-h) + 76px) 0 76px;
  -webkit-mask:var(--seam) top/100% var(--seam-h) no-repeat,
               linear-gradient(#000,#000) bottom/100% calc(100% - var(--seam-h) + 1px) no-repeat;
          mask:var(--seam) top/100% var(--seam-h) no-repeat,
               linear-gradient(#000,#000) bottom/100% calc(100% - var(--seam-h) + 1px) no-repeat;
}
.scene .art{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none}

.s-green {--ground:#4a9a2f;--fg:#fff;--fg-soft:rgba(255,255,255,.88);--eye:#daf5be;--em:#ffe08a;--sink:#2d6a17}
.s-cream {--ground:#fffaf1}
.s-orange{--ground:#fa6d0f;--fg:#fff;--fg-soft:rgba(255,255,255,.92);--eye:#ffe4c9;--em:#fff3dd}
.s-paper {--ground:#fdf3e3}
.s-mint  {--ground:#e8f4de}
.s-sky   {--ground:#bee3f2}
.s-night {--ground:#221f1b;--fg:#fff;--fg-soft:#c6bdae;--eye:#9ed17a;--em:#ff9d4d}
.s-forest{--ground:#357a22;--fg:#fff;--fg-soft:rgba(255,255,255,.88);--eye:#cdeeb2;--em:#ffd98a}

.scene h2,.scene .band-title,.scene .p-head h2,.scene .panel.split h2{color:var(--fg)}
.scene h2 em{color:var(--em)}
.scene .eyebrow{color:var(--eye)}
.scene .p-head p,.scene .panel.split .s-copy>p,.scene .lead{color:var(--fg-soft)}
.scene .p-head a,.scene .panel.split .s-copy a{color:var(--em);text-decoration-color:var(--em)}
/* a link on a LIGHT ground keeps the site's green; only the dark grounds
   fall back to their scene's accent */
.s-cream .p-head a,.s-paper .p-head a,.s-mint .p-head a,.s-sky .p-head a,
.s-cream .panel.split .s-copy a,.s-paper .panel.split .s-copy a,
.s-mint .panel.split .s-copy a,.s-sky .panel.split .s-copy a{color:#317824}
/* a scene supplies its own rhythm, so the stacked-panel one is switched off */
.scene .panel{padding:0;margin:0;border-top:0}
.scene .p-head{margin-bottom:30px}
.scene .scene-h{font:700 clamp(30px,3.8vw,48px)/1.06 var(--display);
  letter-spacing:-1.6px;margin:0 0 14px}
.scene .scene-lead{max-width:56ch;font-size:17px;line-height:1.6;margin:0 0 34px}

/* ── scenery ─────────────────────────────────────────────────────────── */
.blob{position:absolute;border-radius:50%}
.hillband{position:absolute;left:-14%;right:-14%;border-radius:50% 50% 0 0}
.sprig{position:absolute;bottom:0}
.sprig i{display:block;width:70px;height:60px;background:#357a22;border-radius:50%;
  box-shadow:-30px 13px 0 #4a9a2f,27px 15px 0 #4a9a2f}
.sprig b{display:block;width:15px;height:60px;background:#8c5a33;margin:auto}
.puff{position:absolute;width:104px;height:29px;border-radius:40px;background:#fff;
  animation:sail 46s linear infinite}
.puff:before,.puff:after{content:"";position:absolute;background:#fff;border-radius:50%;bottom:0}
.puff:before{width:48px;height:48px;left:17px}
.puff:after{width:37px;height:37px;left:55px}
@keyframes sail{from{transform:translateX(-220px)}to{transform:translateX(calc(100vw + 220px))}}
.star{position:absolute;width:3px;height:3px;border-radius:50%;background:#fff;
  animation:twinkle 3.4s ease-in-out infinite}
.star.big{width:5px;height:5px}
@keyframes twinkle{0%,100%{opacity:.25}50%{opacity:1}}
/* The moon is one div: the disc, the crescent bite (an inset shadow in the
   scene's own ground colour), and three craters. The craters are radial
   gradients on the BACKGROUND rather than child elements, so the inset
   shadow still paints over them — a crater drawn as a ::before would sit
   on top of the bite and hang in the dark half. Sizes and offsets are in
   px against a 64px disc; the whole thing scales with width/height. */
.moon{position:absolute;width:64px;height:64px;border-radius:50%;
  background:
    radial-gradient(circle 6px at 20px 22px, #ddcda6 98%, transparent 100%),
    radial-gradient(circle 4px at 34px 44px, #ddcda6 98%, transparent 100%),
    radial-gradient(circle 2.5px at 14px 42px, #ddcda6 98%, transparent 100%),
    #f7ecd4;
  box-shadow:inset -20px 4px 0 0 #221f1b}
.rulepaper{position:absolute;inset:0;
  background:repeating-linear-gradient(transparent 0 43px,rgba(38,35,31,.055) 43px 44px)}
.margin-rule{position:absolute;top:0;bottom:0;left:16%;width:2px;background:rgba(250,109,15,.14)}
.ring{position:absolute;border-radius:50%;border:2px solid rgba(255,255,255,.22)}
/* Two aircraft at two distances, CROSSING. The near one keeps the
   original 34s left-to-right; the far one is smaller, dimmer, slower and
   flies the other way, right to left, started half a cycle out — so
   whenever one is off the edge the other is mid-screen, and the two read
   as a sky with traffic in it rather than one sprite looping twice. The
   sprite's nose points right, so the westbound one is mirrored
   (scaleX(-1)) and carries that flip in every keyframe: a transform is
   replaced whole, not merged. */
.jet{position:absolute;width:74px;opacity:.9;animation:fly 34s linear infinite}
.jet-far{width:42px;opacity:.55;animation:fly-back 52s linear infinite;animation-delay:-26s}
@keyframes fly{from{transform:translateX(-140px) translateY(14px)}
  to{transform:translateX(calc(100vw + 140px)) translateY(-14px)}}
@keyframes fly-back{from{transform:translateX(calc(100vw + 140px)) translateY(-14px) scaleX(-1)}
  to{transform:translateX(-140px) translateY(14px) scaleX(-1)}}
/* Reduced motion stops both, and the westbound one keeps its mirror and
   its side of the sky — frozen at left:0 facing east it would be the
   same plane twice. */
@media(prefers-reduced-motion:reduce){.puff,.star,.jet{animation:none}.star{opacity:.7}
  .jet-far{transform:translateX(calc(100vw - 220px)) scaleX(-1)}}

/* ── the numbers, as four posters ────────────────────────────────────────
   TWO up, not four across. The numeral is the point of this scene, and
   four columns cap it: the wrap is 1100px, and three gaps plus 22px of
   padding leave a card about 230px of drawing room, against `2,621` in
   Fredoka running roughly 2.5em. A four-column row therefore cannot take
   a digit past ~84px HOWEVER tall the card is made — height buys nothing
   there. Half the columns, and the same row carries 100px.

   The icon stops being a 34px bullet over the number and becomes the
   card's ART: full colour, ~224px, cropped through the bottom-right
   corner. It is absolutely positioned, so `.stat` is a flex column for
   the three text rows alone and `.stat-s` sits on the floor of the card.

   ONE KNOB, and everything else is derived from it. `--num` is the
   numeral, `--art` is 2.24 of it, `--clear` is the padding the text keeps
   off the art — and --clear is DERIVED rather than typed because it is
   the same measurement as --art: the art's left edge stands `--art` minus
   its own negative offset in from the card's right, and the text has to
   stop before it or `2,621` sits on a signpost. Getting that wrong
   errors nowhere; the digits simply land on the drawing.

   --num is bounded on BOTH axes, and the height half is the one that was
   easy to miss. A scene is one screen tall and centres its content, so a
   card that grows past its share does not scroll — it makes the SCENE
   taller than the viewport, which is the fault this page is designed
   against. Width alone was fine at 1440x900 and 45px over at 1366x768,
   a laptop far commoner than the desktop the check runs at. So the
   height term: the eyebrow, the heading and the lead cost about 181px,
   the card's own furniture (padding, label, sub-line) about 109px, THE
   SEAM eats ~24px more off the bottom than the padding gives back (the
   next scene is pulled up --seam-h and paints from its own top at the
   centre), and two rows plus their gap have to fit what is left — which
   lands the numeral at 61svh minus a constant.

   That constant changes ONCE, where .scene's own padding does at
   max-height:800px, and the card's furniture deliberately does NOT change
   with it. Trimming both at the same breakpoint squeezed the tier above
   it twice over, and the result was INVERTED: a 1440x820 window drew a
   65px numeral where a 1366x768 one drew 93px, and two pixels of window
   height either side of 800 swung it between 56px and 100px. One thing
   moves at that line now, so the step is 11px and in the right
   direction. */
.scene .stat-row{gap:18px;grid-template-columns:repeat(2,1fr)}
.scene .stat{
  --num:clamp(46px,min(8vw,calc(61svh - 446px)),92px);
  --art:clamp(120px,calc(var(--num) * 2.44),224px);
  --clear:calc(var(--art) - 58px);
  position:relative;overflow:hidden;display:flex;flex-direction:column;
  padding:22px 32px;border-radius:20px;border-color:transparent;
  background:linear-gradient(180deg,#fff 0%,var(--cream) 100%);
  box-shadow:inset 0 1px 0 #fff,0 9px 0 var(--sink),0 24px 36px rgba(12,38,4,.28)}
.scene .stat img{position:absolute;right:-26px;bottom:-26px;margin:0;
  width:var(--art);height:var(--art)}
/* The three text rows outrank the art without a z-index: they come after
   it in the document and are positioned, which is the whole rule. */
.scene .stat strong,.scene .stat .stat-l,.scene .stat .stat-s{
  position:relative;padding-right:var(--clear)}
/* --orange, the download button's own colour, not --deep: these four
   numbers are the loudest thing in the scene and --deep is the SHADOW
   half of the pair (it is what sits under the button, not on it), which
   read as a browner, duller orange the moment the cards went white. */
.scene .stat strong{font:700 var(--num)/.82 var(--display);
  letter-spacing:-.052em;color:var(--orange)}
.scene .stat .stat-l{margin-top:12px;
  font:600 20px/1.2 var(--display);letter-spacing:-.5px;color:var(--ink)}
/* margin-top:auto, so the sub-line sits on the card's floor rather than
   trailing the label — the poster wants a foot. */
.scene .stat .stat-s{margin-top:auto;padding-top:10px;font-size:13.5px;color:#8a7a62}

/* Tablet: the wrap is now the viewport, so a card loses ~90px of width and
   the whole set comes down with --num. Still two up — at 900px a card is
   417px, which still holds a poster. Only the constants move: less
   padding around the art means less clearance beside it. */
@media(max-width:1100px){
  .scene .stat{--clear:calc(var(--art) - 48px);padding:22px 26px;border-radius:18px}
  .scene .stat img{right:-22px;bottom:-22px}
  .scene .stat .stat-l{margin-top:10px;font-size:17px}
  .scene .stat .stat-s{padding-top:10px;font-size:12.5px}
}

/* ── an in-page phone ────────────────────────────────────────────────────
   Not `.phone`, which is the hero's and sizes itself off the whole
   viewport. This one takes a share of the viewport HEIGHT and derives its
   width from the screenshots' own shape (--shot-ar), so a scene can promise
   to fit on one screen and keep the promise on a laptop as well as a
   monitor. */
/* container-type, so the DRAWN screen below can size itself in cqw off the
   phone it is drawn in rather than off the viewport. The speaking screen
   was set in vh and fixed px: on a 390px phone the device shrank to 138px
   wide while `Grüezi` stayed at 38px, and the word was clipped to `Grüez`
   by .win's own overflow. Nothing said so — it just looked like a short
   word. Sized in cqw it cannot happen at any device size. */
.device{position:relative;margin:0 auto;container-type:inline-size;
  --h:clamp(340px,54vh,510px);--shot-ar:.47146;
  width:calc((var(--h) - 44px)*var(--shot-ar) + 18px);height:var(--h);
  background:#26231f;border:9px solid #26231f;border-radius:38px;
  box-shadow:17px 21px 0 rgba(38,35,31,.18),25px 33px 50px rgba(38,35,31,.25);
  transform:rotate(1.5deg)}
.device .spk{position:absolute;z-index:3;top:5px;left:50%;transform:translateX(-50%);
  width:64px;height:5px;border-radius:8px;background:#57534c}
.device:after{content:"";position:absolute;z-index:3;bottom:5px;left:50%;
  transform:translateX(-50%);width:34%;height:4px;border-radius:3px;background:#57534c}
.device .win{position:absolute;inset:14px 0 15px;border-radius:26px;overflow:hidden;
  background:#fffaf1}
.device .win>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:top;opacity:0;transition:opacity .26s ease}
.device .win>img.on{opacity:1}
.reel-now{margin:16px 0 0;text-align:center;font:600 14px var(--display);color:var(--fg-soft);
  min-height:1.4em}

/* ── the drill kinds, as a chip run ─────────────────────────────────── */
.kinds{display:flex;flex-wrap:wrap;gap:7px;margin:22px 0 0;padding:0;list-style:none}
.kinds li{padding:6px 12px;border-radius:20px;background:var(--orange);
  border:1px solid var(--deep);box-shadow:0 3px 0 var(--deep);
  font-size:12px;font-weight:500;color:#fff}
/* the last chip carries the rest of the number, in the same orange: an
   outlined one read as a different KIND of thing, which it is not */
.kinds li.more{font-weight:700}

/* ── the speaking screen, drawn and animated ────────────────────────── */
/* Every length here is cqw — a share of the PHONE's width — so the screen
   is the same drawing at 158px and at 268px. The numbers are the old
   fixed pixels over 219.7, the device's real container size at the desktop
   width they were tuned at: its border box less its own 9px frame, and NOT
   the 251px its rect reports, because a 1.5deg tilt makes a bounding box
   14px wider than the element inside it. Calibrated that way the desktop
   rendering is the one that was already there, to within a pixel. */
.say-screen{position:absolute;inset:0;display:flex;flex-direction:column;
  padding:8.19cqw 7.28cqw 0;background:#fffaf1;--vu-lo:3.19cqw;--vu-hi:14.56cqw}
.say-top{display:flex;align-items:center;gap:5.01cqw;margin-bottom:8.19cqw}
.say-top i{flex:1;height:3.19cqw;border-radius:1.82cqw;background:#f0e9dd;display:block}
.say-top i b{display:block;height:100%;border-radius:1.82cqw;background:var(--orange);width:64%}
.say-top u{text-decoration:none;color:#b3aca1;font-size:7.74cqw;line-height:1}
.say-h{font:600 7.28cqw var(--display);color:var(--ink);margin:0 0 auto}
.say-word{font:700 17.3cqw/1 var(--display);color:var(--ink);
  letter-spacing:-.026em;text-align:center}
.say-ipa{display:flex;justify-content:center;flex-wrap:nowrap;gap:1.37cqw;margin-top:5.01cqw}
.say-ipa i{font-style:normal;font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size:4.78cqw;padding:1.37cqw 2.28cqw;border-radius:2.73cqw;background:#eef7e8;
  color:#357a22;opacity:0;animation:ipaIn 4.6s linear infinite}
.say-vu{display:flex;align-items:center;justify-content:center;gap:1.82cqw;
  height:19.12cqw;margin-top:auto}
.say-vu i{display:block;width:2.28cqw;height:var(--vu-lo);border-radius:1.37cqw;
  background:var(--orange);animation:vu .62s ease-in-out infinite alternate}
.say-mic{position:relative;width:28.22cqw;height:28.22cqw;border-radius:50%;
  background:var(--orange);display:grid;place-items:center;margin:5.46cqw auto 0;
  box-shadow:0 2.28cqw 0 var(--deep);animation:micRing 4.6s ease-out infinite}
.say-mic svg{width:12.29cqw;height:12.29cqw;fill:none;stroke:#fff;stroke-width:2.1;
  stroke-linecap:round;stroke-linejoin:round}
.say-ok{display:flex;align-items:center;justify-content:center;gap:3.64cqw;
  margin:6.83cqw 0 8.19cqw;padding:4.55cqw;border-radius:6.37cqw;background:#e9f8ee;
  color:#177a45;font:700 6.14cqw var(--body);opacity:0;animation:okIn 4.6s ease-out infinite}
.say-ok svg{width:7.28cqw;height:7.28cqw;fill:none;stroke:currentColor;stroke-width:2.8;
  stroke-linecap:round;stroke-linejoin:round}
/* the bar heights ride vars, because a keyframe resolves cqw against the
   animated element's own container and reading both ends from one place
   is what keeps them in step */
@keyframes vu{from{height:var(--vu-lo)}to{height:var(--vu-hi)}}
@keyframes micRing{0%,58%{box-shadow:0 2.28cqw 0 var(--deep),0 0 0 0 rgba(255,255,255,.5)}
  78%,100%{box-shadow:0 2.28cqw 0 var(--deep),0 0 0 7.74cqw rgba(255,255,255,0)}}
@keyframes ipaIn{0%,6%{opacity:0;transform:translateY(5px)}
  16%,86%{opacity:1;transform:none}96%,100%{opacity:0}}
@keyframes okIn{0%,68%{opacity:0;transform:translateY(9px) scale(.94)}
  78%,94%{opacity:1;transform:none}100%{opacity:0}}
@media(prefers-reduced-motion:reduce){
  .say-vu i,.say-mic,.say-ok,.say-ipa i{animation:none}
  .say-ok,.say-ipa i{opacity:1}
  .say-vu i:nth-child(odd){height:11.83cqw}.say-vu i:nth-child(even){height:6.37cqw}}

/* the one loud line in the speaking copy; on the orange ground it inverts */
.scene .panel.split .s-copy>p.claimline,.claimline{margin:24px 0 0;padding:19px 23px;
  border-radius:18px;background:#fffaf1;color:var(--ink);
  font:600 17px/1.45 var(--display);letter-spacing:-.3px;
  box-shadow:0 6px 0 rgba(38,35,31,.13)}
.claimline em{font-style:normal;color:var(--deep)}

/* ── a dictionary entry, drawn the way the app draws it ─────────────── */
.entry{box-shadow:0 8px 0 rgba(38,35,31,.07)}
.entry .hw{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.entry .hw b{font:700 31px var(--display);letter-spacing:-1px}
.entry .hw code{font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size:14.5px;color:#357a22}
.entry .hw i{font-style:normal;font-size:11.5px;padding:4px 10px;border-radius:20px;
  background:#f3ede1;color:var(--muted);font-weight:500}
.entry .gl{margin:9px 0 16px;font-size:18px;color:var(--ink)}
.entry dl{margin:0;border-top:1px solid var(--line)}
.entry dl>div{display:grid;grid-template-columns:88px 1fr;gap:14px;padding:11px 0;
  border-bottom:1px solid #f3ece1;font-size:14.5px}
.entry dl>div:last-child{border-bottom:0}
.entry dt{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:#357a22;
  font-weight:700;padding-top:3px}
.entry dd{margin:0;line-height:1.5;color:var(--ink)}
.entry dd em{font-style:italic;color:var(--muted)}
.entry .plwhy{margin-top:14px;padding:13px 16px;border-radius:12px;background:#f4faef;
  border:1px solid #dce9d2;font-size:13px;line-height:1.5;color:#48603c}
.entry .plwhy b{color:#357a22}

/* ── the interface languages ────────────────────────────────────────── */
.sixteen{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin:0}
.sixteen span{display:inline-flex;align-items:center;gap:9px;
  padding:9px 17px 9px 10px;border-radius:30px;background:#fff;
  border:1px solid transparent;box-shadow:0 4px 0 rgba(38,35,31,.12);
  font-size:15px;color:var(--ink)}
.sixteen img{width:22px;height:22px;border-radius:50%;flex:none}

/* ── who it's for: the one big sentence ─────────────────────────────── */
.scene .panel.split .s-copy>p.pull,.pull{font:600 clamp(23px,2.9vw,32px)/1.24 var(--display);
  letter-spacing:-.9px;color:var(--fg);margin:14px 0 0;text-wrap:balance}
.pull em{font-style:normal;color:var(--orange)}
.who-art{text-align:center}
.who-art img{width:min(230px,64%);height:auto;margin:0 auto}

/* ── the ladder, on forest green ────────────────────────────────────── */
.scene .lad-labs li{color:#fff;font-weight:500}

/* ── FAQ: the acknowledgements accordion, reused ────────────────────── */
.faq-head{margin:0 0 22px}
.faq-head h2{margin:0;font:700 clamp(28px,3.4vw,40px)/1.12 var(--display);letter-spacing:-1px}
.scene .ack-list{margin:0;box-shadow:0 8px 0 rgba(38,35,31,.07)}
.scene .ack-item>summary{padding:15px 46px 15px 20px}
.scene .ack-name{font-size:16px}
.scene .ack-body{padding:0 20px 18px}
.scene .ack-body p{margin:0;font-size:14.5px;line-height:1.6;color:#4f4a43;max-width:70ch}

/* ── the closing call, back on the hero's sky ───────────────────────── */
.scene.close .wrap{text-align:center}
.scene.close h2{margin:0 0 14px;font:700 clamp(32px,4vw,52px)/1.02 var(--display);
  letter-spacing:-1.8px}
.scene.close h2 em{text-shadow:0 5px 0 rgba(190,78,4,.16)}
.scene.close p{margin:0 auto;max-width:44ch;font-size:17px;line-height:1.6;color:#3b4d2f}
.scene.close .stores{justify-content:center}

/* A 720px-tall window has 180px less than the budget above assumes, and the
   FAQ — eight rows plus an open answer — is the scene that notices first. */
@media(max-height:800px){
  .scene{padding:calc(var(--seam-h) + 56px) 0 56px}
  /* 40px came off the scene's own padding above, so 20px more is free per
     card and the constant in --num's height term drops by the 24px of
     numeral that buys. That is the ONLY thing this tier changes about a
     card — see the note on --num. Without it a 768px-tall laptop, the
     commonest window there is, runs the scene 45px past its screen. */
  .scene .stat{--num:clamp(46px,min(8vw,calc(61svh - 422px)),92px)}
  .scene .ack-item>summary{padding:11px 46px 11px 20px}
  .scene .ack-body{padding-bottom:14px}
  .faq-head{margin-bottom:12px}
  .scene .p-head{margin-bottom:22px}
}

/* A window under ~740px tall cannot hold TWO rows of posters: with the
   numeral already at its 46px floor a card is 146px, and two of them plus
   the heading and the lead leave the bottom row 70px above the scene's
   edge against a 90px seam. So a short window gets ONE row of four
   instead — the old shape, at the new weight.

   The threshold was 700px, which is where two rows stop fitting the
   PADDING; it is 740 now because the seam takes ~34px more off the
   bottom than the padding gives back, and a row of cards tucked under
   the next scene's wave is the same fault as a row that overflows. The
   height term drops out of --num here, because a single row is nowhere
   near the height budget — width is the only thing binding.

   And what binds it is NOT the numeral. In a 234px card the digits are
   the narrowest row there is: `2,621` sets 90px where "on the phone, not
   a server" sets 147px and "words you will know" 135px. Those two are
   fixed strings that do not scale with --num at all, so the art here is
   sized against THEM — a flat vw, rather than the multiple of the numeral
   every wider tier uses — and the two label rows come down with it, since
   an 11px sub-line is 30px of art the card did not have. min-width keeps
   this off phones, where the one-column tier below is the right answer
   and wins by coming later.

   The 56px floor is measured, not chosen: clearance here is not monotonic
   in the art's size, because the sub-line WRAPS. Give the card 8px more
   room and "on the phone, not a server" un-wraps onto one long line and
   the margin gets worse, not better. 44/48/52/56 were all drawn and read
   at 701, 760, 820 and 900 wide; 56 is the one whose worst case is the
   best, at 6px. */
@media(max-height:740px) and (min-width:701px){
  .scene .stat-row{grid-template-columns:repeat(4,1fr);gap:14px}
  .scene .stat{--num:clamp(34px,4.4vw,56px);
    --art:clamp(56px,6.6vw,108px);
    --clear:calc(var(--art) - 40px);padding:20px 22px;border-radius:16px}
  .scene .stat img{right:-18px;bottom:-18px}
  .scene .stat .stat-l{margin-top:10px;font-size:clamp(14px,1.55vw,16px)}
  .scene .stat .stat-s{padding-top:8px;font-size:clamp(10.5px,1.2vw,12px)}
}

@media(max-width:900px){.wrap{width:min(1100px,calc(100% - 36px))}}

/* The hero phone at phone width is bounded on BOTH axes, and it has to be.
   Its width comes from `height:100%` times its own `aspect-ratio`, clamped
   by `max-width` — so a hero whose copy column gets SHORTER makes the
   showcase taller, the wanted width overshoots the clamp, and `object-fit:
   cover` pays the difference by cropping 5% off the sides of the shot. The
   max-height is that clamp expressed as a height, so the width never has to
   be clipped. It went red the day a line came out of the hero. */

/* A phone cannot hold twelve full screens of STACKED content: a split panel
   goes to one column below 700px, so its copy and its device now share a
   screen instead of sitting side by side. The scene stops promising a full
   viewport and everything inside it gives up height instead. */
@media(max-width:700px){
  .wrap{width:calc(100% - 32px)}
  /* THE CURVE IS THE SAME SHAPE, DRAWN SHALLOWER, AND THE BOTTOM PADDING
     IS MEASURED FROM IT. A scene is pulled up by --seam-h and the next
     one paints from its own top at the CENTRE, so the wave eats --seam-h
     of the scene above it right where a phone-width column puts its
     text. Bottom padding therefore has to be seam plus the gap you
     actually want, and it was a flat 40px against a 44px seam — every
     scene on every phone ended 4px UNDER the wave. Four pixels is a
     clipped descender on a last line and a flat cut across the bottom of
     a phone mock, which is what it looked like.

     32px because at 390px wide even a 44px apex is an eighth of the
     screen, and 26px of clearance because that is where the crop on a
     device stops reading as part of the wave. */
  .scene{--seam-h:32px;min-height:0;
    padding:calc(var(--seam-h) + 40px) 0 calc(var(--seam-h) + 26px)}
  /* LEFT, not centred — every scene, not just the split ones. The science
     page stacks its split panels centred (@media max-width:760px) and a
     scene inherited that, while .p-head has been centred by design since
     it was written; on a 358px column that left the page alternating
     between a hard left edge and a centred block as you scrolled.
     Ragged-right is also the easier read for a 15px paragraph this
     narrow, where centring gives both edges a new start position on
     every line. `.scene .p-head` and `.scene .panel.split` outrank the
     rules they override on specificity, so the science page keeps its
     own centring.

     Two things stay centred on purpose: .scene.close, which is the call
     to action and not body copy, and .reel-now, a caption under a phone
     that is itself centred. */
  .scene .panel.split{gap:20px;text-align:left}
  .scene .panel.split .s-copy{margin:0}
  .scene .p-head{text-align:left;margin-left:0}
  .scene.close .p-head,.scene.close{text-align:center}
  /* the language chips join that left edge rather than floating centred
     under a heading that no longer is */
  .sixteen{justify-content:flex-start}
  .scene h2,.scene .panel.split h2,.scene .p-head h2,.scene .scene-h{font-size:clamp(25px,7vw,30px)}
  .scene .p-head p,.scene .panel.split .s-copy>p,.scene .lead,.scene .scene-lead{font-size:15px}
  /* the illustration joins the left edge too — a centred mark over a
     left-aligned block is the same split the page had before */
  .scene .p-icon{width:76px;height:76px;margin-left:0}
  /* ONE column. At 390px a two-column card is 171px wide, which cannot
     hold a poster at all — the numeral would be back to a caption and
     the art would have nowhere to crop into. Four full-width cards cost
     scroll, which is what a phone has. */
  .scene .stat-row{grid-template-columns:1fr;gap:12px}
  .scene .stat{--num:clamp(46px,13vw,80px);--clear:calc(var(--art) - 30px);
    padding:20px 22px;border-radius:16px}
  .scene .stat img{right:-18px;bottom:-18px}
  .scene .stat .stat-l{margin-top:8px;font-size:15.5px}
  .scene .stat .stat-s{margin-top:6px;padding-top:0;font-size:12.5px}
  /* THE DEVICE IS SIZED BY WIDTH HERE, AND CROPPED. On desktop it takes a
     share of the viewport HEIGHT, which on a phone left it 138px wide in
     a 358px column — a third of the width, with the screenshot inside too
     small to read and the drawn speaking screen clipping its own word.
     So: width first, height derived from the shot's own shape, and the
     bottom of the phone cut off by .s-art rather than the whole phone
     shrunk to fit. A phone running out of the frame is the right picture
     anyway; a whole tiny phone floating in space is not.

     .s-art keeps the device in flow with a fixed visible band, and the
     hard shadow is retuned so it fits the 45px of side room the crop
     leaves — the desktop 25/50px blur would have been sliced down its
     right edge, which reads as a seam rather than as a shadow. */
  /* THE DEVICE IS SIZED BY WIDTH HERE, AND CROPPED AT THE KNEE. On desktop
     it takes a share of the viewport HEIGHT, which on a phone left it
     138px wide inside a 358px column — a third of the width, with the
     screenshot inside too small to read anything on. Width first, then
     the height its own screenshot's shape asks for, then .devbox cuts the
     bottom off. A phone running out of the frame is a better picture than
     a whole tiny phone floating in the middle of the section.

     .devbox is a real element rather than :has(.device), because the
     failure mode of a selector the browser does not know is a device with
     no box to stand in — absolutely positioned, out of flow, painted over
     whatever follows. Every device on the page is emitted inside one
     (template.html, and build.py's dictionary mock).

     The shadow is retuned to fit the ~45px of side room the crop leaves:
     the desktop 25/50px blur would be sliced down its right edge, which
     reads as a seam rather than as a shadow. */
  /* The box carries --w, and the device reads it: the device is taken out
     of flow to be cropped, so the box cannot get a width from it. One of
     them is inside a .mock (width:max-content), which measured zero the
     moment the device left the flow, and a zero-wide box centres its
     phone off the edge of the section.

     The box is --w plus 68px so the phone's shadow has 34px of room on
     each side; the shadow is retuned to reach 30px, because the desktop
     25/50px blur would be sliced down its right edge and a sliced shadow
     reads as a seam. */
  .devbox{--w:min(252px,66vw);--cut:clamp(300px,46svh,420px);
    position:relative;overflow:hidden;margin:0 auto;
    width:calc(var(--w) + 68px);height:var(--cut)}
  .device{--h:calc((var(--w) - 18px) / var(--shot-ar) + 44px);
    width:var(--w);height:var(--h);border-radius:32px;border-width:8px;
    position:absolute;top:0;left:50%;margin:0;
    transform:translateX(-50%) rotate(1.5deg);
    box-shadow:10px 12px 0 rgba(38,35,31,.18),12px 14px 18px rgba(38,35,31,.2)}
  .device .win{inset:12px 0 13px;border-radius:22px}
  /* the SPEAKING screen is drawn, not photographed, so cropping it would
     cut the mic button and the verdict — the two things the scene is
     about. It gets the same crop as every other phone, and its own flex
     column is told to end at the cut instead, so the whole screen lands
     inside the visible band and the frame is what runs out of shot. The
     22px is .win's inset plus the ~4px the 1.5deg tilt costs at the
     corner. */
  .say-screen{height:calc(var(--cut) - 22px)}
  .reel-now{margin-top:12px;font-size:13px}
  .kinds{margin-top:14px;gap:5px}
  .kinds li{padding:4px 9px;font-size:10.5px}
  .claimline,.scene .panel.split .s-copy>p.claimline{margin-top:18px;padding:15px 18px;font-size:15.5px}
  .sixteen span{padding:7px 13px 7px 8px;gap:7px;font-size:13px}
  .sixteen img{width:18px;height:18px}
  .entry{padding:18px}
  .entry .hw b{font-size:26px}
  .entry .gl{margin:7px 0 12px;font-size:16px}
  .entry dl>div{padding:9px 0;font-size:13.5px;grid-template-columns:74px 1fr}
  .entry .plwhy{margin-top:11px;padding:11px 13px;font-size:12px}
}

/* ── the phone breaks out of the hero ────────────────────────────────────
   Scene 01 is pulled up 90px (margin-top:-90px) so its seam rises into the
   hillside, and the phone's bottom ~55px sits inside that overlap. The
   phone was LOSING it: .hero carried isolation:isolate, which made every
   z-index in the hero local to it, so the scene — later in the document,
   z-index auto — painted over the whole hero no matter what the phone
   asked for. Raising the phone's z-index alone could never have worked.

   So the hero no longer isolates, and the layers it holds are ordered
   against the scenes instead of against each other:

     .cloud-stream  0  |  behind everything, masked by the hills
     .hill          1  |  BELOW the seam — the scene's arc rises over them
     .scenery       2  |  every prop, in ONE layer (below)
     section.scene  3  |  the seam, and every scene's own ground
     .layout        5  |  the copy and the phone — OVER the seam
     header        10  |

   z-index:3 on .scene is the whole fix: without it the hills and the
   scenery (1 and 2) would paint over the arc and the seam would vanish.
   It also leaves the scenery exactly ONE layer between the hills and
   the seam, so a prop cannot be interleaved BETWEEN two hills: every
   prop has to sit clear above the crest of the hill in front of it,
   which is what build.py's SCENERY table is laid out to do. Note the
   hero still clips at its own bottom edge (overflow:hidden), which is
   what keeps the phone's drop shadow from raining onto the scene. */
section.scene{z-index:3}

/* ── a callout, hanging off a phone mock ─────────────────────────────────
   Five of these used to ring the hero phone, each glued to a slot of its
   own, and each anchored off --phone-w because the showcase is far wider
   than the phone and the gap between the two grows with the viewport.
   They came off on 2026-08-27: above the fold there is now a headline, a
   phone and a hill with something living on it, and five white cards over
   that was one layer too many.

   The card survives as a component, used once, on the dictionary scene,
   and the anchoring got simpler with the move. .mock hugs the device, so
   "hanging off its edge" is `100% - bite` rather than a sum off the
   column's midpoint, and a chip carries which edge it is on (.l / .r)
   and how far down it (--y) instead of a bespoke class per slot.

   THEY NEED THE FULL LAYOUT. A chip is about 160px wide and hangs off
   the right of a mock that is already at the right edge of the wrap, so
   under about 1200px of window it runs off the page and takes the
   horizontal scrollbar with it. That is the real floor, not the 800px
   where the panel stacks: at 900px the overflow is 61px with the two
   column layout still in place and nothing else on the page
   complaining. So they stand down below 1200px, and check.mjs loads a
   1180px viewport to prove it. */
@media(max-width:1199px){.mock .feature{display:none}}

/* ══════════════════════════════════════════════════════════════════════
   THE ROOT: the course chooser (template-root.html).

   speeku.com/ is a DOOR, not a pitch. One build teaches one language, so
   there is no such thing as downloading "Speeku": every claim worth making
   above the fold belongs to a course, and the root's whole job is to hand
   the visitor to /gsw/, /de/ or /pt-pt/. That is why this page carries no
   phone, no store badges and no scenes. It carries three links.

   It is also the one page whose height is a design constraint: the orange
   footer must stay off the first screen, so the hero is exactly 100svh and
   the footer follows it. Below 480px of window that stops being possible
   and the page is allowed to scroll instead of hiding half the chooser.

   THE HORIZON IS TWO NUMBERS. --globe-w is the disc's diameter and --crest
   is how far its highest point sits above the foot of the screen; the disc
   is then placed by subtracting one from the other, so the crest lands
   where it says it does at every window shape. Sizing in vw but positioning
   in svh is what makes a horizon slide up and down as a window is resized,
   and the chooser's bottom padding is written off --crest for the same
   reason: the copy clears the curve because it is told to, not because the
   numbers happened to agree at one size.
   ══════════════════════════════════════════════════════════════════════ */
.hero.root-hero{
  /* THE GLOBE IS THE GROUND, not a band along the bottom. Its crest sits
     ABOVE the top edge by --over and its equator lands at the foot of the
     screen, so what fills the window is the disc's top half: widest where
     the page ends, curving away into two corners of sky at the top. That
     is the whole composition, and it is two numbers.

     RADIUS = VIEWPORT HEIGHT is what puts the equator on the bottom edge,
     hence 210svh of diameter (the extra 10 buys the overshoot back). The
     112vw floor is for windows too wide for that to reach: the disc is at
     its widest exactly at the bottom, so a short wide window would run out
     of circle at the sides. Its half-width at the top edge is
     sqrt(R² - (R - over)²), which is what leaves the sky corners; on a
     phone that term exceeds the window and the globe covers everything,
     which is the same rule reaching a different answer, not a fault. */
  --globe-w:max(210svh,112vw);
  --over:5svh;
  height:100svh;
  min-height:560px;
  display:flex;
  flex-direction:column;
  background:#bee3f2;
}
/* The header lives INSIDE the chooser here, so it sheds everything the
   language pages give it as a page-level bar: the fixed 96px, the content
   column width, the margin:auto that would centre it in both axes inside a
   flex column, and the phone rules that stand it up to --head. */
.root-hero .chooser header{
  flex:none;width:auto;height:auto;min-height:0;margin:0;padding:0;
  justify-content:flex-start;
}
.root-hero .chooser .brand{height:auto;padding-top:0}

.worldrise{
  position:absolute;z-index:1;
  left:50%;
  width:var(--globe-w);
  top:calc(-1 * var(--over));
  transform:translateX(-50%);
  pointer-events:none;
  line-height:0;
}
.worldrise svg{display:block;width:100%;height:auto}
.worldrise .rot{transform-origin:50% 50%;animation:worldRoll 340s linear infinite}
/* The atmosphere: a white ring on the disc's own edge, so only its top arc
   shows and the horizon reads as lit rather than as a cut-out. A static
   box-shadow and not an SVG filter, which would re-filter 1700px of vector
   on every frame of the roll. */
.horizon-rim{
  position:absolute;inset:0;border-radius:50%;
  box-shadow:0 0 0 5px rgba(255,255,255,.5);
}
@keyframes worldRoll{to{transform:rotate(-360deg)}}

/* Left-aligned, on the HEADER's own width: the headline's left edge lands
   under the mark rather than near it, and the page reads down one column the
   way the language pages do. Centred only where the column is the whole
   screen, below 800px, where a left rag against a symmetrical planet just
   looks like a layout that fell over. */
/* The clouds cross in FRONT of the planet. Behind it they were invisible:
   the globe now covers all but two corners, so the only motion left on the
   page would have been a rotation slow enough to be imperceptible. */
.root-hero .cloud-stream{z-index:3}

/* AND THEY ARE THE ROOT'S OWN SIZE. The language pages hang their clouds
   over three hills a few hundred px tall, so a 105px cloud reads as a
   cloud there. Here the ground is a globe two viewport-heights across,
   and against it the same cloud reads as a speck of dust on the screen:
   the one thing on the page with nothing to be big NEXT to.

   So the root multiplies its clouds and nothing else does — --boost on
   the stream, which every page already has at 1. Three of the nine keep
   their original size (.36, .42, .48) and one takes half the boost,
   because a sky where every cloud is huge has no depth in it: the small
   ones are what put the big ones in front.

   THE MULTIPLIER HAS A CEILING and it is arithmetic, not taste. A cloud
   starts at `left` and travels 100vw+380px, scaled about its own centre,
   so it must be clear of both edges at the ends of that run:

     start  52.5 * size <= -left - 52.5      (hidden at 0%)
     end    52.5 * size <= left + 432.5      (hidden at 100%)

   left:-250px balances the two at size 3.48, and the largest cloud here
   is 1.12 * 3 = 3.36. Past that a cloud pops into being at the edge of
   the window mid-loop, which is the one way a background like this
   announces itself. */
.root-hero .cloud-stream{--sky:3}
.root-hero .cloud-stream .cloud{--boost:var(--sky);left:-250px}
.root-hero .cloud-3,.root-hero .cloud-6,.root-hero .cloud-8{--boost:1}
.root-hero .cloud-9{--boost:calc(var(--sky) / 1.5)}

/* A LIGHT GROUND FOR THE COPY. The globe turns, so whatever sits under the
   headline changes over five and a half minutes: ink on the land is fine,
   ink on the ocean is not, and orange on the ocean is worse. Rather than
   freeze the rotation or wash the whole page out, one soft ellipse of the
   site's own cream sits behind the copy column and nowhere else, so the
   planet stays saturated everywhere it is not being read over. Anchored to
   the chooser, so it follows the copy left on desktop and centre on phone.
   The numbers below are not taste: they are what measure_contrast found
   clears 4.5:1 for the paragraph at every angle of the roll. */
.chooser{
  position:relative;z-index:5;
  flex:1;min-height:0;
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
  gap:clamp(14px,2.6svh,30px);
  width:min(1240px,calc(100% - 64px));
  margin:0 auto;
  padding:clamp(10px,2svh,26px) 0;
  text-align:left;
}
/* Opaque cream, so the text stands on the same ground it does everywhere
   else on the site, and the shadow is the pills' own: a hard green offset
   with a soft one under it, which is what makes a thing look placed ON the
   planet rather than cut out of it. */
.chooser-card{
  display:flex;flex-direction:column;align-items:flex-start;
  gap:clamp(13px,2.3svh,26px);
  max-width:min(748px,100%);
  padding:clamp(20px,3svh,34px) clamp(24px,2.6vw,42px) clamp(24px,3.4svh,38px);
  border-radius:30px;
  background:var(--cream);
  box-shadow:0 7px 0 rgba(53,122,34,.19),0 26px 54px rgba(38,35,31,.19);
}
.chooser h1{
  margin:0;font:700 clamp(42px,7.4vw,88px)/.88 var(--display);
  letter-spacing:-3px;color:var(--ink);
}
.chooser h1 em{font-style:normal;color:var(--orange);text-shadow:0 5px 0 rgba(190,78,4,.15)}
/* The benefit line, sized to be read: it is the only sentence on the page
   that says what the visitor gets, so it is not the same weight as the
   detail under it. */
.chooser-lead{margin:0;max-width:40ch;font:500 clamp(17px,1.35vw,20px)/1.45 var(--body);color:#2f3d26}
.chooser-sub{margin:0;max-width:56ch;font-size:14.5px;line-height:1.6;color:#5c6b50}

/* The three courses. Same pill the language pages wear in their tab row,
   grown to the size of the thing the page is FOR, and orange on hover
   because that is what the tab row already does to the course you are on. */
.courses{display:flex;flex-wrap:wrap;justify-content:flex-start;gap:12px}
.courses a{
  display:inline-flex;align-items:center;gap:11px;
  padding:9px 22px 9px 9px;border-radius:40px;
  background:#fff;border:1px solid #e3dfd8;box-shadow:0 5px 0 #e3dfd8;
  color:var(--ink);text-decoration:none;
  font:600 clamp(15px,1.55vw,18px) var(--display);
  transition:background-color .13s ease,border-color .13s ease,
             box-shadow .13s ease,transform .13s ease;
}
.courses a img{width:clamp(30px,3vw,38px);height:clamp(30px,3vw,38px);border-radius:50%;flex:none}
.courses a:hover,.courses a:focus-visible{
  background:var(--orange);border-color:var(--deep);
  box-shadow:0 5px 0 var(--deep);color:#fff;
}
.courses a:active{transform:translateY(2px);box-shadow:0 3px 0 var(--deep)}
.courses a:focus-visible{outline:3px solid var(--blue);outline-offset:4px}

@media(max-width:800px){
  .hero.root-hero{--over:4svh;min-height:520px}
  /* A CLOUD IS BIG NEXT TO SOMETHING. Three times over a 1440px window is
     a cloud with a planet behind it; the same three times over a 430px
     phone is a white shape crossing four fifths of the screen, which is
     weather rather than sky. The multiplier comes down and the small ones,
     which do not take it, are unchanged. */
  .root-hero .cloud-stream{--sky:1.8}
  .chooser{
    width:calc(100% - 32px);
    align-items:center;text-align:center;
  }
  .chooser-card{
    align-items:center;width:100%;gap:clamp(11px,2svh,20px);
    padding:clamp(18px,2.6svh,28px) 20px clamp(20px,3svh,30px);
    border-radius:26px;
  }
  .chooser h1{font-size:min(13vw,7svh,58px);letter-spacing:-2px}
  .chooser-lead{font-size:16px;max-width:30ch}
  .chooser-sub{font-size:13px;max-width:38ch}
  /* full width, so a course is a thumb-sized target rather than a chip */
  .courses{gap:9px;width:100%;max-width:340px;justify-content:center}
  .courses a{width:100%;justify-content:flex-start;padding:8px 18px 8px 8px}
}
/* Too short to hold the composition. Let it scroll rather than crop the
   chooser, which is the one thing on the page that has to be reachable. */
/* A SHORT WINDOW, which on a phone means landscape. The composition does
   not shrink on its own: the headline is three lines of a size driven by
   WIDTH, and a landscape phone is wide, so at 844x390 the group came to
   455px in a 390px window and the last course fell off the bottom. So the
   whole block tightens rather than the page scrolling, which on a screen
   this short would hide the one thing the page is for.
   min-height:100svh, not height:auto: once the group fits, an auto height
   would pull the hero up to the content and float the orange footer into
   the first screen, which is the rule this page exists to keep. */
@media(max-height:520px){
  .hero.root-hero{height:auto;min-height:100svh}
  .chooser{padding:14px 0;gap:8px}
  .chooser .brand{font-size:28px}
  .chooser h1{font-size:clamp(30px,4.4vw,40px);letter-spacing:-1.4px}
  .chooser-lead{font-size:15px;line-height:1.35}
  .chooser-sub{font-size:12.5px;line-height:1.45}
  .chooser-card{gap:9px;padding:14px 22px 17px;border-radius:22px}
  .courses{gap:8px}
}
@media(prefers-reduced-motion:reduce){
  .worldrise .rot{animation:none}
  .courses a{transition:none}
}
