/* Lahore Watch Company — design system. See DESIGN-CONTRACT.md before editing.
   Type:  Jost. Modular scale 1.333 (perfect fourth) from 16px → --s1…--s7, plus --display.
   Space: 8px grid. Section rhythm --sec-pad / --sec-pad-s. Splits: golden ratio (--phi).
   Theme: tokens, not selector prefixes. Dark is :root; light re-declares the SAME names
          on body.light. Never hand-write a colour, duration, tracking or section padding. */

/* Dark palette. Applies to :root and to any dark island inside a light page:
   .st-mark (the emblem band) and .gal .ph (the near-black render plates). */
:root,.st-mark,.gal .ph{
  color-scheme:dark;
  --bg:#0A0A0A; --bg-rgb:10,10,10;
  --ink:#EDEBE6; --mute:#8F8B84;
  --line:rgba(237,235,230,.14); --line-2:rgba(237,235,230,.26);
  --glass:rgba(10,10,10,.72); --glass-2:rgba(14,14,14,.86);
  --focus:var(--brass);
}
:root{
  --paper:#EFEDE8; --paper-ink:#161616; --paper-mute:#6B6863; --paper-line:rgba(22,22,22,.14);
  --brass:#BFA06A;
  --scrim:#050505; --scrim-rgb:5,5,5;
  --ok:#9EC1A6; --err:#D98B7A;
  --f:"Jost","Futura","Century Gothic","Avenir Next",Arial,sans-serif;
  --s1:.75rem; --s2:1rem; --s3:1.333rem; --s4:1.777rem; --s5:2.369rem; --s6:3.157rem; --s7:4.209rem;
  --display:clamp(3rem,7.6vw,7.5rem);
  --track-1:.08em; --track-2:.16em; --track-3:.24em;
  --dur-0:.12s; --dur-1:.3s; --dur-2:.5s; --dur-3:1.2s;
  /* Signature reveals. These are client-signed speeds, NOT a continuation of the --dur-0…3
     feedback scale, and nothing may be moved onto --dur-3 to "tidy" them:
       --dur-4  the emblem clip-path wipe (.emb)
       --dur-5  the slow image scale-in (.st-hero-img img; .ct-vis .ct-layer) */
  --dur-4:2.2s; --dur-5:2.4s;
  --ease:cubic-bezier(.22,.8,.2,1);
  --hdr-h:72px; --m:clamp(24px,5.5vw,96px);
  --sec-pad:clamp(96px,12vw,160px); --sec-pad-s:clamp(64px,9vw,128px);
  --phi:1.618fr;
}
*{box-sizing:border-box}
/* The gutter is reserved on every page, always: the burger sheet locks the body while it is
   open, and the >=901px builder shell is overflow:hidden for its whole life. Without this,
   each of those removes a scrollbar that was there a frame earlier and shifts the layout
   sideways under the reader — and the same jump happens between a scrolling page and the
   builder on every navigation, since these are real full page loads. */
html{-webkit-text-size-adjust:100%;scrollbar-gutter:stable}
body{margin:0;background:var(--bg);color:var(--ink);font:400 var(--s2)/1.6 var(--f);-webkit-font-smoothing:antialiased;letter-spacing:.005em}
img,canvas,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button,input,select,textarea{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--focus);outline-offset:4px}
h1,h2,h3{margin:0;font-weight:300;letter-spacing:-.02em;line-height:1.04;text-wrap:balance}
p{margin:0}
.label{font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase}
.mute{color:var(--mute)}
.pad{padding-inline:var(--m)}
[hidden]{display:none!important}

/* states — global. Every value below is a token, so all of it themes itself. */
::selection{background:var(--ink);color:var(--bg)}
::placeholder{color:var(--mute);opacity:1}
/* Chrome paints a pale autofill wash over our transparent, border-bottom-only fields.
   The 1000px inset shadow repaints the field in the page colour; the absurd delay keeps
   Chrome from re-applying its own background after the initial frame. */
input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,
textarea:-webkit-autofill,textarea:-webkit-autofill:focus,select:-webkit-autofill{
  -webkit-text-fill-color:var(--ink);caret-color:var(--ink);
  -webkit-box-shadow:inset 0 0 0 1000px var(--bg);box-shadow:inset 0 0 0 1000px var(--bg);
  transition:background-color 9999s var(--ease) 0s,color 9999s var(--ease) 0s,border-color var(--dur-1) var(--ease)}
/* .fld input/textarea set outline:none, so restore a focus cue that is not colour-only:
   the rule thickens 1px→2px while padding-bottom drops 12px→11px, so nothing shifts.
   Doubled :focus:focus-visible to out-specify BOTH `.fld input:focus` and the autofill
   rule above — and it deliberately avoids box-shadow, which autofill needs. */
.fld input:focus:focus-visible,.fld textarea:focus:focus-visible{
  border-color:var(--focus);border-bottom-width:2px;padding-bottom:11px}
/* builder panel scrollbar — .p-body has scrollbar-width:thin, which Chrome/Safari ignore */
.p-body::-webkit-scrollbar{width:6px;height:6px}
.p-body::-webkit-scrollbar-track{background:transparent}
.p-body::-webkit-scrollbar-thumb{background:var(--line-2)}
.p-body::-webkit-scrollbar-thumb:hover{background:var(--mute)}

/* Skip link — the first focusable thing in every document, sitting just before .hdr.
   position:fixed means it never occupies layout while parked, so it cannot push the header
   down or open a scroll gap; it is moved out of the viewport by transform rather than by
   clip/width:1px so it is still a real, findable link for assistive tech. Every value is a
   token, so it reads correctly on the dark pages AND on the light Story page. z-index clears
   .hdr (50) and .sheet (45). Parked at top:8px, not 0, so the global :focus-visible ring's
   4px offset has somewhere to draw. */
.skip{position:fixed;z-index:60;top:8px;left:var(--m);padding:12px 24px;background:var(--bg);color:var(--ink);border:1px solid var(--line-2);font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase;white-space:nowrap;transform:translateY(calc(-100% - 16px));transition:transform var(--dur-1) var(--ease)}
.skip:focus{transform:none}

/* header */
.hdr{position:fixed;inset:0 0 auto;z-index:50;height:calc(var(--hdr-h) + env(safe-area-inset-top,0px));padding-top:env(safe-area-inset-top,0px);display:flex;align-items:center;justify-content:space-between;padding-inline:var(--m);transition:background var(--dur-2) var(--ease),backdrop-filter var(--dur-2) var(--ease)}
.hdr.solid{background:var(--glass);backdrop-filter:blur(18px) saturate(1.3);-webkit-backdrop-filter:blur(18px) saturate(1.3)}
.logo{display:flex;align-items:center;gap:12px}
.logo svg{width:22px;height:24px;color:var(--ink)}
.logo span{font-size:var(--s1);font-weight:500;letter-spacing:.3em;text-transform:uppercase;line-height:1.3}
.nav{display:flex;gap:48px}
.nav a{font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase;color:var(--mute);transition:color var(--dur-1) var(--ease);position:relative;padding-block:8px}
.nav a:hover,.nav a[aria-current="page"]{color:var(--ink)}
/* brass = current. See DESIGN-CONTRACT.md § brass. */
.nav a[aria-current="page"]::after{content:"";position:absolute;left:0;right:var(--track-3);bottom:0;height:1px;background:var(--brass)}
.burger{display:none;width:48px;height:48px;margin-right:-12px;border:0;background:none;cursor:pointer;position:relative}
.burger i,.burger i::before{position:absolute;left:12px;width:24px;height:1px;background:var(--ink);content:"";transition:transform var(--dur-2) var(--ease)}
.burger i{top:20px}.burger i::before{top:8px;left:0}
.burger[aria-expanded="true"] i{transform:translateY(4px) rotate(45deg)}
.burger[aria-expanded="true"] i::before{transform:translateY(-8px) rotate(-90deg)}
/* ---- CSS CONTRACT FOR app.js menu(). Read this before rewriting the sheet. ----
   1. The `hidden` attribute in the HTML is the no-JS fallback and the only thing holding
      the sheet out of the page when the module never runs. menu() removes it ONCE on init
      and must then NEVER set it again: while `hidden` is on the element,
      [hidden]{display:none!important} wins and no transition can ever run.
   2. `.open` on #sheet is the single source of truth for the panel.
        open  -> s.classList.add("open");    b.setAttribute("aria-expanded","true")
        close -> s.classList.remove("open"); b.setAttribute("aria-expanded","false")
      aria-expanded drives the burger icon (.burger[aria-expanded="true"]), so the class and
      the attribute are always set together, from a single close() helper.
   3. Do not write inline styles onto #sheet — an inline opacity/display out-specifies these
      rules and strands the panel mid-fade. Locking document.body.style.overflow is fine;
      that is not on the sheet. (html{scrollbar-gutter:stable} keeps that lock shift-free.)
   4. The closed state is visibility:hidden, so the links leave the tab order and the a11y
      tree on their own between opens — no inert bookkeeping needed for that. Escape, the
      focus trap, moving focus in, returning it to #burger and closing on a click outside
      are still JS's job, as is closing the sheet if the viewport crosses back above 760px
      while it is open (the burger is display:none up there, so an open sheet would trap).
   5. --dur-2 is the burger's duration too, so icon and panel read as one gesture. The
      closed rule delays `visibility` by exactly that long, which is what lets the panel
      fade OUT instead of vanishing on the first frame — keep both transition lists intact
      if you touch this. Under prefers-reduced-motion the blanket zeroes duration and delay
      together, so the swap is instant and still correct. */
.sheet{position:fixed;inset:0;z-index:45;background:var(--bg);display:flex;flex-direction:column;justify-content:center;gap:24px;padding-inline:var(--m);opacity:0;visibility:hidden;transform:translateY(-8px);transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease),visibility 0s linear var(--dur-2)}
.sheet.open{opacity:1;visibility:visible;transform:none;transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease),visibility 0s}
/* .nav a's language at sheet scale: mute at rest, ink on hover and press, brass hairline for
   the current page (brass = current). chrome() marks .sheet a[aria-current="page"] — below
   760px .nav is display:none, so this is the ONLY place the current page can be marked.
   width:fit-content keeps that hairline the width of the word instead of the viewport;
   line-height comes down from the inherited 1.6 because a 50px word in an 81px line box
   would hang the rule far below its own baseline. */
.sheet a{width:fit-content;font-size:var(--s6);font-weight:300;letter-spacing:-.02em;line-height:1.2;color:var(--mute);position:relative;transition:color var(--dur-1) var(--ease)}
.sheet a:hover,.sheet a:active,.sheet a[aria-current="page"]{color:var(--ink)}
.sheet a[aria-current="page"]::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--brass)}
@media (max-width:760px){.nav{display:none}.burger{display:block}}
/* No .light overrides here: the header is written entirely in --ink/--mute/--glass,
   which body.light re-declares. This also fixes .hdr.solid and .sheet on /story/. */

/* buttons */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:16px;height:56px;padding-inline:32px;border:1px solid var(--ink);background:var(--ink);color:var(--bg);font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase;cursor:pointer;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),transform var(--dur-0) var(--ease)}
.btn:hover{background:transparent;color:var(--ink)}
.btn svg{width:16px;height:16px;transition:transform var(--dur-1) var(--ease)}
.btn:hover svg{transform:translateX(4px)}
.btn:active{transform:translateY(1px)}
/* app.js disables #send for the duration of the POST. */
.btn:disabled,.btn[aria-disabled="true"]{background:transparent;color:var(--mute);border-color:var(--line-2);cursor:not-allowed}
.btn:disabled:hover,.btn[aria-disabled="true"]:hover{background:transparent;color:var(--mute)}
.btn:disabled:hover svg,.btn[aria-disabled="true"]:hover svg{transform:none}
.btn:disabled:active,.btn[aria-disabled="true"]:active{transform:none}
#send:disabled{cursor:progress}
.link{font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase;display:inline-flex;align-items:center;gap:12px;padding-block:8px;border-bottom:1px solid var(--line);transition:border-color var(--dur-1) var(--ease)}
.link:hover{border-color:var(--ink)}

/* ================= HOME ================= */
.hero{position:relative;height:100svh;min-height:600px;overflow:hidden;background:var(--scrim)}
.hero-img{position:absolute;inset:0;will-change:transform}
.hero-img img{width:100%;height:100%;object-fit:cover;object-position:62% 50%}
.hero::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(var(--scrim-rgb),.92) 0%,rgba(var(--scrim-rgb),.55) 38.2%,rgba(var(--scrim-rgb),0) 70%),linear-gradient(0deg,rgba(var(--scrim-rgb),.9) 0%,rgba(var(--scrim-rgb),0) 38.2%)}
.hero-copy{position:absolute;z-index:2;left:var(--m);right:var(--m);top:38.2%;max-width:720px;will-change:transform}
.hero h1{font-size:var(--display)}
/* one step down from --ink, derived from it rather than a one-off literal. Not opacity:
   .rise ends on opacity:1 with fill-mode both, which would overwrite it. */
.hero p{margin-top:32px;max-width:34ch;font-size:var(--s3);font-weight:300;color:color-mix(in srgb,var(--ink) 80%,var(--scrim));line-height:1.5}
.hero .acts{display:flex;flex-wrap:wrap;align-items:center;gap:32px;margin-top:48px}
.scrollcue{position:absolute;z-index:2;left:var(--m);bottom:calc(32px + env(safe-area-inset-bottom,0px));display:flex;align-items:center;gap:16px;color:var(--mute)}
.scrollcue i{width:48px;height:1px;background:var(--line);position:relative;overflow:hidden}
.scrollcue i::after{content:"";position:absolute;inset:0;background:var(--ink);transform:translateX(-100%);animation:cue 2.4s var(--ease) infinite}
@keyframes cue{60%{transform:translateX(0)}100%{transform:translateX(100%)}}
.rise{animation:rise 1.4s var(--ease) both}
.r1{animation-delay:.12s}.r2{animation-delay:.24s}.r3{animation-delay:.36s}
@keyframes rise{from{opacity:.001;transform:translateY(24px)}to{opacity:1;transform:none}}
@media (max-width:760px){
  /* keeps the system's 38.2% stop; the mid alpha is raised to .7 to hold legibility */
  .hero::after{background:linear-gradient(0deg,rgba(var(--scrim-rgb),.95) 0%,rgba(var(--scrim-rgb),.7) 38.2%,rgba(var(--scrim-rgb),.1) 100%)}
  .hero-copy{top:auto;bottom:96px}
  .hero-img img{object-position:70% 50%}
}

/* anatomy: scroll-driven exploded 3D */
.anat{position:relative;height:560svh;background:radial-gradient(ellipse 60% 50% at 61.8% 50%,#1b1b1c 0%,var(--bg) 70%)}
.anat-stick{position:sticky;top:0;height:100svh;display:grid;grid-template-columns:1fr var(--phi);align-items:center;overflow:hidden}
.anat canvas{position:absolute;inset:0;width:100%;height:100%;will-change:opacity}
.anat-video{position:absolute;inset:0;opacity:0;pointer-events:none;overflow:hidden;will-change:opacity}
.anat-video video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:40% 50%;filter:brightness(.74) contrast(1.15) saturate(.9);transform:scale(var(--z,1.08));will-change:transform}
.anat-video::after{content:"";position:absolute;inset:0;background:radial-gradient(ellipse 75% 85% at 62% 50%,rgba(var(--bg-rgb),0) 40%,rgba(var(--bg-rgb),.85) 100%)}
.anat-cap{position:relative;z-index:2;padding-left:var(--m);pointer-events:none}
.anat-stick::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:linear-gradient(90deg,rgba(var(--bg-rgb),.9) 0%,rgba(var(--bg-rgb),.55) 26%,rgba(var(--bg-rgb),0) 44%)}
@media (max-width:760px){.anat-stick::before{background:linear-gradient(0deg,rgba(var(--bg-rgb),.92) 0%,rgba(var(--bg-rgb),.5) 30%,rgba(var(--bg-rgb),0) 50%)}}
.cap{position:absolute;top:50%;left:var(--m);right:0;transform:translateY(-50%);opacity:0;transition:opacity .7s var(--ease),transform .7s var(--ease)}
.cap.on{opacity:1;transform:translateY(-50%)}
.cap:not(.on){transform:translateY(calc(-50% + 16px))}
/* .anat-cap is pointer-events:none so the empty half of the panel stays transparent to the
   canvas; the live caption's own text takes them back so it can be selected and copied. */
.cap.on h2,.cap.on p{pointer-events:auto}
.cap h2{font-size:var(--s6)}
.cap p{margin-top:16px;color:var(--mute);max-width:26ch}
.anat-prog{position:absolute;z-index:2;left:var(--m);bottom:calc(48px + env(safe-area-inset-bottom,0px));display:flex;gap:8px}
.anat-prog i{width:32px;height:1px;background:var(--line);transition:background var(--dur-2) var(--ease)}
/* brass = current. See DESIGN-CONTRACT.md § brass. */
.anat-prog i.on{background:var(--brass)}
@media (max-width:760px){
  .anat-stick{grid-template-columns:1fr}
  .anat-cap{position:absolute;inset:auto 0 96px 0}
  .cap{top:auto;bottom:0;transform:none}.cap.on{transform:none}.cap:not(.on){transform:translateY(16px)}
  .cap h2{font-size:var(--s5)}
}

/* figures */
/* Two facts, not a three-stat bar. Golden-ratio split with 100 dominant, and the
   supporting figure set a golden ratio smaller so the weighting reads in type too. */
.figs{display:grid;grid-template-columns:var(--phi) 1fr;border-top:1px solid var(--line)}
.fig{padding:64px 32px 96px 0;border-right:1px solid var(--line)}
.fig:last-child{border-right:0}
.fig+.fig{padding-left:32px}
.fig b{display:block;font-weight:300;font-size:var(--display);line-height:1;letter-spacing:-.04em;font-variant-numeric:tabular-nums}
.fig+.fig b{font-size:calc(var(--display) * .618)}
.fig span{display:block;margin-top:24px;color:var(--mute)}
@media (max-width:760px){.figs{grid-template-columns:1fr}.fig{border-right:0;border-bottom:1px solid var(--line);padding:48px 0!important}}

.statement{padding-block:var(--sec-pad) var(--sec-pad-s)}
/* editorial body copy, not a display role — its maximum is exactly --s7. See contract §4. */
.statement p{font-size:clamp(2rem,4.2vw,var(--s7));font-weight:300;line-height:1.14;letter-spacing:-.02em;max-width:18ch}

.cta{display:grid;grid-template-columns:var(--phi) 1fr;align-items:end;gap:48px;padding-block:var(--sec-pad-s);border-top:1px solid var(--line)}
.cta h2{font-size:var(--display)}
.cta div{display:grid;gap:24px;justify-items:start}
@media (max-width:760px){.cta{grid-template-columns:1fr}}

/* footer */
.ftr{display:grid;grid-template-columns:var(--phi) 1fr 1fr;gap:48px;padding-block:96px 48px;border-top:1px solid var(--line);color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1)}
.ftr a:hover{color:var(--ink)}
.ftr nav,.ftr address{display:grid;gap:12px;font-style:normal;align-content:start}
.ftr small{grid-column:1/-1;padding-top:48px;border-top:1px solid var(--line);font-size:var(--s1)}
@media (max-width:760px){.ftr{grid-template-columns:1fr}}

/* ================= BUILD ================= */
body.page-build{overflow:hidden}
.bld{position:fixed;inset:0;display:grid;grid-template-columns:1fr 416px;background:radial-gradient(ellipse 55% 60% at 45% 50%,#1d1d1f 0%,var(--bg) 70%)}
.bld-stage{position:relative;min-width:0}
.bld-stage canvas{position:absolute;inset:0;width:100%;height:100%;cursor:grab;touch-action:none}
.bld-stage canvas:active{cursor:grabbing}
.bld-hud{position:absolute;left:var(--m);bottom:40px;display:flex;align-items:center;gap:32px;z-index:2}
/* .views is layout only. Its three buttons are .ch.on-stage — one chip, one place.
   See DESIGN-CONTRACT.md §5. They keep aria-pressed: the group must be able to say
   "none of these is the current view", which it does the moment the user drags. */
.views{display:flex;flex-wrap:wrap;gap:8px}
.live{position:absolute;left:var(--m);top:104px;z-index:2;display:grid;gap:4px}
.live b{font-weight:300;font-size:var(--s4);letter-spacing:.02em;font-variant-numeric:tabular-nums}
.live span{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase}
.hint{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase}
.panel{position:relative;z-index:3;background:var(--glass-2);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border-left:1px solid var(--line);display:flex;flex-direction:column;min-height:0;padding-top:calc(var(--hdr-h) + env(safe-area-inset-top,0px))}
.p-head{padding:32px 40px 24px}
.p-head h1{font-size:var(--s5)}
.p-head .ref{margin-top:12px;color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-2)}
.p-body{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:0 40px 24px;scrollbar-width:thin}
.acc{border-top:1px solid var(--line)}
.acc:last-child{border-bottom:1px solid var(--line)}
/* three flex items now: title, summary, marker. <b> takes the slack so the summary
   stays hard against the marker instead of floating in the middle. */
.acc>button{width:100%;display:flex;align-items:baseline;gap:16px;padding:24px 0;border:0;background:none;cursor:pointer;text-align:left}
.acc>button b{font-weight:400;font-size:var(--s3);margin-right:auto}
.acc>button span{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1);text-align:right;max-width:55%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Shared open-state marker — the contact FAQ's "+" rotating to "×", promoted to serve
   both accordions. Brass on open is the one sanctioned brass use inside the builder:
   it marks the current section. See DESIGN-CONTRACT.md §3 and §6. */
.acc>button::after,.ct-faq summary::after{content:"+";flex:none;align-self:baseline;color:var(--mute);font-size:var(--s3);font-weight:300;line-height:1;transition:transform var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.acc.open>button::after,.ct-faq details[open] summary::after{transform:rotate(45deg);color:var(--brass)}

/* Expand on grid-template-rows, not max-height: the bodies are wrapping chip rows whose
   height depends on panel width and on whether the conditional strap fold is showing, so
   any max-height cap would make the easing lie. 0fr→1fr interpolates the real height with
   zero measurement and nests without coordination — which is what .fold needs.
   .acc-in must NOT use gap: a track gap is paid even when the track is 0fr, so a collapsed
   fold would still leave 24px of dead space. The rhythm is padding-top on each row, and for
   a fold it lives INSIDE the clipped wrapper. */
.acc-body{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--dur-2) var(--ease),opacity var(--dur-1) var(--ease)}
.acc.open>.acc-body{grid-template-rows:1fr;opacity:1}
.acc-body>*{overflow:hidden;min-height:0}
/* .acc-in must also carry no padding of its own: it is the element 0fr clamps to zero, and
   under box-sizing:border-box a padding floors the border box above the row size, so 32px of
   it would survive the collapse. The trailing rhythm is generated content instead, which is
   inside the clipped box and disappears with it. */
.acc-in::after{content:"";display:block;height:32px}
.acc-in>*{padding-top:24px}
.acc-in>:first-child{padding-top:0}
.fold{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--dur-2) var(--ease)}
.acc-in>.fold{padding-top:0}
.fold.open{grid-template-rows:1fr}
.fold>*{overflow:hidden;min-height:0}
.fold-in>*{padding-top:24px}
.opt-l{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase;margin-bottom:12px}
.sws{display:flex;flex-wrap:wrap;gap:12px}
.sw{width:40px;height:40px;border-radius:50%;border:0;padding:0;cursor:pointer;position:relative;box-shadow:inset 0 0 0 1px rgba(255,255,255,.12)}
.sw::after{content:"";position:absolute;inset:-5px;border-radius:50%;border:1px solid transparent;transition:border-color var(--dur-1) var(--ease)}
.sw[aria-checked="true"]::after{border-color:var(--ink)}
/* The swatches carry no visible name. A tooltip would be clipped (.p-body scrolls, so its
   used overflow-x is auto too) and a hover scale would collide with the -5px rings, so the
   row gets a persistent caption: hovered-or-focused, falling back to selected. It doubles
   as the selected-value readout these rows never had. */
.sw-cap{margin-top:12px;min-height:12px;color:var(--mute);font-size:var(--s1);line-height:1;letter-spacing:var(--track-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* One chip. .ch and .views button were the same control declared twice. §5. */
.chs{display:flex;flex-wrap:wrap;gap:8px}
.ch{height:40px;padding-inline:16px;border:1px solid var(--line);background:none;cursor:pointer;color:var(--mute);font-size:var(--s1);font-weight:500;letter-spacing:var(--track-2);text-transform:uppercase;display:inline-flex;align-items:center;gap:8px;transition:color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease),transform var(--dur-0) var(--ease)}
.ch:active{transform:translateY(1px)}
.ch[aria-pressed="true"],.ch[aria-checked="true"]{color:var(--ink);border-color:var(--ink)}
/* the only permitted divergence: chips floating over the WebGL stage need a backing */
.ch.on-stage{background:var(--glass);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.mv{display:grid;gap:8px}
.mv button{display:grid;grid-template-columns:1fr auto;gap:4px 16px;padding:16px;border:1px solid var(--line);background:none;cursor:pointer;text-align:left;transition:border-color var(--dur-1) var(--ease)}
.mv button b{font-weight:400}
.mv button small{grid-column:1/-1;color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1)}
.mv button span{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1);align-self:center}
.mv button[aria-checked="true"]{border-color:var(--ink)}
/* Hover, everywhere it was missing. Inside (hover:hover) so a tap does not leave a touch
   device stuck showing hover on the last thing pressed. Hover is --line-2, never --ink:
   jumping straight to --ink would make hover indistinguishable from selected. */
@media (hover:hover){
  .ch:hover{color:var(--ink);border-color:var(--line-2)}
  .sw:hover::after{border-color:var(--line-2)}
  .sw[aria-checked="true"]:hover::after{border-color:var(--ink)}
  .mv button:hover{border-color:var(--line-2)}
  .mv button[aria-checked="true"]:hover{border-color:var(--ink)}
  .acc>button:hover b,.ct-faq summary:hover{color:var(--ink)}
  .acc>button:hover::after,.ct-faq summary:hover::after{color:var(--ink)}
  .acc.open>button:hover::after,.ct-faq details[open] summary:hover::after{color:var(--brass)}
  .buildcard button:hover{color:var(--ink)}
}
.fld{display:grid;gap:8px}
/* .flab is the contact form's label for a chip group — a <span>, because the group is a
   radiogroup labelled by id, not a <label> for one control. Same treatment, one rule. */
.fld label,.flab{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase}
.fld input,.fld textarea{width:100%;background:none;border:0;border-bottom:1px solid var(--line);padding:8px 0 12px;font-size:var(--s3);font-weight:300;border-radius:0;transition:border-color var(--dur-1) var(--ease)}
.fld input:focus,.fld textarea:focus{outline:none;border-color:var(--ink)}
.p-foot{padding:24px 40px calc(24px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--line)}
.p-foot .btn{width:100%}
@media (max-width:900px){
  body.page-build{overflow:auto}
  .bld{position:static;display:block;background:none}
  .bld-stage{position:sticky;top:0;height:56svh;background:radial-gradient(ellipse 70% 60% at 50% 50%,#1d1d1f 0%,var(--bg) 75%);z-index:1}
  .live{top:80px}.live b{font-size:var(--s3)}
  .bld-hud{bottom:16px;left:24px;right:24px;justify-content:space-between}
  .hint{display:none}
  .panel{padding-top:0;border-left:0;background:var(--bg);position:relative;z-index:2}
  .p-head,.p-body{padding-inline:24px}
  .p-foot{position:sticky;bottom:0;background:var(--bg);padding-inline:24px}
}
/* build/index.html carries the same footer partial as every other page — byte-identical, so
   the four shells stay in sync. It is wanted below 901px, where the block above unlocks the
   body and the whole page scrolls normally. At 901px and up the builder is a fixed,
   non-scrolling shell (body.page-build{overflow:hidden} + .bld{position:fixed;inset:0}):
   there is nowhere for a footer to be seen, and leaving it in flow parks a run of links
   below a fold that only the keyboard can reach. display:none takes it out of the tab order
   with it. This is the ONLY rule that may hide the shared footer. */
@media (min-width:901px){body.page-build .ftr{display:none}}

/* ================= STORY (light) ================= */
/* Light is a TOKEN SCOPE, not a selector prefix. Re-declare names; never restyle components.
   --scrim is deliberately NOT flipped: it sits behind imagery, which is dark in both themes.
   --focus is brass darkened to #8D764E — 3.71:1 on #EFEDE8 (WCAG 1.4.11 needs 3:1). */
body.light{
  color-scheme:light;
  --bg:var(--paper); --bg-rgb:239,237,232;
  --ink:var(--paper-ink); --mute:var(--paper-mute);
  --line:var(--paper-line); --line-2:rgba(22,22,22,.26);
  --glass:rgba(239,237,232,.72); --glass-2:rgba(239,237,232,.88);
  --focus:#8D764E; --ok:#576A5B; --err:#825349;
  background:var(--bg);color:var(--ink)
}
.st-hero{min-height:100svh;display:grid;grid-template-columns:var(--phi) 1fr;gap:clamp(32px,6vw,96px);align-items:end;padding-top:calc(var(--hdr-h) + clamp(40px,7vw,72px));padding-bottom:var(--sec-pad-s)}
/* .st-hero-copy used to have no rule at all and worked only by being the grid's first child.
   It now owns the column's internal rhythm, which removes two magic margins below. */
.st-hero-copy{display:grid;align-content:end;gap:40px}
.st-hero h1{font-size:var(--display)}
.st-hero .lead{font-size:var(--s3);font-weight:300;line-height:1.5;color:var(--mute);max-width:32ch}
.st-hero-img{margin:0;height:min(72svh,760px);overflow:hidden;background:var(--scrim)}
.st-hero-img img{width:100%;height:100%;object-fit:cover;object-position:58% 50%;transform:scale(1.08);transition:transform var(--dur-5) var(--ease)}
.st-hero-img.in img{transform:scale(1)}
/* Three claims as stacked rows grouped by hairlines — not three equal icon cards. Golden-ratio
   split: the claim takes the wide column, the sentence that substantiates it the narrow one.
   A <dl> rather than headings: these are term/definition pairs, so there is no heading level
   for them to be wrong at. */
.st-prin{padding-block:var(--sec-pad-s);border-top:1px solid var(--line)}
.prin{margin:0}
.prin-row{display:grid;grid-template-columns:var(--phi) 1fr;gap:clamp(24px,5vw,80px);align-items:baseline;padding-block:clamp(24px,3.5vw,40px)}
.prin-row:first-child{padding-top:0}
.prin-row:last-child{padding-bottom:0}
.prin-row+.prin-row{border-top:1px solid var(--line)}
.prin-row dt{font-size:var(--s4);line-height:1.15;letter-spacing:-.02em;max-width:20ch}
.prin-row dd{margin:0;color:var(--mute);font-weight:300;line-height:1.5;max-width:46ch}
@media (max-width:900px){.st-hero{grid-template-columns:1fr;min-height:0}.st-hero-img{height:56svh;order:-1}.prin-row{grid-template-columns:1fr;gap:12px;align-items:start}}

/* Dark island inside the light page. Its token values are declared up top in the
   `:root,.st-mark,.gal .ph` group so .emb / .st-mark .label / .st-mark p keep resolving to
   the DARK palette. Do not add hand-written colours here — re-declare a token instead. */
.st-mark{background:var(--bg);color:var(--ink)}
.st-mark-in{display:grid;grid-template-columns:1fr var(--phi);gap:clamp(48px,8vw,128px);align-items:center;padding-block:var(--sec-pad)}
/* 2.2s is signed off. Do not speed it up: the interruption problem is fixed in the observer. */
.emb{width:min(100%,360px);height:auto;color:var(--brass);justify-self:center;clip-path:inset(100% 0 0 0);transition:clip-path var(--dur-4) var(--ease)}
.emb.in{clip-path:inset(0 0 0 0)}
.st-mark .label{color:var(--mute)}
.st-mark h2{font-size:var(--s6);margin-top:24px}
.st-mark p:not(.label){margin-top:24px;font-size:var(--s3);font-weight:300;line-height:1.5;color:var(--mute);max-width:32ch}
@media (max-width:760px){.st-mark-in{grid-template-columns:1fr}.emb{width:180px;justify-self:start}}

.st-gal{padding-block:var(--sec-pad) var(--sec-pad-s)}
/* One rule for both section heads — they were identical declarations, and .st-steps-head
   used to push its spacing onto .steps instead of owning it. */
.st-gal-head,.st-steps-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:end;gap:24px 32px;margin-bottom:64px}
.st-gal-head h2,.st-steps-head h2{font-size:var(--s6)}
/* Four builder configurations at four weights, each one a link that preloads the builder
   with that configuration. Mobile-first: one column by default, so it always reaches 1. */
.gal{display:grid;grid-template-columns:1fr;column-gap:clamp(24px,3vw,40px);row-gap:clamp(40px,7vw,64px);align-items:start}
.gal-i{display:block;color:inherit}
.gal .ph{display:block;aspect-ratio:4/5;background:radial-gradient(ellipse 70% 60% at 50% 45%,#232325 0%,#0c0c0d 75%);overflow:hidden;position:relative}
/* Degradation: the <img> ships without a src — app.js injects one when the section nears the
   viewport. With JS off or WebGL unavailable that never happens, so the tile would stay an
   empty shimmering box forever. ::before keeps the configuration readable until .ready lands,
   and being a pseudo-element it is invisible to assistive tech, so nothing reads twice. */
.gal .ph::before{content:attr(data-spec);position:absolute;left:16px;right:16px;bottom:16px;z-index:1;font-size:var(--s1);font-weight:500;letter-spacing:var(--track-2);text-transform:uppercase;line-height:1.6;color:var(--mute);transition:opacity var(--dur-1) var(--ease)}
.gal .ph.ready::before{opacity:0}
.gal .ph::after{content:"";position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.06) 50%,transparent 70%);background-size:200% 100%;animation:shim 1.6s linear infinite}
.gal .ph.ready::after{display:none}
@keyframes shim{to{background-position:-200% 0}}
.gal img{width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity var(--dur-3) var(--ease),transform var(--dur-3) var(--ease)}
.gal .ready img{opacity:1}
/* The caption hands over from the plate label: while the tile is still a placeholder the
   spec lives inside it, so the two never show at once and nothing reads as duplicated. */
.gal-cap{display:block;margin-top:16px;font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase;color:var(--mute);transition:color var(--dur-1) var(--ease),opacity var(--dur-1) var(--ease)}
.gal .ph:not(.ready)+.gal-cap{opacity:0}
.gal-cap::after{content:"";display:inline-block;vertical-align:middle;width:0;border-top:1px solid currentColor;transition:width var(--dur-1) var(--ease),margin-left var(--dur-1) var(--ease)}
@media (hover:hover){
  .gal-i:hover .ready img{transform:scale(1.04)}
  .gal-i:hover .gal-cap{color:var(--ink)}
  .gal-i:hover .gal-cap::after{width:24px;margin-left:12px}
}
@media (min-width:620px){
  .gal{grid-template-columns:repeat(6,1fr);row-gap:clamp(32px,5vw,56px)}
  .gal-i:nth-child(1){grid-column:1/span 4}
  .gal-i:nth-child(2){grid-column:5/span 2;align-self:end}
  .gal-i:nth-child(3){grid-column:1/span 3;margin-top:clamp(24px,4vw,48px)}
  .gal-i:nth-child(4){grid-column:4/span 3;margin-top:clamp(64px,10vw,104px)}
}
@media (min-width:1001px){
  .gal{grid-template-columns:repeat(12,1fr)}
  .gal-i:nth-child(1){grid-column:1/span 7;grid-row:1/span 2;margin-top:0}
  .gal-i:nth-child(2){grid-column:9/span 4;grid-row:1;align-self:start;margin-top:0}
  .gal-i:nth-child(2) .ph{aspect-ratio:1}
  .gal-i:nth-child(3){grid-column:9/span 4;grid-row:2;margin-top:clamp(32px,3vw,48px)}
  .gal-i:nth-child(3) .ph{aspect-ratio:3/4}
  .gal-i:nth-child(4){grid-column:3/span 5;grid-row:3;margin-top:clamp(64px,7vw,112px)}
}

.st-steps{padding-block:var(--sec-pad-s);border-top:1px solid var(--line)}
/* Two phases of three, not six equal columns. One rail runs vertically down the left at
   EVERY width, so it stays continuous across both phases and never disappears on narrow —
   the old horizontal rail was display:none below 1000px while the JS kept computing --fill.
   Brass on the fill: brass = progressing. See DESIGN-CONTRACT.md § brass. */
.steps{--rail:clamp(24px,3vw,48px);position:relative;margin:0;padding-left:var(--rail);counter-reset:s}
.steps::before,.steps::after{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;background:var(--line)}
.steps::after{background:var(--brass);transform-origin:top;transform:scaleY(var(--fill,0));transition:transform .2s linear}
.phase+.phase{margin-top:clamp(56px,7vw,96px)}
.phase>h3{position:relative;font-size:var(--s1);font-weight:500;letter-spacing:var(--track-3);text-transform:uppercase;color:var(--mute)}
.phase>h3::before{content:"";position:absolute;top:.25em;left:calc(var(--rail)*-1 - 5px);width:9px;height:9px;border-radius:50%;background:var(--bg);border:1px solid var(--ink)}
/* Unequal columns, per phase: the widest holds the stage that carries the most weight —
   Prototype in phase one, Inspect (which now carries the six checks) in phase two. */
.phase-l{list-style:none;margin:32px 0 0;padding:0;display:grid;gap:clamp(24px,3vw,48px);grid-template-columns:1fr 1fr var(--phi)}
.phase+.phase .phase-l{grid-template-columns:1fr var(--phi) 1fr}
.phase-l>li{counter-increment:s;display:flex;flex-direction:column}
.phase-l>li::before{content:counter(s,decimal-leading-zero);display:block;font-size:var(--s1);letter-spacing:var(--track-2);color:var(--mute);margin-bottom:16px}
.phase-l>li>b{display:block;font-weight:400;font-size:var(--s3);line-height:1.15}
.phase-l>li.wide>b{font-size:var(--s4)}
.phase-l>li>p{margin-top:8px;color:var(--mute);line-height:1.5}
/* margin-top:auto lands every phase's outcomes on one line, so they read as a band rather
   than drifting with however each sentence happened to wrap. */
.steps .yield{margin-top:auto;padding-top:24px;font-size:var(--s1);letter-spacing:var(--track-1);color:var(--ink)}
.steps .yield::before{content:"";display:block;width:24px;border-top:1px solid var(--line);margin-bottom:12px}
/* The six checks, folded out of the deleted .qg tile grid into the stage that owns them. */
.checks{list-style:none;margin:16px 0 0;padding:0;color:var(--mute);line-height:1.6}
.checks li{display:inline}
.checks li+li::before{content:" · "}
@media (max-width:900px){
  .phase-l,.phase+.phase .phase-l{grid-template-columns:1fr;gap:0}
  .phase-l>li{padding-block:32px}
  .phase-l>li+li{border-top:1px solid var(--line)}
  .phase-l>li:first-child{padding-top:0}
  .phase-l>li:last-child{padding-bottom:0}
  .phase-l>li.wide>b{font-size:var(--s3)}
}

.st-cal{padding-block:var(--sec-pad-s);border-top:1px solid var(--line)}
.st-cal h2{font-size:var(--s6);margin-bottom:48px}
/* The table has a 680px floor, so it scrolls on phones. It had no affordance at all: no
   fade, no hint, and no way to scroll it from a keyboard. .cal-wrap is now a labelled,
   focusable scroll region; .cal-scroll is the non-scrolling parent that carries the edge. */
.cal-scroll{position:relative}
.cal-wrap{overflow-x:auto}
.cal-scroll::after{content:"";position:absolute;top:0;right:0;bottom:0;width:64px;pointer-events:none;background:linear-gradient(90deg,rgba(var(--bg-rgb),0),rgba(var(--bg-rgb),1))}
.cal-hint{margin-top:16px;font-size:var(--s1);font-weight:500;letter-spacing:var(--track-2);text-transform:uppercase;color:var(--mute)}
.cal{width:100%;border-collapse:collapse;min-width:680px}
.cal th,.cal td{text-align:left;padding:24px 24px 24px 0;border-bottom:1px solid var(--line);font-weight:400;vertical-align:top}
.cal thead th{font-size:var(--s1);font-weight:500;letter-spacing:var(--track-2);text-transform:uppercase;color:var(--mute);padding-top:0}
.cal tbody th{font-size:var(--s3);font-weight:400;width:24%}
.cal td{font-size:var(--s2)}
/* 780px is where the padded content column clears the table's 680px floor, so the affordance
   is shown exactly when the table can actually scroll. */
@media (min-width:780px){.cal-scroll::after,.cal-hint{display:none}}

.st-cta{padding-block:var(--sec-pad);border-top:1px solid var(--line);display:flex;flex-wrap:wrap;justify-content:space-between;align-items:end;gap:48px}
.st-cta h2{font-size:var(--display)}
.st-cta-acts{display:flex;flex-wrap:wrap;align-items:center;gap:32px}

/* ================= CONTACT ================= */
.ct2{display:grid;grid-template-columns:1fr var(--phi);min-height:100svh}
.ct-vis{position:sticky;top:0;height:100svh;overflow:hidden;background:radial-gradient(ellipse 70% 55% at 50% 42%,#222224 0%,var(--bg) 75%)}
/* Two stacked render layers. app.js raises the new frame on .ct-ghost — which sits above
   the resting layer in DOM order — and only copies it down once it has painted, so the panel
   never empties between configurations. The ghost never scales: its job is a straight dissolve. */
.ct-vis .ct-layer{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 30%;opacity:0;transform:scale(1.04);transition:opacity var(--dur-3) var(--ease),transform var(--dur-5) var(--ease)}
.ct-vis .ct-layer.in{opacity:1;transform:none}
.ct-vis .ct-ghost{transform:none;transition:opacity var(--dur-3) var(--ease)}
.ct-vis::after{content:"";position:absolute;inset:0;background:linear-gradient(0deg,rgba(var(--scrim-rgb),.96) 0%,rgba(var(--scrim-rgb),.55) 38.2%,rgba(var(--scrim-rgb),0) 61.8%)}
.ct-vis-copy{position:absolute;z-index:2;left:var(--m);right:48px;bottom:56px}
.ct-lines{display:grid;gap:16px}
.ct-lines a{display:grid;gap:4px;font-size:var(--s3);font-weight:300}
.ct-lines .label{color:var(--mute)}
/* WhatsApp brand green, deliberately not --ok: a channel mark, not a status colour. */
.wa-btn{display:inline-flex;align-items:center;gap:12px;margin-top:32px;height:48px;padding-inline:20px;border:1px solid rgba(158,193,166,.35);color:#CFE3D5;font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase;transition:border-color var(--dur-1) var(--ease),background var(--dur-1) var(--ease)}
.wa-btn:hover{border-color:#9EC1A6;background:rgba(158,193,166,.08)}
.wa-btn svg{width:18px;height:18px}
/* One gutter system: --m on all four sides (the old clamp(48px,6vw,96px) was a second,
   non-matching gutter), --sec-pad/--sec-pad-s for the vertical rhythm. */
.ct-main{padding:var(--sec-pad) var(--m) var(--sec-pad-s);max-width:960px;min-width:0}
.ct-main h1{font-size:var(--display)}
.ct-sub{margin-top:24px;max-width:40ch}
/* The slot itself carries no spacing, so an empty slot costs nothing and swapping the build
   card for the prompt no longer collapses the form upward by a whole block. */
.buildslot>*{margin-top:48px}
.ct-main form{display:grid;gap:40px;margin-top:56px}
.row{display:grid;grid-template-columns:1fr 1fr;gap:40px}
.row>.fld{min-width:0}
.ct-main input,.ct-main textarea{min-width:0}
.fld .chs{margin-top:8px}
/* The global focus rule out-specifies a plain `.fld.err input`, so the invalid colour is
   restated at the same weight — otherwise the first bad field shows --focus, not --err. */
.fld.err input,.fld.err input:focus:focus-visible{border-color:var(--err)}
.fmsg{color:var(--err);font-size:var(--s1);letter-spacing:var(--track-1);line-height:1.6}
.fmsg:empty{display:none}
.buildcard{display:grid;grid-template-columns:96px 1fr auto;gap:24px;align-items:center;padding:16px;border:1px solid var(--line);background:rgba(255,255,255,.02)}
.buildcard img{width:96px;height:96px;object-fit:cover;background:#151515}
.buildcard b{font-weight:400;display:block;letter-spacing:var(--track-1)}
.buildcard small{display:block;color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1);margin-top:4px;line-height:1.6}
.buildcard button{border:0;background:none;color:var(--mute);cursor:pointer;font-size:var(--s1);letter-spacing:var(--track-2);text-transform:uppercase;align-self:start}
.buildprompt{display:flex;justify-content:space-between;align-items:center;gap:24px;min-height:128px;padding:24px;border:1px dashed var(--line)}
.buildprompt p{color:var(--mute)}
.sendrow{display:flex;flex-wrap:wrap;align-items:center;gap:24px}
.note{color:var(--mute);font-size:var(--s1);letter-spacing:var(--track-1)}
.note.bad{color:var(--err)}.note.good{color:var(--ok)}
.hp{position:absolute;left:-9999px}
/* Two plain lines, not a third numbered card row. The six-step process lives on /story/. */
.next{list-style:none;margin:64px 0 0;padding:24px 0 0;display:grid;gap:12px;max-width:58ch;border-top:1px solid var(--line);color:var(--mute)}
.next li{line-height:1.7}
.next a{border-bottom:1px solid var(--line-2);transition:color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.next a:hover{color:var(--ink);border-color:var(--ink)}
.ct-faq{margin-top:96px;border-top:1px solid var(--line)}
.ct-faq details{border-bottom:1px solid var(--line)}
.ct-faq summary{list-style:none;cursor:pointer;display:flex;justify-content:space-between;padding:24px 0;font-size:var(--s3);font-weight:300}
.ct-faq summary::-webkit-details-marker{display:none}
/* The "+" marker, its open state and its hover now live in the shared accordion block in the
   BUILD region — this treatment serves .acc>button and .ct-faq summary alike. */
.ct-faq p{padding-bottom:24px;color:var(--mute)}
/* Contract §6's .faq-in wrapper cannot animate a <details>: the UA keeps the body at
   display:none while closed, so a 0fr->1fr transition has no start frame to run from.
   ::details-content is the only mechanism that folds both ways, and it needs no wrapper. */
@supports selector(::details-content){
  .ct-faq details::details-content{
    block-size:0;overflow:hidden;
    transition:block-size var(--dur-2) var(--ease),content-visibility var(--dur-2) allow-discrete;
    interpolate-size:allow-keywords}
  .ct-faq details[open]::details-content{block-size:auto}
}
@media (max-width:900px){
  .ct2{grid-template-columns:1fr}
  /* the h1 now lives in .ct-main, so the visual panel stops short of a full viewport */
  .ct-vis{position:relative;height:61.8svh}
  .ct-main{padding:var(--sec-pad-s) var(--m)}
  .row{grid-template-columns:1fr}
  .buildcard{grid-template-columns:72px 1fr}.buildcard img{width:72px;height:72px}.buildcard button{grid-column:1/-1;justify-self:start}
}

/* Zeroing duration alone is not enough. A delayed transition or animation still sits out its
   full delay and then snaps, which reads as a hang, not as "motion off" — and the file has
   real delays (.r1/.r2/.r3, the sheet's visibility step, the autofill parking delay). The
   delays go to 0s with the durations. scroll-behavior joins them for the same reason: an
   instant jump is the reduced-motion answer to smooth scrolling. */
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important;scroll-behavior:auto!important}}
