/* ============================================================
   ASIS — main.css
   paper-cream editorial page, one ember accent, stepped motion.
   ============================================================ */

@font-face{font-family:"Calistoga";src:url("../fonts/calistoga-400.woff2") format("woff2");font-weight:400;font-display:swap;}
@font-face{font-family:"Newsreader";src:url("../fonts/newsreader-400.woff2") format("woff2");font-weight:400;font-display:swap;}
@font-face{font-family:"Newsreader";src:url("../fonts/newsreader-500.woff2") format("woff2");font-weight:500;font-display:swap;}
@font-face{font-family:"Newsreader";src:url("../fonts/newsreader-400i.woff2") format("woff2");font-weight:400;font-style:italic;font-display:swap;}
@font-face{font-family:"Plex Mono";src:url("../fonts/plexmono-400.woff2") format("woff2");font-weight:400;font-display:swap;}
@font-face{font-family:"Plex Mono";src:url("../fonts/plexmono-500.woff2") format("woff2");font-weight:500;font-display:swap;}

:root{
  --paper:    #F7F1E6;  /* page background — warm cream */
  --paper-2:  #FFFDF7;  /* elevated surfaces (cards, header band) */
  --ink:      #221C14;  /* warm near-black — primary text */
  --ink-soft: #3A3226;  /* secondary body text */
  --muted:    #766A56;  /* meta text (contrast on paper ≈ 4.7:1 — verified) */
  --ember:    #E0492A;  /* THE accent — display tier: large type (≥18px), marks, field (3.6:1 on paper) */
  --ember-deep:#BE3A1F; /* THE accent — text tier: labels, links, button fills (4.9:1 on paper, AA at any size) */
  --rust:     #9E2F1A;  /* deep ember — field band 4 */
  --gold:     #E8A33D;  /* field band — optimism */
  --neon:     #E0FE52;  /* the face's skin (It.93) — craft.wild.as's `data-base` PASTELLED, by direction.
                           The reference ships #D8FF00: zero blue, a fully saturated chartreuse that is
                           right on its cool near-white and reads acid on this warm cream. Lifting the
                           blue channel to 0x52 keeps the hue and drops the acid — same family, paper's
                           own temperature. It is still the one colour here that is NOT of this page,
                           and it costs the silhouette: 1.01:1 on --paper, so the disc's EDGE carries by
                           HUE alone, and there is no rim to help since It.93 removed it. That is the
                           reference's own trade (its neon is 1.10:1 on its near-white) and pastelling
                           does not soften it — it makes it fractionally fainter, 1.025 -> 1.011.
                           What the lift DOES buy is the drawing, which is what actually has to read:
                           --ink on it goes 14.65 -> 14.84:1 and --ember 3.53 -> 3.58:1. The scatter's
                           two visible rungs are mixes of THIS token, so they followed it without being
                           retuned. It.92's --rose is gone with the kiss face; nothing may paint this
                           but the face. */
  --taupe:    #C7BBA6;  /* field band 1 (faintest) */
  --sage:     #7FA06E;  /* field band — calm, growth */
  --sky:      #6E93B0;  /* field band — trust, clarity */
  --glow:     #FFC98F;  /* highlight band */
  --line:     rgba(34,28,20,.14);
  --line-2:   rgba(34,28,20,.06);
  --grid-dot: rgba(34,28,20,.045); /* the faint page dot-grid */

  --cell: 12px;              /* layout grid unit; every padding snaps to it */
  --gutter: 60px;            /* 5 cells; 24px (2 cells) under 680px */
  --maxw: 1176px;            /* the page measure — grows on wide displays, see below */
  --radius: 26px;            /* dark-section sheet rounding */
  --field-cell: 10px;        /* ember-field pixel size */

  /* pixel-stepped corners (signature button/tag shape) */
  --step10: polygon(10px 0, calc(100% - 10px) 0, calc(100% - 10px) 5px, calc(100% - 5px) 5px, calc(100% - 5px) 10px, 100% 10px, 100% calc(100% - 10px), calc(100% - 5px) calc(100% - 10px), calc(100% - 5px) calc(100% - 5px), calc(100% - 10px) calc(100% - 5px), calc(100% - 10px) 100%, 10px 100%, 10px calc(100% - 5px), 5px calc(100% - 5px), 5px calc(100% - 10px), 0 calc(100% - 10px), 0 10px, 5px 10px, 5px 5px, 10px 5px, 10px 0);
  --card-line: rgba(34,28,20,.22);
  --card-radius: 18px;               /* the transcript card's curve — inherits the 18px scale the notch used */   /* firmer than --line: a traced silhouette has to read as a shape */
  --step6: polygon(6px 0, calc(100% - 6px) 0, calc(100% - 6px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 6px, 100% 6px, 100% calc(100% - 6px), calc(100% - 3px) calc(100% - 6px), calc(100% - 3px) calc(100% - 3px), calc(100% - 6px) calc(100% - 3px), calc(100% - 6px) 100%, 6px 100%, 6px calc(100% - 3px), 3px calc(100% - 3px), 3px calc(100% - 6px), 0 calc(100% - 6px), 0 6px, 3px 6px, 3px 3px, 6px 3px, 6px 0);
  /* the same corner one size up, for the closing slab: a 10px notch on a 68px button reads
     as damage rather than as the signature. THE SAME 21 POINTS as --step10 and --step6, in
     the same order, which is what lets any of the three interpolate into any other — the
     closing button's hover bites from this one down to --step10 and back. */
  --step14: polygon(14px 0, calc(100% - 14px) 0, calc(100% - 14px) 7px, calc(100% - 7px) 7px, calc(100% - 7px) 14px, 100% 14px, 100% calc(100% - 14px), calc(100% - 7px) calc(100% - 14px), calc(100% - 7px) calc(100% - 7px), calc(100% - 14px) calc(100% - 7px), calc(100% - 14px) 100%, 14px 100%, 14px calc(100% - 7px), 7px calc(100% - 7px), 7px calc(100% - 14px), 0 calc(100% - 14px), 0 14px, 7px 14px, 7px 7px, 14px 7px, 14px 0);

  /* IT.107 — ONE CLOCK FOR EVERY CONTROL ON THE PAGE. it is not a new number: it is the
     .2s `.btn` has answered a hand with since It.1, and the masthead's GET ASIS is the
     instance the direction named. every button's hover now reads from this token, so the
     row of controls on this page cannot drift apart again one rule at a time (they had
     drifted to .15s, .18s and .25s). the EASING is the same rule's default `ease` and is
     therefore written nowhere — a hover that names its own curve is a hover that has left
     the family. SELECTION choreography is NOT on this clock and must not be put on it:
     the pricing plate's .42s flight and the twelve's .58s are journeys between two boxes,
     not a control answering a pointer. */
  --hov: .2s;

  --serif:"Newsreader",Georgia,"Times New Roman",serif;
  --display:"Calistoga","Newsreader",Georgia,serif;
  --mono:"Plex Mono","SFMono-Regular",ui-monospace,Menlo,monospace;
  /* language names fall through to system fonts for non-Latin scripts */
  --serif-lang:"Newsreader",Georgia,"Times New Roman","PingFang SC","Hiragino Sans","Microsoft YaHei",serif;
  /* the wheel's own face. Calistoga is the site's display serif and it is the only thing
     here that is BOLD BY DESIGN rather than by weight — it ships 400 and nothing else, so
     asking for a 600 anywhere on this page is asking for a synthesised smear (It.72). the
     non-Latin names fall through it to the system faces exactly as they already fall
     through --serif-lang; what keeps them level with Calistoga's mass is --lstroke, which
     is raised for those scripts rather than a font-weight being faked. */
  --display-lang:"Calistoga","Newsreader",Georgia,"PingFang SC","Hiragino Sans","Microsoft YaHei",serif;
}
@media(max-width:680px){ :root{--gutter:24px;} }
/* ===== wide displays: one page measure that grows with the viewport, instead of
   stranding every section in a 1176px column with half the screen unused. every
   prose block already carries its own ch-based cap (.shead h2 22ch, .sintro 52ch,
   .why-body 56ch, .dt 54ch…), so widening the container gives the width to the
   things that want it — walls, grids, media, canvases — and never to line length.
   the opening sequence shares this measure, so its stage lines up with the page. ===== */
@media(min-width:1280px){
  :root{--maxw:clamp(1176px, 84vw, 1760px); --gutter:clamp(60px, 3.2vw, 76px);}
}
@media(min-width:1400px){ :root{--slide-w:430px;} }

*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
html{scroll-behavior:smooth; overscroll-behavior:none;}
body{margin:0; overscroll-behavior:none; background:var(--paper); color:var(--ink-soft); overflow-x:hidden;
  font-family:var(--serif); font-weight:400; font-size:clamp(16px,1.15vw,18px); line-height:1.66;
  -webkit-font-smoothing:antialiased;}
a{color:inherit;}
h1,h2,h3{color:var(--ink); font-family:var(--display); font-weight:400;}
::selection{background:var(--glow); color:var(--ink);}
:focus-visible{outline:2px solid var(--ember); outline-offset:3px; border-radius:2px;}

/* skip link */
.skip{position:fixed; left:12px; top:-60px; z-index:300; padding:10px 18px; background:var(--ink); color:var(--paper);
  font-family:var(--mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase; text-decoration:none;
  transition:top .2s ease;}
.skip:focus{top:12px;}

/* visually hidden, read aloud — a section whose visible title is display art
   still names itself to assistive tech */
.vh{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap; border:0;}

/* mono micro labels — two distinct jobs, two distinct colours:
   .kick is ASIS naming a thing (ember-deep, 4.9:1); .micro is quiet meta (muted, 4.7:1) */
.kick{font-family:var(--mono); font-weight:500; font-size:10.5px; line-height:1.5; letter-spacing:.18em;
  text-transform:uppercase; color:var(--ember-deep); margin:0;}
.micro{font-family:var(--mono); font-weight:400; font-size:10.5px; line-height:1.6; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); margin:0;}

/* links in body copy: ember dashed underline that grows on hover */
main p a, .legal a, .tcard a{color:var(--ink); text-decoration:none;
  background:repeating-linear-gradient(90deg,var(--ember) 0 4px,transparent 4px 7px) left bottom/0% 3px no-repeat;
  transition:background-size .45s cubic-bezier(.16,1,.3,1); padding-bottom:2px;}
main p a:hover, .legal a:hover, .tcard a:hover{background-size:100% 3px;}

/* ===== ember field canvas ===== */
#field{position:fixed; inset:0; width:100%; height:100%; display:block; z-index:0; pointer-events:none;}
/* It.91: the cursor shapes (ring/arrow/heart/twelve) move UNDER the copy — z-index 1, above
   the field at 0 and below main at 2 — so the flock passes BEHIND the words instead of being
   suppressed wherever they are. At 60 it sat above the whole page, which is the only reason
   field.js carried five separate glyph-avoidance rules; all five are gone with this line.
   The cursor is now the same kind of thing the field is: a ground the copy sits on. */
#cursorfx{position:fixed; inset:0; width:100%; height:100%; display:block; z-index:1; pointer-events:none;}
main, .masthead, .sitefoot, #stacker{position:relative; z-index:2;}
.masthead{z-index:50;}

/* ===== masthead — in flow, no panel: a hairline separates it, and the FIELD itself
   stays out of the band above it (field.js), so the header sits on calm, stable paper ===== */
.masthead{background:transparent; border-bottom:1px solid var(--line);}
.mh-row{max-width:var(--maxw); margin:0 auto; min-height:calc(var(--cell)*4 + 12px); padding:6px var(--gutter);
  display:flex; align-items:center; gap:calc(var(--cell)*2.5);}
.wordmark{font-family:var(--display); font-size:26px; line-height:1; color:var(--ink); text-decoration:none; letter-spacing:0;}
.wordmark .dot{color:var(--ember);}
/* user direction, 2026-08-03: the HEADER wordmark reads ember whole — 26px display type
   sits on the ≥18px tier where --ember's 3.62:1 is legal, and the dot it already owned
   simply joins its word. Scoped to the masthead: the footer keeps the ink wordmark. */
.masthead .wordmark{color:var(--ember);}
.mh-nav{display:flex; gap:calc(var(--cell)*2); margin-left:auto;}
.mh-nav a{font-family:var(--mono); font-weight:500; font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-soft); text-decoration:none; padding:6px 2px; transition:color .15s ease;}
.mh-nav a:hover{color:var(--ember-deep);}
.mh-cta{flex:none;}
.mh-menu{display:none; margin-left:auto;}
.mh-menu summary{list-style:none; cursor:pointer; font-family:var(--mono); font-weight:500; font-size:10.5px;
  letter-spacing:.14em; text-transform:uppercase; color:var(--ink); padding:8px 2px;}
.mh-menu summary::-webkit-details-marker{display:none;}
.mh-menu-panel{position:absolute; top:100%; left:0; right:0; z-index:20; display:none; flex-direction:column; gap:2px;
  background:var(--paper); border-bottom:1px solid var(--line); padding:12px var(--gutter) 18px;}   /* transient dropdown: honest opaque paper, no glass */
.mh-menu[open] .mh-menu-panel{display:flex;}
.mh-menu-panel a{font-family:var(--mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-soft); text-decoration:none; padding:10px 0; border-top:1px solid var(--line-2);}
.mh-menu-panel a:first-child{border-top:0;}
@media(max-width:680px){ .mh-nav{display:none;} .mh-menu{display:block;} }

/* ===== buttons (pixel-stepped corners) ===== */
.btn{position:relative; display:inline-flex; align-items:center; justify-content:center; text-decoration:none;
  font-family:var(--mono); font-weight:500; font-size:12.5px; line-height:1; letter-spacing:.12em; text-transform:uppercase;
  padding:15px 30px; background:var(--ember-deep); color:var(--paper); border:0; cursor:pointer;
  clip-path:var(--step10); transition:background var(--hov);}
/* GATED ON A REAL POINTER (It.107). a touch screen latches :hover on the last thing
   tapped and never clears it, so on a phone `See how it works` stayed in its hover fill
   for the rest of the visit — the hover reading as a SELECTED state on a button that
   selects nothing. this is the same `@media(hover:hover)` the chips, the arrows and the
   carousel already stand behind; the button now joins its own family. */
@media(hover:hover) and (pointer:fine){
  .btn:hover{background:var(--ink);}   /* the pair trades places on hover — colour swap only, no translate */
}
/* IT.107 — THIS IS THE PAGE'S ONE BUTTON HOVER, AND THE MASTHEAD'S `GET ASIS` IS ITS
   REFERENCE INSTANCE. one property changes (the fill), over --hov = .2s, on the default
   ease, with no transform, no shape change and no border; the return is the SAME
   declaration read backwards, so leaving costs exactly what arriving cost. every other
   control on this page now answers a hand on that clock — see --hov below. */
.btn-s{padding:11px 20px; font-size:11px;}
.btn-o{background:var(--ink); color:var(--paper);}          /* secondary: the ink counterweight to the ember primary */
@media(hover:hover) and (pointer:fine){
  .btn-o:hover{background:var(--ember-deep); color:var(--paper);}
}
/* THE CLOSING SLAB IS THE BIGGEST INSTANCE OF THE SITE'S ONE BUTTON, NOT A SECOND DESIGN.
   only size, tracking, air and corner change here, because the whole argument of this page
   is that it is calm — so the last thing on it has to earn attention by being LARGER, not
   louder. it comes out ~2.9x the hero button's area, at 17px against 12.5.
   THE TRACKING OPENS WITH THE SIZE, .12em -> .18em, and that is not decoration: at 12.5px
   .12em is only just enough to stop mono capitals setting solid, but at 17px the letterform
   has room of its own and the wider track is what makes two words read as a SIGN rather than
   as a word. IT.107 RETIRES THE CORNER BITE. It.104 gave this one button a --step14 ->
   --step10 hover and called it "the only hover on this page that changes SHAPE" — which,
   read back under a direction to make every button answer a hand the way the masthead's
   GET ASIS does, is the sentence that condemns it: the reference changes ONE property and
   it is the fill. --step14 stays as the slab's RESTING corner, because the size argument
   above is untouched; what goes is the interpolation to --step10 on hover, so this button
   now differs from the masthead's only in the ways the direction protects (size, tracking,
   air, corner). the clip-path leaves the transition list with it. */
.btn-xl{font-size:17px; letter-spacing:.18em; padding:24px 54px; clip-path:var(--step14);}
@media(max-width:560px){ .btn-xl{font-size:14px; letter-spacing:.15em; padding:19px 34px;} }
.btn .lbl{position:relative; z-index:2;}
.btn .pxfx{position:absolute; inset:0; z-index:1; display:grid; pointer-events:none;}

/* ===== pixel controls widget — no panel: the chips float straight on the field ===== */
.pxctl{position:fixed; left:16px; bottom:16px; z-index:200; display:flex; align-items:center; gap:6px;
  font-family:var(--mono); font-size:10px; letter-spacing:.1em; text-transform:uppercase;
  transition:opacity .35s ease, transform .35s ease;}
.pxctl .lbl{display:flex; align-items:center; height:22px; padding:0 7px; margin:0 1px 0 3px;
  background:var(--paper-2); border:1px solid var(--line); border-radius:6px; color:var(--muted);}   /* label chips: legible over whatever scrolls beneath */
.pxctl .lbl:first-child{margin-left:0;}
.pxctl button{font:inherit; width:22px; height:22px; display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line); background:var(--paper-2); color:var(--ink); border-radius:6px; cursor:pointer; padding:0;
  transition:background var(--hov), border-color var(--hov);}
.pxctl button:hover{border-color:var(--ink);}
.pxctl button.on{background:var(--ink); color:var(--paper); border-color:var(--ink);}
.pxctl.hide{opacity:0; transform:translateY(8px); pointer-events:none;}
@media(max-width:720px){ .pxctl{display:none;} }

/* ===== back-to-top chip ===== */
.toplink{position:fixed; top:calc(var(--cell)*6); right:16px; z-index:60; display:inline-flex; align-items:center; gap:8px;
  height:calc(var(--cell)*3); padding:0 18px; font-family:var(--mono); font-size:11px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--ink); text-decoration:none; background:transparent;
  border:1px solid var(--line);
  transform:translateY(calc(-100% - var(--cell)*7)); pointer-events:none;
  transition:transform .4s steps(4), background var(--hov), color var(--hov), border-color var(--hov);}
.toplink.show{transform:none; pointer-events:auto;}
.toplink:hover{background:var(--ink); color:var(--paper); border-color:var(--ink);}
@media(max-width:900px){ .toplink{display:none;} }

/* ===== layout base ===== */
section[id]{scroll-margin-top:calc(var(--cell)*6);}
.wrap{max-width:var(--maxw); margin:0 auto; padding:0 var(--gutter);}
.sec{padding:calc(var(--cell)*7) 0;}
.shead{margin-bottom:calc(var(--cell)*3);}
.shead .kick{margin-bottom:14px;}
.shead h2{font-size:clamp(29px,3.6vw,52px); line-height:1.06; letter-spacing:0; margin:0; max-width:22ch;
  text-wrap:balance;}
.shead .sintro{margin:calc(var(--cell)*1.5) 0 0; max-width:52ch; color:var(--ink-soft); font-size:clamp(16px,1.15vw,18px); line-height:1.66;}
.shead.c{text-align:center;}
.shead.c h2, .shead.c .sintro{margin-left:auto; margin-right:auto;}
/* kicks hug their text so data-safe="kids" rects match the glyphs, not the full row */
.shead .kick, .hero-copy .kick{width:fit-content;}
.shead.c .kick{margin-left:auto; margin-right:auto;}
.wrap.c{text-align:center;}

/* ===== wide displays, section by section. the shared move: a section head that carries an
   intro splits into two columns — kicker and heading hold the left, the intro sits at their
   baseline on the right — so a 1600px section reads as one composed block instead of a short
   heading marooned beside a field of emptiness. heads without an intro stay single-column. ===== */
@media(min-width:1280px){
  .sec{padding:calc(var(--cell)*9) 0;}
  .shead{margin-bottom:calc(var(--cell)*4);}
  .shead:not(.c):has(.sintro){display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.82fr);
    column-gap:clamp(48px,5vw,96px); align-items:end;}
  .shead:not(.c):has(.sintro) > .kick{grid-column:1; grid-row:1;}
  .shead:not(.c):has(.sintro) > h2{grid-column:1; grid-row:2;}
  .shead:not(.c):has(.sintro) > .sintro{grid-column:2; grid-row:2; margin:0; max-width:46ch;}

  /* why: the argument sits beside its heading rather than under it (no intro to
     split). the whole of that treatment — the grid AND the row each block sits in —
     now lives in the `===== why =====` block further down, stated after its own base
     rules, because the section became a framed box whose frame is set there too and
     splitting one section's layout across two places is how a cascade bug is born. */

  /* demo: the section is already two columns of its own, so the shared head-splitting
     rule above must not split it a second time — the kicker, the heading and the line
     under them belong in one narrow column beside the card, not in two beside each
     other. the transcript keeps a reading measure by being a column, not by a cap. */
  #demo .shead:not(.c):has(.sintro){display:block;}
  #demo .shead:not(.c):has(.sintro) > .sintro{margin:calc(var(--cell)*1.5) 0 0; max-width:34ch;}
  /* past the split the words stop needing width and the card starts: the heading column
     is capped by its own 22ch/34ch measures long before the card is */
  .demo-grid{grid-template-columns:minmax(0,.88fr) minmax(0,1fr); column-gap:clamp(56px,4.6vw,104px);}
}

/* ===== reveals: stepped, choppy — and bidirectional since It.113 =====
   every hidden state stands under html.rv, the engine's own mark: with no JS,
   a stale engine, or reduced motion (the mark is withheld) the static page is
   whole — nothing offset, clipped or dimmed.
   the system re-arms: an element hides again only once it is FULLY off screen
   (a reset in view is exactly the flashing the direction bans), and the edge
   it left by is remembered as `.up`, so it returns along the road the scroll
   is travelling — a down-scroll rises in, an up-scroll settles down in.
   one clock rules the pace: --rvk, written once at the moment a reveal fires,
   from the live scroll velocity (1 for a slow read, down to .35 for a flick);
   every family clock below multiplies it, so the motion answers the hand
   without a per-frame style write — triggered, never scrubbed.
   the families are each section's own answer in one stepped grammar — lifts,
   wipes, clips, cascades; steps() always, blur and curves never. */
.rv .reveal{--rvy:18px; transform:translateY(var(--rvy)); transition:transform calc(.8s*var(--rvk,1)) steps(6); transition-delay:calc(var(--rvi,0)*.12s*var(--rvk,1));}
.rv .reveal.up{--rvy:-14px;}
.reveal.in{transform:none;}
.rv .split{opacity:0;}                              /* raw text hidden until JS splits it into lines */
.split.ready{opacity:1;}
.split .ln{display:block; overflow:hidden; padding-bottom:.08em; margin-bottom:-.08em;}
.rv .split .ln > span{display:block; transform:translateY(115%); transition:transform calc(.9s*var(--rvk,1)) steps(8); transition-delay:calc(var(--rvi,0)*.09s*var(--rvk,1));}
.rv .split.up .ln > span{transform:translateY(-115%);}
.split.in .ln > span{transform:translateY(0);}

/* -- #how: the cascade. the three numbered beats arrive in order, left to
      right, each a stepped lift; the container stands still so the march IS
      the motion — the numbers already say 01 02 03, the timing now agrees. */
.rv #how .beats{--rvy:0px;}
.rv #how .beats .beat{transform:translateY(22px); transition:transform calc(.55s*var(--rvk,1)) steps(5); transition-delay:calc(var(--rci,0)*.15s*var(--rvk,1));}
#how .beats.in .beat{transform:none;}
.rv #how .beats.up .beat{transform:translateY(-16px);}
#how .beats .beat:nth-child(1){--rci:0;} #how .beats .beat:nth-child(2){--rci:1;} #how .beats .beat:nth-child(3){--rci:2;}

/* -- #demo: the message arrives. the transcript card unclips top-down in
      seven steps, header first — reading order, and writing has one
      direction, so the unroll never flips for the return (the pac-man rule).
      the side copy steps in from the margin it hangs on.
      THE CLIP LIVES ON THE STAGE'S CHILDREN, NEVER THE STAGE: the observer
      intersects through the target's own clip-path, so an element inset to
      nothing has no visible box and can never see itself arrive — the stage
      stays whole and watches, the card and its wake do the unrolling. */
.rv #demo .demo-stage{--rvy:0px;}
.rv #demo .demo-stage > *{clip-path:inset(0 0 100% 0); transition:clip-path calc(.7s*var(--rvk,1)) steps(7);}
#demo .demo-stage.in > *{clip-path:inset(0 0 0 0);}
.rv #demo .demo-side .reveal{--rvy:0px; transform:translateX(-26px); transition:transform calc(.6s*var(--rvk,1)) steps(6); transition-delay:calc(var(--rvi,0)*.12s*var(--rvk,1));}
#demo .demo-side .reveal.in{transform:none;}
.rv #demo .demo-side .reveal.up{transform:translate(-26px,-10px);}

/* -- #day: the track slides. the head steps in from the left, the whole
      momentum track from the right — the first gesture the section makes is
      the one the Drag tell asks for. the slides themselves carry nothing
      (It.108: five entrances inside a sliding thing fight each other). */
.rv #day{overflow-x:clip;}                          /* the 64px approach must not widen the page: clip (not hidden —
                                                       no new scroll container) truncates the transformed box's
                                                       contribution to scrollWidth, which body overflow-x cannot */
.rv #day .wrap.ch{--rvy:0px; transform:translateX(-26px); transition:transform calc(.6s*var(--rvk,1)) steps(6);}
#day .wrap.ch.in{transform:none;}
.rv #day .wrap.ch.up{transform:translate(-26px,-8px);}
.rv #day .track-wrap{transform:translateX(64px); transition:transform calc(.7s*var(--rvk,1)) steps(6);}
#day .track-wrap.in{transform:none;}
.rv #day .track-wrap.up{transform:translate(64px,-10px);}

/* -- #memory keeps its own hands: the head's line masks arrive with every
      other headline, and the wall's tile entrance belongs to memory.js —
      doubled motion there would be two engines saying one thing. */

/* -- #why: the argument writes itself. the lede unclips left to right in
      eight steps (writing has one direction — the wipe never flips), the two
      paragraphs under it step up in turn, and the scramble keeps its
      geometry: the lede never translates, so nothing it measures moves. */
.rv #why .why-body{--rvy:0px;}
.rv #why .why-body .why-lede{clip-path:inset(0 100% 0 0); transition:clip-path calc(.8s*var(--rvk,1)) steps(8);}
#why .why-body.in .why-lede{clip-path:inset(0 0 0 0);}
.rv #why .why-body p:not(.why-lede){transform:translateY(14px); transition:transform calc(.5s*var(--rvk,1)) steps(4); transition-delay:calc((.22s + var(--rci,0)*.14s)*var(--rvk,1));}
#why .why-body.in p:not(.why-lede){transform:none;}
.rv #why .why-body.up p:not(.why-lede){transform:translateY(-10px);}
#why .why-body p:nth-of-type(2){--rci:0;} #why .why-body p:nth-of-type(3){--rci:1;}

/* -- #languages: the globe settles — a stepped settle from 96.5%, once.
      languages.js lays the circle from measured rects when it goes live, so
      this stage keeps the one-shot contract and never re-arms. */
.rv #languages .lstage{--rvy:0px; transform:translateY(16px) scale(.965); transform-origin:50% 60%; transition:transform calc(.75s*var(--rvk,1)) steps(5);}
#languages .lstage.in{transform:none;}

/* -- #trust: three promises, dealt. the cards land one, two, three on the
      numbers they carry; the container stands still like #how's. */
.rv #trust .tcards{--rvy:0px;}
.rv #trust .tcards .tcard{transform:translateY(26px); transition:transform calc(.6s*var(--rvk,1)) steps(5); transition-delay:calc(var(--rci,0)*.16s*var(--rvk,1));}
#trust .tcards.in .tcard{transform:none;}
.rv #trust .tcards.up .tcard{transform:translateY(-18px);}
#trust .tcards .tcard:nth-child(1){--rci:0;} #trust .tcards .tcard:nth-child(2){--rci:1;} #trust .tcards .tcard:nth-child(3){--rci:2;}

/* -- #pricing: two sizes weigh in, pro then max a stepped beat apart. the
      outline canvas baselines against settled cards, so the row is the other
      one-shot: revealed once, then the geometry is never touched again. */
.rv #pricing .pcards{--rvy:0px;}
.rv #pricing .pcards .pcard{transform:translateY(24px); transition:transform calc(.65s*var(--rvk,1)) steps(5); transition-delay:calc(var(--rci,0)*.18s*var(--rvk,1));}
#pricing .pcards.in .pcard{transform:none;}
#pricing .pcards .pcard:nth-of-type(1){--rci:0;} #pricing .pcards .pcard:nth-of-type(2){--rci:1;}

/* -- #cta: insert coin. the one button pops up a size on its own ground in
      four steps — the arcade's hello. the magnet transforms the child and
      the pop transforms the row, so neither writes over the other. */
.rv #cta .cta-btnrow{--rvy:0px; transform:scale(.92); transform-origin:50% 100%; transition:transform calc(.5s*var(--rvk,1)) steps(4);}
#cta .cta-btnrow.in{transform:none;}
.rv #cta .cta-btnrow.up{transform:scale(.92) translateY(-12px);}

/* -- footer: the colophon stands up last — brand, three columns, then the
      base line, one stepped beat each. the engine marks them (rvt) so the
      markup never changes hands. */
.rv .sitefoot .rvt{transform:translateY(16px); transition:transform calc(.6s*var(--rvk,1)) steps(5); transition-delay:calc(var(--rvi,0)*.11s*var(--rvk,1));}
.sitefoot .rvt.in{transform:none;}
.rv .sitefoot .rvt.up{transform:translateY(-12px);}

/* ===== hero ===== */
/* hero: copy on the left, the living object on the right — the ground is calm now
   (It.37 retired the ambient drift), so the bonsai carries the hero's life */
.hero{position:relative; min-height:calc(100vh - 61px); display:flex; align-items:center;   /* the prototype's full first viewport (It.46);
  61px = the in-flow masthead band as it actually renders (It.48) — at 72px the hero ended 11px above the fold and the bottom-anchored tree sat 11px high */
  padding:calc(var(--cell)*4) 0 calc(var(--cell)*7);}   /* the in-flow masthead supplies the top air */
.hero-grid{display:block; width:100%;}
.hero-copy{max-width:620px;}
@media(min-width:1280px){ .hero-copy{max-width:700px;} }   /* wide: more room for the statement */
/* the bonsai's box. under 901px it stands beneath the copy at the map's own square
   ratio; on desktop it anchors to the hero's bottom-right, wide, and is allowed to
   reach behind the copy — the engine letterboxes (contain-fit, bottom-anchored), the
   text rides above it, and the canvas takes NO pointer events (bonsai.js listens on
   the window), so the overlap can never block a click, a link or a selection. */
.hero-art{position:relative; margin:calc(var(--cell)*2.5) auto 0; width:min(96vw,660px); aspect-ratio:216/144; pointer-events:none;}
.hero-art canvas{position:absolute; inset:0; width:100%; height:100%;}
.hero-copy{position:relative; z-index:1;}
/* the headline joins the halo family: where the canopy passes behind it, the glyph
   edges stay clean — paper outline, no panel, exactly the kick/sub treatment */
.hero h1{text-shadow:1px 0 0 var(--paper), -1px 0 0 var(--paper), 0 1px 0 var(--paper), 0 -1px 0 var(--paper),
  1px 1px 0 var(--paper), -1px -1px 0 var(--paper), 1px -1px 0 var(--paper), -1px 1px 0 var(--paper);}
@media(min-width:901px){
  /* the composition follows the canopy (It.58). the tree is WIDTH-bound — the map is 3:2, so the
     drawn tree is only its box width over 1.5 — and in a narrow, tall window (a half-screen browser
     at ~951x1018) it can no longer reach the masthead line: the copy stayed centred in an unchanged
     hero and 289px of dead paper opened under the nav. two continuous terms answer it, in order.
     (1) the art GROWS past its 90.9vw resting width until it is as tall as the copy's own box,
     stopping at the widest box the viewport can show uncropped — 100vw - 12px, the 12 keeping the
     left branch tip clear of a classic scrollbar's reserved gutter. (2) whatever height the tree
     still falls short by is spent as extra bottom padding: that shrinks the centring box, so the
     copy RISES toward the nav by exactly the shortfall (padding moves a centred box by half of
     what it takes, hence the 2x). both terms are 0 wherever the tree already covers --box-h, so
     EVERY landscape size — 1536x1024, 1920x1080, 1440x900, 1280x900, 1536x780 — renders to the
     pixel as before; the crossover (1280x969) is smooth. no breakpoint, no second arrangement. */
  .hero{--box-h:calc(100vh - 61px - var(--cell)*11);   /* the hero's content box: the fold less its own 4+7 cells of padding */
    --art-w:min(1396px, max(90.9vw, calc(1.5*var(--box-h))), calc(100vw - 12px));
    --art-h:calc(var(--art-w)/1.5);                    /* the height the tree actually draws to */
    --sky:clamp(0px, calc(2*(var(--box-h) - var(--art-h))), calc(var(--box-h) - 460px));   /* the upper bound is a copy-height's worth, so an extreme portrait can never push the hero past the fold and the pot below it */
    padding-bottom:calc(var(--cell)*7 + var(--sky));}
  .hero-art{position:absolute; right:-6px; bottom:calc(var(--cell)*0.75);   /* flush right: the prototype leaves 5px of paper at 1536 */
    width:var(--art-w); height:min(calc(100% - 16px),940px);   /* at 1536x1024 --art-w rests at its 1396px cap, so a cell still lands at the mock's own 6.36px grain */
    margin:0; aspect-ratio:auto; z-index:0;}
}
.hero h1{font-size:clamp(42px,5.2vw,74px); line-height:.98; letter-spacing:-0.005em; word-spacing:.06em; margin:18px 0 0;}
/* the subject of the sentence carries THE accent. --ember is the display tier (3.62:1 on paper,
   AA at this size and never used below 18px); --gold is a field band and stays out of type. */
.hero h1 .accent{color:var(--ember);}
/* the accent on type, by tier and nothing else: --ember is legal from 18px up (3.62:1), so
   it takes headings; body copy has to use --ember-deep (4.90:1, AA at any size), and gets
   the family's real 500 rather than a synthesised one to carry the extra weight */
.shead h2 .accent{color:var(--ember);}
.sintro .accent{color:var(--ember-deep); font-weight:500;}
/* hero copy carries more weight than body copy: Newsreader 500 (the family's heaviest real cut) at full ink */
.hero-sub{margin:calc(var(--cell)*2) 0 0; max-width:34ch; color:var(--ink); font-weight:500;
  font-size:clamp(17px,1.3vw,20px); line-height:1.6; font-synthesis-weight:none;}
.hero-ctas{display:flex; flex-wrap:wrap; gap:14px; margin-top:calc(var(--cell)*2.5);}
.hero-copy .micro{margin-top:calc(var(--cell)*1.5); font-weight:500; color:var(--ink-soft);}
/* the hero holds the only copy standing on the live ambient field, where a band cell can sit
   directly behind a glyph. a 1px paper halo keeps the labels and the sub legible over any band —
   the field still shows through between letters, so it costs no transparency. */
.hero-copy .kick, .hero-copy .micro, .hero-sub{
  text-shadow:1px 0 0 var(--paper), -1px 0 0 var(--paper), 0 1px 0 var(--paper), 0 -1px 0 var(--paper),
              1px 1px 0 var(--paper), -1px -1px 0 var(--paper), 1px -1px 0 var(--paper), -1px 1px 0 var(--paper);}

/* ===== the sequence — the thesis told as three scroll-driven beats. it ran directly under
   the hero until It.60 moved it below #how by user direction; nothing here depends on where
   it sits (the stage measures its own rect), so the block is unchanged.
   the section is three viewports tall; the stage sticks for the whole run, so scrolling
   advances the beat instead of the page. reference pacing (one viewport per beat, .8s
   swaps) with smooth quint eases — this section's deliberate exception to the stepped
   signature — plus a rail whose fill scrubs continuously with the scroll.
   the stage shares the page measure, so the statement lines up with every other
   section's copy; the art takes everything the reading column doesn't — here the
   animation is the subject, and it is given height as well as width. ===== */
.seq{height:260vh; height:260svh;}
.seq-stage{position:sticky; top:0; height:100vh; height:100svh; display:flex; align-items:center; overflow:hidden;}
.seq-inner{width:100%; max-width:var(--maxw); margin:0 auto; padding:0 var(--gutter);}

/* the rail: which beat you're on, and how many are left */
.seq-rail{display:flex; flex-wrap:wrap; gap:calc(var(--cell)*2); margin:0 0 calc(var(--cell)*2);}
.seq-rail span{position:relative; padding-bottom:9px; font-family:var(--mono); font-weight:500; font-size:10.5px;
  letter-spacing:.14em; text-transform:uppercase; color:var(--muted); transition:color .3s ease;}
.seq-rail span::after{content:""; position:absolute; left:0; bottom:0; width:100%; height:1px;
  background:var(--line);}
/* the fill is scrubbed by scroll (--fill 0..1 from sequence.js); the short linear
   transition only smooths the gaps between wheel deltas */
.seq-rail span::before{content:""; position:absolute; left:0; bottom:0; width:100%; height:1px; z-index:1;
  background:var(--ember-deep); transform:scaleX(var(--fill,0)); transform-origin:left;
  transition:transform .18s linear;}
.seq-rail span.on{color:var(--ember-deep);}

/* the panels stack in one box; only the active one is visible. text slides in from the side
   the scroll came from, art rises — the two axes keep the swap from reading as a crossfade.
   the grid is deliberately asymmetric: the statement holds a tight reading column while
   the art takes all the rest, and the art is tall enough that the composition fills the
   stage instead of floating in the middle of it */
.seq-panels{position:relative; min-height:min(60vh,720px); min-height:min(60svh,720px);}
.seq-panel{position:absolute; inset:0; display:grid;
  grid-template-columns:clamp(260px,23vw,400px) minmax(0,1fr);
  gap:clamp(32px,4.5vw,80px); align-items:center; opacity:0; pointer-events:none;
  transition:opacity .62s cubic-bezier(.23,1,.32,1);}
.seq-panel .seq-text{transform:translateX(60px); transition:transform .8s cubic-bezier(.23,1,.32,1);}
.seq-panel.from-left .seq-text{transform:translateX(-60px);}
.seq-panel .seq-art{transform:translateY(24px) scale(.985); transform-origin:50% 62%;
  transition:transform .8s cubic-bezier(.23,1,.32,1);}
.seq-panel.on{opacity:1; pointer-events:auto;}
.seq-panel.on .seq-text, .seq-panel.on .seq-art{transform:none;}
.seq-panel h2{font-size:clamp(34px,4.2vw,62px); line-height:1.04; letter-spacing:-0.005em; margin:0;
  color:var(--ember); max-width:12ch; text-wrap:balance;}
.seq-text p{margin:calc(var(--cell)*1.5) 0 0; max-width:26ch; color:var(--ink-soft);
  font-size:clamp(16px,1.2vw,19.5px); line-height:1.6;}
.seq-art{position:relative; height:min(60vh,720px); height:min(60svh,720px);}
.seq-artin{position:absolute; inset:0;}
.seq-art canvas{width:100%; height:100%; display:block;}

/* the made things (beat 02) and the memory page (beat 03) are depictions of the product's
   own surfaces — paper props laid on the living field. they are artwork inside the stage,
   not page chrome: the field ghosts through their translucency and flows around them */
/* the three made things read as one cluster, stepped diagonally across the stage.
   they keep their own proportions — the box is sized to them, not the other way round */
.seq-mk{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  width:min(100%,940px); display:flex; flex-direction:column; align-items:flex-start;
  gap:clamp(14px,2.4vh,30px);}
.mk-i{display:flex; align-items:center; gap:clamp(14px,1.4vw,22px); width:min(100%,820px);
  padding:clamp(20px,3.8vh,44px) clamp(20px,1.7vw,32px);
  background:rgba(255,253,247,.93); border:1px solid var(--line); border-radius:14px;
  box-shadow:0 2px 10px rgba(34,28,20,.05), 0 16px 40px rgba(34,28,20,.07);
  transition:box-shadow .5s cubic-bezier(.23,1,.32,1);}
.mk-i.made{box-shadow:0 3px 14px rgba(34,28,20,.07), 0 24px 56px rgba(34,28,20,.10);}
.mk-i:nth-child(2){align-self:center;}
.mk-i:nth-child(3){align-self:flex-end;}
.mk-k{flex:none; display:inline-grid; place-items:center; min-width:72px; padding:6px 10px;
  background:var(--ink); color:var(--paper); font-family:var(--mono); font-weight:500;
  font-size:9.5px; letter-spacing:.14em; text-transform:uppercase; clip-path:var(--step6);
  transform-origin:left center; transition:background .25s ease;}
.mk-i.made .mk-k{background:var(--ember-deep);}
.mk-w{font-size:clamp(16.5px,1.15vw,20px); color:var(--ink); line-height:1.3;}
.mk-w small{display:block; font-family:var(--mono); font-size:clamp(11px,.72vw,12.5px);
  letter-spacing:.04em; color:var(--muted); margin-top:4px;}
.mk-s{margin-left:auto; display:flex; align-items:center; gap:7px; white-space:nowrap;
  font-family:var(--mono); font-weight:500; font-size:10.5px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--ember-deep);}
.mk-s i{flex:none; width:14px; height:14px; border:1.5px solid var(--ember-deep);
  border-radius:50%; position:relative;}   /* the check is drawn, not typed — no glyph */
.mk-s i::after{content:""; position:absolute; left:3px; top:3.5px; width:6px; height:3px;
  border-left:1.5px solid var(--ember-deep); border-bottom:1.5px solid var(--ember-deep);
  transform:rotate(-45deg);}

/* centred by margin, not by transform, so the scene owns `transform` outright */
.seq-mem{position:absolute; inset:0; margin:auto; height:max-content;
  width:min(100%,900px); padding:clamp(18px,2.3vh,28px) clamp(22px,2vw,34px) clamp(42px,5.2vh,58px);
  background:rgba(255,253,247,.93); border:1px solid var(--line); border-radius:16px;
  box-shadow:0 2px 10px rgba(34,28,20,.05), 0 18px 46px rgba(34,28,20,.07);}
/* the page is titled, not labelled: Calistoga in the display accent, with the quiet
   mono counterweight opposite and a rule that draws itself under both */
.mem-h{position:relative; display:flex; justify-content:space-between; align-items:baseline; gap:14px;
  padding-bottom:clamp(12px,1.7vh,20px); margin-bottom:0;}
.mem-h::after{content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--line); transform:scaleX(var(--hr,1)); transform-origin:left;}
.mem-t{font-family:var(--display); font-size:clamp(22px,1.9vw,30px); line-height:1; color:var(--ember);}
.mem-e{font-family:var(--mono); font-weight:500; font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); white-space:nowrap;}

.mem-r{display:grid; grid-template-columns:154px 1fr; gap:18px; align-items:center;
  padding:clamp(16px,3.2vh,38px) 0; border-top:1px solid var(--line-2);}
.mem-h + .mem-r{border-top:0;}
/* each category is its own small monument — an emblem raised on a whispered accent
   plate above a mono label, with an accent rule beneath that the scene draws. the
   accent comes from the field's own ladder and is NEVER text (P7 stands): priorities
   burn ember (what matters most), routines keep sage time (what repeats), commitments
   hold sky (what is kept); each figure still peaks in one gold cell. one anatomy,
   three accents, three motion signatures — distinct at a glance, one system in hand */
[data-cat="pri"]{--cat:var(--ember); --cat-soft:rgba(224,73,42,.10);}
[data-cat="rou"]{--cat:var(--sage); --cat-soft:rgba(127,160,110,.16);}
[data-cat="com"]{--cat:var(--sky); --cat-soft:rgba(110,147,176,.15);}
.mem-cat{position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:9px;
  font-family:var(--mono); font-weight:500; font-size:10.5px; line-height:1;
  letter-spacing:.16em; text-transform:uppercase; color:var(--ember-deep);
  transform-origin:left center; transition:color .2s;}
.mem-r:hover .mem-cat{color:var(--ink);}        /* the pair's move: a colour swap only, no translate */
/* the figure: 8px accent cells on an 11px pitch — tight enough to read as one stroke —
   over a stepped-corner whisper of the same accent */
.mem-cat i{position:relative; z-index:0; display:block; width:41px; height:26px; margin:6px 0 2px 2px;}
.mem-cat i::after{content:""; position:absolute; inset:-7px -9px; z-index:-1;
  background:var(--cat-soft); clip-path:var(--step6);}
.mem-cat i::before{content:""; position:absolute; width:8px; height:8px; background:var(--cat);}
.mem-cat i b{position:absolute; width:8px; height:8px; background:var(--cat);}
/* the accent rule beneath the word — drawn by the scene (--cr), each in its own manner */
.mem-cat::after{content:""; width:38px; height:3px; background:var(--cat);
  transform:scaleX(var(--cr,1)); transform-origin:left center;}
[data-cat="pri"] .mem-cat i::before{left:0; top:18px;}                     /* rising — what comes first */
[data-cat="pri"] .mem-cat i b:nth-child(1){left:11px; top:12px;}
[data-cat="pri"] .mem-cat i b:nth-child(2){left:22px; top:6px;}
[data-cat="pri"] .mem-cat i b:nth-child(3){left:33px; top:0; background:var(--gold);}      /* the summit */
[data-cat="rou"] .mem-cat i::before{left:0; top:9px; background:var(--gold);}    /* the downbeat holds */
[data-cat="rou"] .mem-cat i b:nth-child(1){left:11px; top:9px;}            /* an even beat — what repeats */
[data-cat="rou"] .mem-cat i b:nth-child(2){left:22px; top:9px;}
[data-cat="rou"] .mem-cat i b:nth-child(3){left:33px; top:9px;}
[data-cat="com"] .mem-cat i::before{left:0; top:10px;}                     /* a check — what is held */
[data-cat="com"] .mem-cat i b:nth-child(1){left:11px; top:16px;}
[data-cat="com"] .mem-cat i b:nth-child(2){left:22px; top:8px;}
[data-cat="com"] .mem-cat i b:nth-child(3){left:33px; top:0; background:var(--gold);}      /* the tip */
.mem-r p{margin:0; font-size:clamp(15px,1.2vw,19.5px); color:var(--ink); line-height:1.45;
  text-wrap:pretty;}
.mem-f{position:absolute; left:clamp(22px,2vw,34px); right:clamp(22px,2vw,34px);
  bottom:clamp(16px,2vh,22px); margin:0;
  font-family:var(--mono); font-size:9.5px; letter-spacing:.08em; color:var(--muted);}
.mem-c{position:absolute; z-index:3; display:flex; gap:8px; align-items:baseline; white-space:nowrap;
  padding:8px 13px; background:rgba(255,253,247,.93); border:1px solid var(--line); border-radius:10px;
  font-family:var(--mono); font-weight:500; font-size:10px; letter-spacing:.05em; color:var(--ink-soft);
  box-shadow:0 2px 9px rgba(34,28,20,.09);}
.mem-c b{font-weight:500; color:var(--ember-deep); letter-spacing:.1em; text-transform:uppercase;
  font-size:9px;}
/* they gather from outside the page, never across its title: each starts level with or
   below its own row, so the flight reads as arriving rather than colliding */
.mem-c.c0{left:0; top:36%;}
.mem-c.c1{right:0; top:58%;}
.mem-c.c2{left:3%; bottom:3%;}

/* the constant beneath the three changing statements */
.seq-by{margin:calc(var(--cell)*2) 0 0;}

/* compositor hints, only where the mechanism actually runs */
@media(min-width:901px){
  .seq-panel{will-change:opacity;}
  .seq-panel .seq-text, .seq-panel .seq-art, .seq-artin{will-change:transform;}
}

/* very wide: the swap travels further, so the bigger stage still reads as motion.
   scoped :not(.on) — these sit after the .on reset, and without it the live panel
   would inherit the offset and sit 84px off its own column */
@media(min-width:1680px){
  .seq-panel:not(.on) .seq-text{transform:translateX(84px);}
  .seq-panel.from-left:not(.on) .seq-text{transform:translateX(-84px);}
  .seq-panel:not(.on) .seq-art{transform:translateY(34px) scale(.98);}
}

/* narrow + reduced motion: no stickiness, no swapping — the three beats simply stack and read.
   the props stop being absolutely placed art and become ordinary stacked blocks */
@media(max-width:900px){
  .seq{height:auto;}
  .seq-stage{position:static; height:auto; overflow:visible; padding:calc(var(--cell)*5) 0;}
  .seq-inner{max-width:none; padding:0 var(--gutter);}
  .seq-rail{display:none;}
  .seq-panels{min-height:0;}
  .seq-panel{position:static; grid-template-columns:1fr; gap:calc(var(--cell)*2);
    opacity:1; pointer-events:auto; margin-bottom:calc(var(--cell)*5);}
  .seq-panel:last-child{margin-bottom:0;}
  .seq-panel .seq-text, .seq-panel.from-left .seq-text, .seq-panel .seq-art{transform:none;}
  .seq-art{height:auto;}
  [data-seq="0"] .seq-art{height:320px;}
  .seq-artin{position:static;}
  .seq-mk{position:static; left:auto; top:auto; transform:none; width:100%; gap:calc(var(--cell)*1.5);}
  .seq-mem{position:static; inset:auto; margin:0; height:auto; width:100%; padding:18px 20px 20px;}
  .mk-i{width:100%;}
  .mem-c{display:none;}
  .mem-f{position:static; left:auto; right:auto; bottom:auto; margin-top:12px;}
  .mem-r{grid-template-columns:1fr; gap:9px; padding:13px 0;}
  .mem-cat{justify-self:start;}
  .seq-by{margin-top:calc(var(--cell)*4);}
}

/* ===== how: words-become-plans flow ===== */
.flowbox{width:100%; aspect-ratio:3/1; margin-bottom:calc(var(--cell)*2);}
.flowbox canvas{width:100%; height:100%; display:block;}
.beats{display:grid; grid-template-columns:1fr; gap:calc(var(--cell)*2);}
@media(min-width:560px){ .beats{grid-template-columns:repeat(3,1fr);} }
.beat{position:relative; display:flex; flex-direction:column; gap:11px;}
/* ===== the stage number — ONE object, shared by #how's beats and #trust's promises.
   the two numbered sections of this page used to number in two different registers: a
   filled pixel chip here, a tracked mono label there. they are the same editorial move,
   so they are now the same mark, declared once.

   THE ROW CARRIES THE SIZE AND EVERYTHING ELSE IS WRITTEN IN ITS EM. that is what lets
   one declaration serve a title that ramps 19 -> 28 across four breakpoints without the
   chip being restated at any of them. the arithmetic runs through the chip's own font,
   because `width` in em resolves against the element's OWN size: .474em of the row is
   the 9px numeral this chip has always carried at #how's 19px title, and 2.43 of THAT
   is its 22px square. at 19px the pair therefore computes to 21.85 / 9.006 — the shipped
   22 / 9 to within a rounding — and above it the square tracks the title instead of
   shrinking against it. weight 500 is a real Plex Mono file (never a synthesised one)
   and it is what holds a small numeral open when it is knocked out of a solid field. ==== */
.beat .bl, .tcard .tl{display:flex; align-items:center; gap:.474em; line-height:1.25;}
.beat .dn, .tcard .tn{display:inline-grid; place-items:center; font-size:.474em; width:2.43em; height:2.43em;
  flex:none; margin:0; background:var(--ember-deep); color:var(--paper);
  font-family:var(--mono); font-weight:500; letter-spacing:.04em; clip-path:var(--step6);}   /* pixel-stepped chip, brand-filled (paper on ember-deep, 4.9:1) */
/* the title takes the row's size, so #how and #trust set theirs on .bl / .tl alone */
.beat .bt{font-family:var(--display); font-size:1em; color:var(--ember-deep);}             /* the stage title on the accent's text tier — AA at any size */
/* It.88: the stage title is #trust's 24px, not its own 19 — the two sections say the
   same kind of thing and were saying it at two different scales. the one place it may
   not have that size is the 560–~960 band, where these are three columns of 155–200px
   and 24px breaks "It remembers." onto a second line (measured: it needs 183.8px against
   154.7 at 560). so the middle of the ramp is fluid and the ends are #trust's own: 24
   below 560 where the beats stand one per row, 24 again by ~960, and #trust's 24→28
   from 1280. */
.beat .bl{font-size:24px;}
@media(min-width:560px){ .beat .bl{font-size:clamp(19px,2.5vw,24px);} }
@media(min-width:1280px){ .beat .bl{font-size:clamp(24px,1.6vw,28px);} }

/* ===== It.93 — the stage number's hover life: the reference's own smiley =====
   It.90 opened this chip into a nine-cell face knocked out of the plate in the
   numeral's own paper. It.92 keeps the idea and throws out the construction: the
   face is no longer a knockout inside the chip, it is a 13x13 pixel SPRITE — the
   craft.wild.as reference's own field size — laid over the row, in its own
   colours, three and a half times the size, with hearts thrown off it.
   `assets/js/stagenum.js` deals the three faces (01 smiles, 02 falls in love,
   03 blows a kiss) and holds the header on the reference and what is taken from
   it literally. WHAT LIVES HERE is the arithmetic and the motion.

   THE ARITHMETIC IS STILL THE CHIP'S OWN, CARRIED TWO STEPS FURTHER, and it is
   the thing an edit will get wrong. It.88's chip is 2.43em of a .474em font =
   1.15182em of the ROW, read as SEVEN cells, so one cell is 1.15182/7 =
   .16454571em. The open sprite is TWENTY-FIVE of those cells = 4.11364em, and
   the closed sprite is the chip exactly: 7/25 = .28. Between them are THREE
   whole-cell steps of six — 7 -> 13 -> 19 -> 25 — which is why the growth is
   `steps(3, start)` and not an ease, and why the middle stage is the one worth
   knowing: at 13 chip-cells the sprite's own 13 lattice cells are ONE chip cell
   each, so for one beat the two grids are the same unit. Nothing on this object
   is ever measured in script.

   WHY 25 AND NOT MORE, measured. The sprite is pinned at its left edge (which is
   the text column's edge, and a centred zoom would hang it in the gutter), so
   all the horizontal growth goes right and all the vertical growth is free.
   25 chip-cells is 98.7px at #how's 24px row, 115.2 at #trust's 28, 78.2 at the
   19px band — against a beat column that is never narrower than 154.7px (560px
   viewport, three abreast, the binding case). It therefore never leaves its own
   column at any width, and the page's scrollWidth is untouched.

   IT IS ALLOWED TO SIT ON TOP, and that is the whole reason it can be this big.
   It.90's nine cells were the most that fits between the chip and the title; a
   sprite that clears the title has to cover it. Covering is not a contrast
   problem — nothing is being read THROUGH the sprite, it is in front of the
   title the way a menu is in front of a page, and it is gone the moment the
   pointer leaves. (The rule that does apply is the 3:1 non-text minimum for the
   sprite's own silhouette against paper, which is what the rim is for.)

   NOTHING HERE IS A LAYOUT PROPERTY. scale, transform, opacity, background-color.
   #how's third beat has under 3px of slack at 560 — It.88 turned down a 24px
   title over that exact measurement, and this may not spend it. ==== */
@media(hover:hover) and (pointer:fine){
  .pxrow{position:relative;}
  /* the plate does not vanish when the sprite opens — it becomes the sprite's
     rim (see below), so all that happens here is the fill letting go.

     THE WAY BACK IS NOT THE WAY OUT REVERSED, and the delays are where that is
     said. Opening, the numeral goes at once and the sprite takes its place.
     Closing, the sprite has to be gone BEFORE the numeral comes back, or for a
     fifth of a second there is a face and a numeral in the same 27px — so the
     base rules (which are what a leaving row transitions TO) carry a .2s delay
     and the `.hot` rules zero it. Same two declarations, two different
     directions, no JS involved. */
  .pxrow .dn, .pxrow .tn{transition:background-color .16s steps(2) .2s;}
  .pxrow.hot .dn, .pxrow.hot .tn{background-color:transparent; transition-delay:0s;}
  /* the numeral leaves fast and small, so the plate reads as opening rather than
     as the number being blown up. it is a box the engine wraps around whatever
     string the markup holds, never a re-authored one. `scale` and not `transform`,
     because every moving part of this object keeps transform free for one job. */
  .pxrow .pxn{display:block; transition:opacity .12s steps(2) .2s, scale .3s steps(2,start) .2s;}
  .pxrow.hot .pxn{opacity:0; scale:.5; transition-delay:0s, 0s;}
  /* and the title steps back. A 99px sprite on a 24px row covers most of "You
     talk.", and the choice is not whether to overlap — nothing this size can
     avoid it — but whether the overlap reads as a collision or as the row giving
     the number the floor. #how already speaks this language: `.beats:hover .beat`
     drops the beats you are not on to .45. This is the same sentence one level
     down. It is opacity on a `transform`-free box, so nothing reflows, and the
     title is at full strength again .2s after the pointer leaves. */
  .pxrow .bt, .pxrow h3{transition:opacity .22s steps(2);}
  .pxrow.hot .bt, .pxrow.hot h3{opacity:.34;}

  /* ---- the sprite ------------------------------------------------------
     TWO 13x13 grids in one box. `.pxd` is the disc and never moves; `.pxg` is
     the features, and the engine translates it toward the hand every frame.
     Splitting them is what makes the gaze possible at all — the reference
     translates its eyes and mouth over a still face (its `<g transform>`), and a
     lean that took the silhouette with it would just be the whole object
     sliding. It.92 had a THIRD grid, the rim, whose job was to trim a feature
     that leaned out to the disc's edge; with no outline to trim against and a
     lean that is now half a cell rather than a whole one, both are gone. */
  .pxrow .pxf{position:absolute; z-index:2; left:0; top:50%; width:4.11364em; height:4.11364em;
    margin-top:-2.05682em; pointer-events:none; transform-origin:0 50%;
    scale:.28; transition:scale .34s steps(3,start), transform .2s;}
    /* `transform` is in that list only for the way out: the hop below is an
       animation, and removing `.hot` deletes it mid-flight, which would snap the
       sprite down. With a transition on the same property the snap becomes a
       fall. */
  .pxrow.hot .pxf{scale:1;}
  .pxrow .pxd, .pxrow .pxg{position:absolute; inset:0; display:grid;
    grid-template-columns:repeat(13,1fr); grid-template-rows:repeat(13,1fr);}
    /* `.pxg` carries NO transform of its own: the engine writes fractional px
       into it on every frame, the way the reference does (`translate(5.46,0.64)`
       on the element the user supplied). There is no transition here either —
       the lerp IS the smoothing, and a transition on top would smooth a value
       that is already smoothed and lag the hand. */

  /* every cell arrives on `scale` — the disc ring by ring out of the middle, the
     features after the last ring — and leaves back along the anti-diagonal. the
     1.04 is the reference's own bleed (its rects are 1.04 of a 1.0 cell): four
     percent of overlap is what keeps a contiguous run of cells from showing a
     seam between its tracks, and it is invisible at the silhouette. */
  .pxrow .pxf i{display:block; background-color:var(--sk); scale:0;
    transition:scale .16s steps(3); transition-delay:var(--dr,0ms);}
  .pxrow.hot .pxf i{scale:1.04; transition-delay:var(--d,0ms);}

  /* ---- the palette ------------------------------------------------------
     THREE COLOURS, AND NO OUTLINE. The reference's smiley is a bare disc with
     seven dark cells on it — no rim, no plate, no border — and It.92's
     --ember-deep ring is gone with the skins it was holding together.
       disc  --neon, the reference's `data-base` pastelled, on all three faces.
             That costs the silhouette: 1.01:1 on paper, so the disc's EDGE is
             carried by hue and nothing else. It is the reference's own trade
             (its neon is 1.10:1 on its near-white) and it is directed.
       ft    --ink, the mouth and 01's eyes. 14.84:1 on the neon — the drawing is
             what has to read, and it does, by a wide margin.
       ac    --ember, 02's hearts and 03's blush, at 3.58:1. Byte-identical to
             the reference's own #e0492a, which is also the second entry of its
             `ACC` list, so the accent was never a choice this page had to make.
     `--sk` is what the engine's scatter overwrites per cell, which is why every
     rung below is written through it and never as a `background` outright. */
  .pxrow .pxf .pxd i{--sk:var(--neon);}
  .pxrow .pxf .pxg i.ft{--sk:var(--ink);}
  .pxrow .pxf .pxg i.ac{--sk:var(--ember);}

  /* ---- the wave, and the scatter over it ---------------------------------
     The reference's disc runs TWO colour behaviours at once and It.92 shipped
     only an approximation of one of them. Its `stampDisk` is
         v = hsh(...) < 0.18 ? ACCV[...] : val + 0.03*sin((c*0.7 + r*0.7) - t*0.01)
     — a gentle diagonal wave with ~18% of cells jumping to an accent, re-rolled
     at `floor(t/140)`, about 7fps.

     THE WAVE IS HERE; THE SCATTER IS IN THE ENGINE, because a per-cell re-roll
     at 7fps is not a thing CSS can express and pretending otherwise is what got
     It.92 a travelling band instead of a flicker. `0.01 rad/ms` is one cycle
     every 628ms and `0.7 rad/cell` a 70ms lag per anti-diagonal step; both are
     spent verbatim, and `--ord` is the c+r the engine writes.

     ITS AMPLITUDE IS 0.03 OF A UNIT HEAT, which is a whisper, so the rungs here
     are mixed at 94% — It.92 learned this the hard way at 74% (a mottled disc
     that read as a JPEG) and again at 88%. The wave is the floor the scatter
     stands on, not the effect. */
  .pxrow .pxf i{--hi:color-mix(in srgb, var(--sk) 94%, var(--glow));
                --lo:color-mix(in srgb, var(--sk) 94%, var(--rust));}
  @keyframes pxWave{0%{background-color:var(--hi);} 12%{background-color:var(--sk);}
                    50%{background-color:var(--lo);} 62%{background-color:var(--sk);}}
  .pxrow.hot .pxf i{animation:pxWave .628s steps(1,end) calc(var(--ord,0) * -70ms) infinite;}

  /* ---- the blink --------------------------------------------------------
     The reference drops a lid for ~110ms on a multi-second timer, and it does it
     by SQUASHING the eye rect: `height 12 -> 2` with `y` pushed down 5. That is
     a geometry blink, and on a one-cell eye it is the only kind available —
     It.92 blinked by switching off the top row of a two-cell eye, which a single
     cell does not have. So the eye scales to 2/12 of its height about a point
     5/12 down, which is the reference's own two numbers.

     The timer is a fixed 3.4s cycle carrying two blinks, at 0.70s and 2.14s.
     Fixed rather than the reference's `2200 + random*4200`, because there is no
     Math.random in this object; early in the cycle rather than late, because a
     blink parked at the end is a blink most hovers never see. Only 01 blinks:
     hearts have no lids and 03's eyes are shut already. */
  @keyframes pxLid{0%,20%{transform:none;} 20.5%,23.5%{transform:scaleY(.1667);} 24%,63%{transform:none;}
                   63.5%,66%{transform:scaleY(.1667);} 66.5%,100%{transform:none;}}
  .pxrow .pxf .pxg i.e{transform-origin:50% 41.67%;}
  .pxrow.hot .pxf .pxg i.e{animation:pxWave .628s steps(1,end) calc(var(--ord,0) * -70ms) infinite,
                           pxLid 3.4s steps(1,end) 0s infinite;}

  /* ---- the hop ----------------------------------------------------------
     The reference's `smJump`, and the element the user supplied carries
     `class="smiley sm-jump"`, so it is on by default. `.62s`,
     `cubic-bezier(.3,1.5,.5,1)`, stops at 0/28/52/68/100, and -18px then -7px on
     a 128px face = -14.0625% and -5.46875%. Those two percentages are now used
     RAW. It.92 quantised them to two cells and one on the argument that a sprite
     on a lattice may not land on half a pixel — but the supplied element's own
     gaze is `translate(5.46, 0.64)`, which settles it: the reference moves this
     face in fractions, and so does this. Smooth, and deliberately: this is the
     reference's curve, kept on purpose, in a page whose default is stepped. */
  @keyframes pxHop{0%{transform:none;} 28%{transform:translateY(-14.0625%);} 52%{transform:none;}
                   68%{transform:translateY(-5.46875%);} 100%{transform:none;}}
  .pxrow.hot .pxf{animation:pxHop .62s cubic-bezier(.3,1.5,.5,1) .34s 1 both;}

  /* ---- the hearts -------------------------------------------------------
     Four on arrival at 90ms apart, then one every 820ms while the hand stays —
     the first three numbers are the reference's, the fourth is ours, because a
     burst that fires once leaves a long hover dead. The glyph is the same eleven
     cells the engine draws 02's eyes and 03's kiss with, so a heart leaving the
     face is literally the face's own heart. The flight is the reference's line
     for line: 1s ease-out, invisible until 14%, rising and growing to 1.1, gone.
     Its 24x19 at a 128px face are SIX chip-cells wide here, which computes to
     23.7 x 19.0 at #how's 24px row — the same two numbers, arrived at from this
     page's own unit rather than copied.

     Smooth easing is the exception on this page and it is directed here: the
     brief asked for the reference's motion, timing and softness on these
     specifically. It.92's sideways drift is gone — the reference has none, and
     the fixed twelve-column walk the engine starts them on already fans them. */
  .pxrow .pxh{position:absolute; z-index:4; pointer-events:none;
    left:calc(var(--hx,6) * .316434em); top:calc(50% - 2.45em);
    width:.98727em; height:.789818em; display:grid;
    grid-template-columns:repeat(5,1fr); grid-template-rows:repeat(4,1fr);
    animation:pxHeart 1s ease-out forwards;}
  .pxrow .pxh i{display:block; background-color:var(--ember);}
  @keyframes pxHeart{0%{opacity:0; transform:translate(-50%,0) scale(.5);}
                     14%{opacity:1;}
                     100%{opacity:0; transform:translate(-50%, -2.4em) scale(1.1);}}

  /* the trigger is the open sprite's WIDTH and the ROW's own height. width, so
     the hand can cross the whole face without closing it; the row's height and
     not the sprite's, because a 99px-tall transparent pad would reach down over
     the paragraph — and in #trust's third card, over the Privacy Policy link
     inside it. It.90 could make the pad and the face the same box only because
     the face was 35px. */
  .pxrow .pxhit{position:absolute; z-index:3; left:0; top:0; height:100%; width:4.11364em;}
  /* ...but it may not steal the physics line's grab: #trust's strip opens to 60px
     when held, which reaches into the row. the line wins there. */
  #trust .mline-hit{z-index:6;}
}

.beat .bd{margin:0; font-size:14.5px; line-height:1.55; color:var(--ink-soft);}
/* the pixel tracker under the hovered beat (62px, snapped to 10px by JS) */
@media(hover:hover){
  .beats:hover .beat{opacity:.45;} .beats .beat:hover{opacity:1;}
  .beats .beat{transition:opacity .3s ease; cursor:none; padding-bottom:17px;}
  .beat .pxline{position:absolute; left:0; right:0; bottom:0; height:8px; opacity:0; transition:opacity .25s steps(3); pointer-events:none;}
  .beat:hover .pxline{opacity:1;}
  .beat .pxline::before{content:""; position:absolute; inset:0; background:repeating-linear-gradient(90deg,var(--line) 0 9px,transparent 9px 10px);}
  .beat .pxline i{position:absolute; top:0; left:0; width:62px; height:8px; background:repeating-linear-gradient(90deg,var(--ember) 0 9px,transparent 9px 10px); transform:translateX(var(--lx,0px));}
}

/* ============================================================
   IN PRACTICE — one card, one exchange at a time
   ============================================================
   The section is a two-column editorial spread: the words on the left, one card
   on the right, optically centred against each other. Nothing scrolls, in either
   axis, ever.

   The card holds ONE exchange and walks the deck by itself. It is built around a
   single idea — a spoken moment has two halves and a seam. Above the seam is what
   you said, set at reading scale in ink. The seam is a rule that ASIS advances as
   it works, its label sitting on the left of it. Below the seam is the answer, the
   largest type in the section after the heading, in the accent, and under that the
   one mono line that says what actually happened. Two halves, one seam, no chrome.

   There is deliberately NOTHING that says which example is showing. No dots, no
   pager, no numbers, no play button — a reader watching an illustration does not
   need an index into it, and every one of those devices makes a card look like a
   widget. The only control is hover (or focus), which holds the sequence exactly
   where it is so a slow reader is never overtaken, and it has no chrome at all.

   Transitions are the page's own line-mask reveal, at its documented .9s steps(8)
   with the 0.09s stagger: the finished exchange masks upward in three groups and
   the next rises into its place from below. Because the incoming text arrives with
   everything still in `.su`, it enters faint and then DEVELOPS under the caret —
   the arrival and the typing are one continuous move, not two.

   The trail is `~/Website_resource/Mouse Effects/10`, at full strength and lit
   through the ember field's OWN band ladder — taupe, sage, sky, gold, glow, ember,
   rust — stepping ALONG the echoes over time, so the smear reads as light
   refracting off this page's palette rather than as a generic hue cycle. Engine in
   demo.js. */
.demo-grid{display:grid; grid-template-columns:minmax(0,.92fr) minmax(0,1fr);
  column-gap:clamp(40px,4.4vw,88px); align-items:center;}
.demo-note{margin:0;}
.demo-stage{position:relative;}

/* ---- the card. paper lifted off paper: an inset top highlight, a contact shade, a
   mid lift and one wide soft cast — four layers at very low alpha rather than one
   heavy drop, which is the difference between a sheet and a UI panel. the face stays
   translucent so the ember field still runs faintly through it, but firmly enough
   that the echoes riding behind can never print a hairline across the reading column. */
.cv{position:relative; z-index:1; display:block;
  background:linear-gradient(177deg, rgba(255,253,247,.975), rgba(255,253,247,.915));
  border:1px solid var(--card-line); border-radius:var(--card-radius);
  --pad:clamp(24px,2.4vw,38px); padding:0 var(--pad);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.82),
             0 1px 1px rgba(34,28,20,.03),
             0 10px 20px -16px rgba(34,28,20,.18),
             0 36px 64px -38px rgba(34,28,20,.30);
  transition:border-color .3s steps(3), box-shadow .55s cubic-bezier(.16,1,.3,1);}
@media(hover:hover){
  /* the card's own tell is its trail; the border firms one step — the colour-swap-only
     move (It.22 §707) — and the cast deepens as if it had lifted a millimetre */
  .demo-stage:hover .cv{border-color:rgba(190,58,31,.45);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.82),
               0 1px 1px rgba(34,28,20,.03),
               0 14px 26px -16px rgba(34,28,20,.20),
               0 48px 80px -40px rgba(34,28,20,.34);}
}
/* ---- the wake. the trail is no longer eight hairline outlines: it is the CTA marquee's
   own material. `field.js` draws the closing line by stamping its glyph mask one pixel per
   FIELD CELL; this canvas walks the card's silhouette at each lagged position and stamps it
   the same way — viewport-aligned to the field's lattice, `cell - 1` with the 1px gutter,
   the live cell size read off the same pxctl button — so the card's after-image and the
   closing line are made of the same thing. It sits behind the card, takes no pointer, and
   is cleared the instant the loop sleeps. Engine in demo.js. ---- */
/* the CSS box is stated OUTRIGHT, never left to `inset` alone: a canvas takes its intrinsic
   size from its width/height ATTRIBUTES, so an auto-sized one grows to whatever backing
   store the script just gave it and then feeds that back in — this box reached 2400x1200
   before it was pinned, and the wake drew at correct lattice coordinates inside a wildly
   mis-anchored canvas. */
/* the horizontal reach is the page GUTTER, not a constant: the stage is the right column,
   so its right edge sits exactly one gutter in from the viewport, and +gutter reaches that
   edge and stops. a flat -96px overhung it and put 30px of document overflow on the page at
   1024. vertically there is nothing to overflow, so the wake keeps its full 96px there. */
.demo-wake{position:absolute; left:calc(-1 * var(--gutter)); top:-96px;
  width:calc(100% + var(--gutter) * 2); height:calc(100% + 192px);
  z-index:0; display:block; pointer-events:none;}

/* ---- the head: a voice mark, the name in the wordmark's own face, and a rule that spans
   the whole card. head and foot both bleed to the edges — a card whose rules stop short of
   its sides reads as a box with lines in it; one whose rules reach the edge reads as a
   sheet with a head and a foot. ---- */
.cv-head{display:flex; align-items:center; gap:13px;
  margin:0 calc(-1 * var(--pad)); padding:calc(var(--cell)*1.9) var(--pad) calc(var(--cell)*1.4);
  border-bottom:1px solid var(--line);}
/* THE MARK. an ember square in front of a wordmark is a status light, and a status light is
   the cheapest object a card can wear. this is three thin bars at speaking level — the one
   picture of a voice that still reads at 15px — held still and deliberately uneven at rest,
   and stepping while ASIS actually speaks. scaleY, never height: the bars must not lay out. */
.cv-mark{display:flex; align-items:flex-end; gap:3px; height:15px; flex:none;}
.cv-mark i{display:block; width:2px; height:15px; background:var(--ember-deep);
  transform:scaleY(var(--h,.4)); transform-origin:50% 50%; transition:transform .45s steps(4);}
.cv-mark i:nth-child(1){--h:.36;}
.cv-mark i:nth-child(2){--h:.82;}
.cv-mark i:nth-child(3){--h:.52;}
.cv-mark.live i{animation:vlevel .78s steps(3) infinite;}
.cv-mark.live i:nth-child(2){animation-delay:.13s;}
.cv-mark.live i:nth-child(3){animation-delay:.26s;}
@keyframes vlevel{0%,100%{transform:scaleY(.3);} 50%{transform:scaleY(1);}}
@keyframes dotPulse{0%,100%{transform:scale(1); opacity:.8;} 50%{transform:scale(1.4); opacity:1;}}
/* the display face, by direction. Calistoga ships a 400 only, so this asks for exactly that
   — a 500 here would be a synthesised smear — and it is the face the masthead wordmark is
   set in, which is the point: the card is signed, not labelled. */
.cv-name{font-family:var(--display); font-weight:400; font-size:19px; letter-spacing:.002em;
  color:var(--ember-deep);}

/* ---- the deck. with JS one exchange occupies the stage and the rest are gone; with
   no JS this is simply an honest list of all of them, one under the next. ---- */
.cv-deck{list-style:none; margin:0; padding:0;}
/* no border between exchanges any more: each one now ENDS on its foot's full-bleed rule,
   and a second divider under that is the same line said twice. only the JS-less stack ever
   shows more than one. */
.cv-ex{padding:calc(var(--cell)*2.25) 0 0;}
/* the three mask groups. the inner block is what moves; the mask is what makes the
   move read as type leaving a page rather than a box sliding around on one. */
/* the mask takes the card's full width and pads the text back into place, so the footer
   inside it can bleed its rule to the card's edges without overflow:hidden shearing it */
.cv-m{overflow:hidden; margin:0 calc(-1 * var(--pad)); padding:0 var(--pad);}
.cv-mv{display:block; transition:transform .9s steps(8) var(--d,0s);}
.cv-ex.out .cv-mv{transform:translateY(-118%);}
.cv-ex.pre .cv-mv{transform:translateY(118%); transition:none;}

/* ---- above the seam: what you said ---- */
.cv-lbl{margin:0 0 10px; font-family:var(--mono); font-weight:500; font-size:10px; line-height:1.5;
  letter-spacing:.16em; text-transform:uppercase; color:var(--muted);}
.cv-ask{margin:0; font-size:clamp(19px,1.45vw,22px); line-height:1.45; color:var(--ink);
  max-width:40ch;}

/* ---- the seam: an ember capsule saying what ASIS is doing, and a rule it advances one
   stage at a time. the capsule is the accent's TEXT tier filled solid with a --paper word
   on it — 4.90:1 either way round, the same pairing the memory chips and every .btn use —
   so the one thing on the card that is happening right now is also the one thing wearing
   colour. it hugs its label and changes width instantly at the swap, while the word is
   invisible: an instant change belongs to a stepped page far more than a width tween does.
   the rule is progress through THIS exchange, which is worth saying; it is not, and must
   never become, progress through the deck. ---- */
.cv-state{display:flex; align-items:center; gap:12px; margin:calc(var(--cell)*2) 0 0; height:24px;}
/* one colour source for everything on the capsule: the mark takes `currentColor` rather
   than naming --paper again, which is both tidier and keeps a 5px mark out of the
   opaque-panel check that (correctly) watches for paper backgrounds on this page */
.cv-scap{display:inline-flex; align-items:center; gap:8px; flex:none; height:24px;
  padding:0 12px 0 10px; border-radius:999px; background:var(--ember-deep);
  color:var(--paper); overflow:hidden;}
.cv-sm{width:5px; height:5px; flex:none; background:currentColor; opacity:.72;
  transition:opacity .3s steps(3);}
.cv-sl{min-width:0; font-family:var(--mono); font-weight:500; font-size:9.5px; line-height:24px;
  letter-spacing:.11em; text-transform:uppercase; color:inherit; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  transition:opacity .2s steps(4), transform .2s steps(4);}
.cv-sr{position:relative; flex:1; height:1px; background:var(--line-2);}
.cv-sr i{position:absolute; inset:0; display:block; background:var(--ember-deep);
  transform:scaleX(var(--p,0)); transform-origin:0 50%; transition:transform .5s steps(4);}
.cv-state.live .cv-sm{opacity:1; animation:dotPulse 1s ease-in-out infinite;}
.cv-state.swap .cv-sl{opacity:0; transform:translateY(-5px);}
/* the incoming label RISES in — an animation, not a reversed transition, so it enters
   from below instead of dropping back down the way it left */
.cv-state.enter .cv-sl{animation:dslIn .28s steps(4) both;}
@keyframes dslIn{from{opacity:0; transform:translateY(5px);} to{opacity:1; transform:none;}}

/* ---- below the seam: the answer, and the fact. the reply is the largest type in the
   section after the heading, because it is the payoff and the page should say so.
   Newsreader ships 400 and 500 only — 500 IS the semibold here; 600 would synthesise a
   fake bold, which on a serif at this size looks smeared rather than emphatic. ---- */
.cv-reply{margin:calc(var(--cell)*2) 0 0; font-size:clamp(22px,1.75vw,27px); font-weight:500;
  line-height:1.36; letter-spacing:.002em; color:var(--ember-deep); max-width:34ch;}
/* ---- the foot. the fact ASIS actually wrote deserves its own band rather than a caption
   hanging off the reply: a full-bleed rule, its own air, and the copy in --ink at 11.9:1.
   the rule is drawn whether or not the fact has landed yet, so the card is never a sheet
   with an unfinished bottom edge. ---- */
.cv-foot{margin:calc(var(--cell)*2.25) calc(-1 * var(--pad)) 0;
  padding:calc(var(--cell)*1.6) var(--pad) calc(var(--cell)*2);
  border-top:1px solid var(--line);}   /* the head's rule, exactly — the card closes the way it opens */
/* the record is mono again, by direction. It.72 set it as a Newsreader italic to carry the
   emphasis a bold could not (the only italic this site ships is a 400); back on Plex Mono it
   reads as a machine-written record, which is what it is, and takes its weight from the real
   500 and from full --ink at 16.3:1 rather than from a face change. */
.cv-proof{margin:0; font-family:var(--mono); font-weight:500; font-size:11px; line-height:1.5;
  letter-spacing:.085em; color:var(--ink);}

/* ---- the tape (JS only). the not-yet-said tail HOLDS ITS PLACE, so every line break is
   decided once at full length and typing can never reflow the card. the two heights are
   MEASURED across the whole deck (demo.js), because a card that resizes between examples
   is the one thing that would make the loop feel cheap.
   the tail is INVISIBLE, not faint. it sat at .12 for a while so an arriving exchange
   would rise in already written and then develop — but a sentence showing in grey before
   it has been said reads as spoiled text rather than as anticipation, and it gave away the
   one thing the typing is for. it keeps its place and shows nothing. ---- */
.cr{display:none; width:2px; height:.9em; margin-left:3px; vertical-align:-.06em; background:var(--ember-deep);}
.typing > .cr, p.typing .cr{display:inline-block; animation:caretBlink 1s steps(1) infinite;}
@keyframes caretBlink{50%{opacity:0;}}
.su{opacity:0;}
.cv.js .cv-deck{position:relative;}
.cv.js .cv-ask{height:calc(var(--ra,2) * 1.45em); overflow:hidden;}
.cv.js .cv-reply{height:calc(var(--rr,2) * 1.36em); overflow:hidden;}
.cv.js .cv-state{opacity:0; transition:opacity .3s steps(3);}
.cv.js .cv-ex.work .cv-state{opacity:1;}   /* the seam waits for the ask: a working line
                                              under a half-typed sentence says nothing */
.cv.js .cv-proof{opacity:0; transform:translate(-7px,5px);
  transition:opacity .35s steps(4), transform .45s steps(5);}
.cv.js .cv-ex.said .cv-proof{opacity:1; transform:none;}
/* the capsule holds ONE width, the widest stage in the whole deck. it never truncates and
   it never jumps at a swap, so the progress rule always starts from the same x */
.cv.js .cv-sl{width:var(--sw, 130px);}
/* the measuring pass: reservations off for one layout read, then back on */
.cv.measuring .cv-ask, .cv.measuring .cv-reply{height:auto; overflow:visible;}
.cv.measuring .cv-sl{width:auto;}
.cv.measuring .cr{display:none;}

/* ---- narrow: the columns stack. the card keeps its shape exactly — one exchange, one
   seam — and only its type comes down a step. ---- */
@media(max-width:1023px){
  .demo-grid{grid-template-columns:minmax(0,1fr);}
  .demo-stage{margin-top:calc(var(--cell)*3);}
  .cv-ask, .cv-reply{max-width:none;}
  /* the trail does not run down here, and a canvas 64px wider than a full-measure card is
     40px of document overflow for nothing */
  .demo-wake{display:none;}
}
@media(max-width:560px){
  .cv-ask{font-size:17.5px;}
  .cv-reply{font-size:20px;}

  /* 42 mono characters at the wide tracking broke "9:00" onto a line of its own */
  .cv-proof{font-size:10px; letter-spacing:.06em;}
  .cv-sl{font-size:9px; letter-spacing:.09em;}
}


/* ===== a day with ASIS: springy carousel, now carrying five recordings =====

   IT.108: the engine is UNCHANGED. The momentum track, the resistance past the ends, the
   12px settle grid, the Drag tell, the hover arrows and the chip that steps up over the
   media are this section's signature and the direction was to change what the tiles SHOW.

   WHAT MOVED IS THE MEDIA BOX. `.csm` was a 340px-tall canvas holding a generative pixel
   thumb; it is now a SQUARE holding a 1440x1440 recording of the app. `aspect-ratio` and
   NEVER a height: the box is the media's own shape at every width, which is the whole of
   the no-crop guarantee (`object-fit:cover` on a ratio-matched container crops nothing)
   and it means the track's height is known before a byte of video arrives, so the page
   below it never shifts. One token, `--slide-media`, is both the tile's width and — since
   the box is square — the number the arrows key off, so the two cannot drift apart.

   THE CORNER IS ROUND, AND THAT PUTS IT BACK IN ITS OWN FAMILY. Pixel art on transparent
   ground had no edge to cut; a recording does, and five wildly different scenes (pale
   yellow, tan, teal, two near-blacks) only read as one product if every one is cut the
   same way. The first cut used `--step14` — but the stepped polygons are declared at the
   top of this file as the SIGNATURE BUTTON/TAG SHAPE, and they are worn by controls:
   buttons, chips, tags, the prev/next arrows. A media panel is not a control, and this
   page's reading surfaces already take plain radii (`--card-radius` on the transcript
   card, `--radius` on the dark sheets, `--mrad` on the memory stage). So the tile takes
   `--card-radius`, the same 18px the transcript card takes, because it is the same KIND
   of object: a panel you read, sized in the same hundreds of pixels.
   The controls standing ON it stay stepped — the hover chip at `--step6`, the arrows at
   `--step10` — so the contrast between a soft panel and hard-cornered controls is the
   system working rather than an inconsistency. ===== */
:root{--slide-media: min(82vw, var(--slide-w,380px));}
/* the head and the Drag tell. The tell used to sit BESIDE a two-line head, in the air to
   its right; the head now carries an intro that fills that air, so squeezing the tell in
   next to it put two unrelated things a few pixels apart. It takes its own line instead,
   pushed right and sitting directly above the track — which is what it is pointing at. */
.caro .ch{display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--cell);}
.caro .ch .shead{flex:1 1 100%; margin-bottom:0;}
.caro .ch .hint{flex:none; margin:calc(var(--cell)*1.5) 0 0 auto;}
.track-wrap{overflow:hidden; margin:calc(var(--cell)*2) 0 0; padding:calc(var(--cell)*2) 0; touch-action:pan-y;
  user-select:none; -webkit-user-select:none; position:relative;}
.track-wrap, .track-wrap *{cursor:grab;}
.track-wrap.drag, .track-wrap.drag *{cursor:grabbing !important;}
.track{display:flex; gap:28px; width:max-content; padding:0 max(var(--gutter), calc((100vw - var(--maxw)) / 2)); will-change:transform;}
.slide{flex:0 0 auto; width:var(--slide-media); display:flex; flex-direction:column; align-items:center; text-align:center;}
/* --dm-bg is the clip's own first-frame corner colour, set on the markup: the box is that
   colour from layout to first frame, so a tile never flashes paper and never flashes white */
.slide .csm{position:relative; width:100%; aspect-ratio:1/1; overflow:hidden;
  background:var(--dm-bg,var(--ink)); border-radius:var(--card-radius);}
/* `border-radius:inherit` on the media itself, not `overflow:hidden` on the parent alone:
   a <video> is composited on its own layer and WebKit has never reliably clipped one to an
   ancestor's radius — the corners come back square the moment playback starts. The two
   children are the same box as their parent, so inheriting the radius is exact, not a
   number repeated in three places. */
.slide .csm .dmp, .slide .csm .dmv{position:absolute; inset:0; width:100%; height:100%;
  display:block; object-fit:cover; pointer-events:none; border-radius:inherit;}
/* the poster is not a placeholder to be swapped, it is the floor: it stays lit underneath
   and the video CROSSFADES ONTO IT when playback actually starts (day.js adds .on at the
   `playing` event, not at play() — the difference is one decoded frame, and it is the
   difference between a dissolve and a flash of --dm-bg). With no JS, and under reduced
   motion, the floor is the whole tile and the section reads complete. */
.slide .csm .dmv{opacity:0; transition:opacity .55s ease;}
.slide .csm.on .dmv{opacity:1;}
/* It.114 — the hover glitter's ground: day.js appends this canvas on first hover,
   above the recording, under the chip and the arrows, and never under a hand */
.slide .csm .dmglit{position:absolute; inset:0; width:100%; height:100%; pointer-events:none;}
/* the moment's place in the day. ember-deep is the accent's TEXT tier at 10.5px, and this
   is ASIS naming a thing — which is exactly what .kick is for elsewhere on the page. */
.slide .w{font-family:var(--mono); font-weight:500; font-size:10.5px; line-height:1.5;
  letter-spacing:.18em; text-transform:uppercase; color:var(--ember-deep); margin:20px 0 0;}
.slide .t{font-family:var(--display); font-size:24px; line-height:1.16; color:var(--ink); margin:10px 0 0; max-width:20ch;}
.slide .d{color:var(--ink-soft); font-size:14.5px; line-height:1.55; margin:10px 0 0; max-width:36ch;}
/* hover tell: no scrim — a small ink chip steps up over the media box (pointer devices only).
   It stays deliberately GENERIC. It is decoration that only exists on a fine pointer, so
   nothing a touch reader needs may live in it; what the tile is showing is said by the two
   lines under it, which everybody gets. */
.slide .rc-clip{display:none; position:absolute; left:0; right:0; bottom:14px; z-index:3; overflow:hidden;
  line-height:1; text-align:center; pointer-events:none;}
.slide .rc-i{display:inline-block; transform:translateY(150%); transition:transform .5s steps(5);
  background:var(--ink); color:var(--paper); font-family:var(--mono); font-weight:500; font-size:10.5px;
  letter-spacing:.14em; text-transform:uppercase; padding:9px 14px; clip-path:var(--step6);}
.slide:hover .rc-i{transform:translateY(0);}
@media(hover:hover) and (pointer:fine){
  .slide .rc-clip{display:block;}
}
/* prev / next arrows — keyed off the media's half-height, which for a square tile is the
   same number as its width, so one token serves both and they cannot drift */
.sl-arrow{position:absolute; top:calc(var(--cell)*2 + var(--slide-media)/2); transform:translateY(-50%); z-index:6; width:42px; height:42px;
  border:0; background:var(--ink); color:var(--paper); display:none; align-items:center; justify-content:center; padding:0;
  cursor:pointer; clip-path:var(--step10); transition:background .2s ease, opacity .25s ease;}
  /* NOT `var(--hov)`, deliberately, and v73's E1/E4 hold it there: It.107 unified every
     control on the page onto one clock and LEFT THIS SECTION'S OWN CONTROLS ALONE. The
     number is the same .2s; the point is that the decision to exclude them was made and
     must not be quietly undone by a later hand tidying tokens. */
.sl-arrow.prev{left:16px;} .sl-arrow.next{right:16px;}
.sl-arrow svg{width:18px; height:18px;}
.sl-arrow:hover{background:var(--ember-deep);}
.sl-arrow[disabled]{opacity:0; pointer-events:none;}
@media(hover:hover) and (pointer:fine){ .sl-arrow{display:flex;} }

/* ===== memory — the twelve, NAMED beside one button =====
   the section rests as a head, one control, and the twelve category names filling the
   band to its right. the control is a real <details>/<summary>: with no JS, no WebGL,
   or reduced motion it opens an HONEST GRID of the twelve — photograph, name,
   explanation, all reachable, information parity whole. that grid is also the slider's
   source: memory.js adds .gl and the same twelve <li> become the
   ~/Website_resource/Sliders/4 stage — one portrait plane painted by a WebGL mesh (the
   reference's own shader, ported off three.js), the index huge over its top-left, the
   category outlined at its bottom-right, the line beneath it, long arrows either side.
   nothing here is a panel: the field runs behind the whole stage and the only
   surfaces are the photographs themselves.
   the reference's smooth eases — power4.inOut on the words, sine.out on the
   plane's arrival, an elastic settle on the enlarge — are user-directed, as this
   section's have been since It.32, and the plate's flight is the same exception in the
   same curve. the stepped signature holds everywhere else, including this section's
   own arrow/minus and the open button's pixel flicker. */
.mstage{--mrad:clamp(14px,1.4vw,20px);}
/* THE ROW IS TWO COLUMNS AND TWO GRID ROWS, and the column is auto-sized BY the button:
   nothing here is measured in JS, so a late webfont or a longer label re-lays itself.
   the twelve sit in column 2 of row 1, beside the button. the stage takes the whole of
   row 2, because it has to open below BOTH of them — which is exactly why it is a
   SIBLING of the <details> and not a child (a closed <details> hides every child but
   its summary, so the stage could not have stayed inside and let the twelve out).
   the row also owns the block's top spacing, so the button's own margin cannot collapse
   through the positioning context the travelling plate is measured against. */
.morow{position:relative; margin:calc(var(--cell)*3) 0 0;
  display:grid; grid-template-columns:auto minmax(0,1fr);
  column-gap:calc(var(--cell)*2.5); align-items:start;}
.mopen{grid-column:1; grid-row:1; margin:0;}
.morow .mrow{grid-column:2; grid-row:1;}
.morow .mstage{grid-column:1/-1; grid-row:2;}
.mopen:not([open]) ~ .mstage{display:none;}
.mobtn{list-style:none; cursor:pointer; white-space:nowrap;}
/* It.107: the `background .18s steps(3)` override is GONE, so this reads `.btn`'s own
   `background var(--hov)` like every other instance of the site's one button. It.63c's
   argument for stepping it — the fill is a STATE, and the page states states in three
   steps — is still true of the STATE (mo-away still moves the orange out to a word); what
   it cannot also govern is the HOVER, which is this button answering a pointer and now
   answers it on the masthead's clock. */
.mobtn::-webkit-details-marker{display:none;}
.mobtn::marker{content:"";}
/* the two labels share ONE grid cell, so the cell is as wide as the longer of them and
   the button never resizes on the swap. `visibility` is the hide, not `opacity`: it
   takes the spare label out of the accessibility tree while it still sizes the cell,
   which is the whole point of stacking them. */
.mow{display:inline-grid; justify-items:center; align-items:center;}   /* INLINE-grid: buttons.js
  wraps a .btn's children in an inline .lbl, and a block-level box in there splits it into
  anonymous blocks and grows the button 12.5px taller */
.mow > span{grid-area:1/1; visibility:hidden;}
.mow .mow-s,.morow.mo-on .mow .mow-c{visibility:visible;}
.morow.mo-on .mow .mow-s{visibility:hidden;}
/* the counterweight: while the plate is away the button takes --btn-o's own ink, and
   hovering it offers the ember back — which is literally what a click then does */
.morow.mo-away .mobtn{background:var(--ink);}
@media(hover:hover) and (pointer:fine){ .morow.mo-away .mobtn:hover{background:var(--ember-deep);} }
/* the disclosure tell is a pixel arrow that loses its head when open, leaving the bar
   as a minus — stepped, like every other state change that is not the slider's motion.
   the head is a 4.5px square wearing two of its own borders, rotated a quarter turn:
   one --step-family mark, no glyph, and it points along the row at the twelve. */
.moi{position:relative; display:inline-block; width:10px; height:9px; margin-left:12px; flex:none;}
.moi::before,.moi::after{content:""; position:absolute;}
.moi::before{left:0; top:4px; width:10px; height:1px; background:currentColor;}
.moi::after{right:0; top:1.75px; width:4.5px; height:4.5px; transform:rotate(45deg);
  border-top:1px solid currentColor; border-right:1px solid currentColor;
  transition:opacity .18s steps(2);}
.morow.mo-on .moi::after{opacity:0;}

/* ===== the travelling plate — the whole of the open gesture in one box.

   nothing is chosen at rest, so at rest this does not exist: the only ember in the row
   is the button's own fill. picking a category flies THAT fill out of the button and
   onto the word, and the button drops to ink behind it. the plate is not a decoration
   of the chosen chip, it IS the chosen chip's plate in transit — same --ember-deep,
   same stepped silhouette — so when it lands the chip's own ::before takes over
   pixel-identical and this hides in the same frame, with nothing to cross-fade.

   the two ends do not wear the same corner: a .btn is cut at --step10 and a chip at
   --step6, and both are 21-point polygons, so the corner steps INTERPOLATE down the
   flight. the box shrinks and its own notches shrink with it.

   the curve is the section's own power4.inOut. the size trails the position by 60ms,
   which is the whole of the character: the box leaves before it has finished becoming
   a word-sized box, and arrives having just settled into one.

   the TRAVEL is a transform, which is the composited half and by far the longer move.
   the SIZE is width/height, which is not — deliberately. a transform scale would carry
   the stepped corner with it and the notches would arrive the wrong size, which breaks
   the one thing the whole effect rests on: both ends have to be pixel-identical to the
   plate they stand in for, or the hand-off shows. this is one absolutely-positioned
   leaf with no children and no siblings depending on it, so the layout it dirties is
   its own box and nothing else on the page reflows. ===== */
.mpuck{position:absolute; left:0; top:0; width:0; height:0; z-index:3; opacity:0;
  background:var(--ember-deep); clip-path:var(--step6); pointer-events:none;
  transform:translate3d(var(--pkx,0px),var(--pky,0px),0);
  transition:transform .58s cubic-bezier(.77,0,.175,1),
    width .52s cubic-bezier(.77,0,.175,1) .06s, height .52s cubic-bezier(.77,0,.175,1) .06s,
    clip-path .52s cubic-bezier(.77,0,.175,1) .06s;}
.mpuck.on{opacity:1;}

/* ---- the plate CARRIES THE TYPE, and its own edge is what reveals it ----

   an opaque box flying over a word covers it, and a word that is only re-coloured when
   the box has stopped arrives in a single frame: it reads as a glitch, because nothing
   moved it there. so the plate carries a COPY of the type at each end of its flight —
   the control it left and the word it is going to — each parked at its own place in the
   row, each clipped by the plate.

   the copy holds still. the transform here is the plate's own, NEGATED, so along one
   shared curve the two cancel exactly at every instant: the plate travels at
   a + f(t)(b - a) and the copy at (wx - that), which is wx for all t. it is not
   approximate and it needs no frame loop — it is the same transition read backwards.

   what the eye gets is a WIPE. the departing label is uncovered, letter by letter, as
   the plate slides off it, and the arriving word is uncovered the same way as the plate
   slides on. no fade, no pop, and the colour of a word never changes anywhere it can be
   seen — which is the whole complaint, answered at its cause rather than smoothed over.

   the copies are cloned WHOLE, so their type is right by construction rather than by
   being described twice. they carry no fill: the plate under them is the fill. ---- */
.mpin{position:absolute; left:0; top:0; white-space:nowrap; pointer-events:none;
  transform:translate3d(calc(var(--wx,0px) - var(--pkx,0px)), calc(var(--wy,0px) - var(--pky,0px)), 0);
  transition:transform .58s cubic-bezier(.77,0,.175,1);}
.mpin *{pointer-events:none;}
/* the copy is whatever was cloned — a summary or a chip — so it is addressed as the pin's
   own child rather than by either name. a `.mpin` descendant rule naming the chip class
   would ALSO be the sheet's first declaration of that class, and v29 looks it up by first
   match: a rule about a flying copy would answer a question about the real chips and read
   as two era-red regressions that never happened.
   AND THE SAME TRAP CATCHES THE COMMENT: v29 reads raw CSS, comments included, so naming
   that class here with its brace attached fails the check this note is explaining. Say the
   class name without one. */
.mpin > *{opacity:1; transform:none; transition:none; background:transparent;}
.mpin .mtile::before{display:none;}     /* insurance: a cloned chip is always unpressed */
.mpin .mtn{color:var(--paper);}
/* while a plate is in the air the chips swap state INSTANTLY, because every one of those
   swaps happens under it. stepped is the page's grammar for a change you can see; this
   one you cannot, and a .18s ramp here only leaks two muddy half-colours out from under
   the moving edge. the class comes off a frame after the landing, never in the same
   style-change event, or the swap it is protecting transitions after all. */
.morow.mo-fly .mtile::before,.morow.mo-fly .mtn{transition:none;}

/* ===== the physics line — assets/js/mline.js, built to ~/Website_resource/Physics Effects/2.

   #memory NO LONGER CARRIES ONE (user direction): the band right of the button now
   holds the twelve names themselves, which is the thing the line was ruling toward.
   the engine and these base rules stay in force for #why and #trust, whose instances
   are unchanged — mline.js needed no edit, and `data-anchor` is now an opt-in with no
   taker, kept because it is the generic half of the placement contract.

   the geometry is the reference's, unit for unit: a 1px-tall box, a 500-tall svg pulled
   up 250 so the path's y=250 IS the line, and a hit strip centred on it that GROWS on
   hover so a drag cannot slip out of its own target. the box does NOT bound the pull on
   its own — movementY is per event, so one fast flick posts far more than the box is
   tall; MAX_PULL in mline.js is what actually holds the bow inside the air it is given,
   and the two numbers are set against each other per instance. ===== */
.mline{position:absolute; height:1px; pointer-events:none; left:0; right:0; top:0;
  --grab:20px;        /* at rest: the reference's own 40px strip, centred */
  --grab-on:60px;     /* engaged: as far as the air around THIS line allows, both ways */
  --pull:88px;        /* and how far it may be loaded; mline.js reads it per instance */
  --mlw:2.5px;        /* weight and colour are per-instance too: a line answers to the */
  --mlc:var(--ink);}  /* section it rules, and the two sections are not alike */
.mline-svg{position:absolute; left:0; top:-250px; width:100%; height:500px; display:block; overflow:visible;}
.mline-path{fill:none; stroke:var(--mlc); stroke-width:var(--mlw); transition:stroke .18s steps(3);}
/* the hover tell is the page's own, not a new one: --ember-deep in three steps is the
   answer .ltile:hover .ln1 and .mh-nav a:hover already give a cursor, and It.79 gave
   the same one to #why's copy. the target is the hit strip, so the line is warm exactly
   while it is under the hand and cools on release — the swing that follows plays out in
   ink, which is the honest reading: the colour says "you are holding this", not "this
   is moving". */
.mline-hit:hover ~ .mline-svg .mline-path{stroke:var(--ember-deep);}
.mline-hit{position:absolute; left:0; width:100%; display:block; pointer-events:auto;
  height:calc(var(--grab)*2); top:calc(var(--grab)*-1);}
.mline-hit:hover{height:calc(var(--grab-on)*2); top:calc(var(--grab-on)*-1);}

/* the air above the stage is a CHILD, not padding: the open/close tween runs on this
   box's height, and with border-box a padded box bottoms out at its own padding — so
   the collapse used to stop 42px short and the rest went in one snap when the
   [open] attribute came off. as a block child it is inside the clip and the close
   reaches a true zero. */
.mstage{overflow:hidden;}
.mstage::before{content:""; display:block; height:calc(var(--cell)*3.5);}
.mfoot{margin:calc(var(--cell)*3.5) 0 0;}
/* the second way out. the <summary> is the first, and it is the only one that works
   without JS — so this one waits for memory.js to say it can do anything */
.mshut{display:none; margin:calc(var(--cell)*3) 0 0; text-align:center;}
.mstage.js .mshut{display:block;}
.mclose{appearance:none; background:transparent; border:0; padding:8px 4px; cursor:pointer;
  font-family:var(--mono); font-weight:500; font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--muted); transition:color var(--hov);}
.mclose:hover{color:var(--ember-deep);}

/* ---- the honest grid: what the button opens when the slider cannot run ---- */
.mfigs{list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(196px,1fr)); gap:calc(var(--cell)*3) calc(var(--cell)*2.5);}
.mfx{font-family:var(--mono); font-weight:500; font-size:10.5px; line-height:1.5; letter-spacing:.18em;
  color:var(--ember-deep); margin:0 0 8px;}
.mfimg{display:block; width:100%; height:auto; aspect-ratio:1; object-fit:cover; border-radius:var(--mrad);}
.mfn{margin:14px 0 6px; font-family:var(--display); font-weight:400; font-size:clamp(20px,1.8vw,26px);
  line-height:1.1; color:var(--ink);}
.mfd{margin:0; color:var(--ink-soft); font-size:clamp(14.5px,1.05vw,16px); line-height:1.55;}
.mgl,.mplane,.mnav{display:none;}

/* ---- the twelve names, standing in the band right of the button ----
   they are built by memory.js, so with no JS at all this container is empty and the
   section degrades to exactly what it always was: a button and the honest grid behind
   it. `mo-live` is memory.js saying the twelve exist and can be picked.
   at rest none of them is chosen — the orange lives on the button until a pick sends
   it out — so the row reads as twelve quiet words, which is what lets one of them
   going ember mean something. */
.mrow{display:none;}
.morow.mo-live .mrow{display:flex; flex-wrap:wrap; align-items:baseline;
  gap:2px clamp(2px,.45vw,8px); margin:0 0 0 calc(var(--cell)*-1);}   /* the negative margin
    pulls the first word's own padding back so its INK, not its box, starts the band */

/* ---- the slider. everything below waits on .gl, which memory.js adds only when
   there is a WebGL context to draw into and motion is welcome ---- */
/* the plane is SQUARE, not the reference's 3:4 card: these twelve photographs are
   720x720, so a square stage is the only one that shows all of every one of them —
   the shader's cover then resolves to 1:1 and crops nothing (It.63b, user direction) */
.mstage.gl{--mpw:clamp(250px,25vw,404px); --mph:var(--mpw);
  --mixs:clamp(78px,11vw,176px);      /* the index — display art, so the accent's display tier */
  --mfns:clamp(30px,4.7vw,70px);      /* the outlined category — sized so the longest
                                         of the twelve, Commitments, still clears the
                                         stage's right edge at every width (worst case
                                         ~1405px, where --maxw is pinned and the plane
                                         is still growing: 5.7px to spare) */
  --marm:clamp(52px,6.5vw,132px);     /* how far the arrows stand off the plane */
  --mtxh:190px;}                      /* the tallest category block; memory.js measures it */
/* the twelve chips are WORDS, not thumbnails (It.63b, user direction): the photographs
   belong on the stage, and twelve of them above it competed with the one being read.
   the DESIGN is the page's own signature at index scale (It.63c, user direction) — the
   pixel-stepped silhouette the buttons wear, cut here at the small --step6 corner. at
   rest there is no plate at all, so the ember field runs clean behind twelve quiet
   words; the hand lays a whisper of ink under one; the chosen one takes the solid
   ember-deep plate and turns its word to paper. one control language, three states,
   every change stepped — .btn's own grammar, not a new one.
   .mtile keeps its name AND its base-rule contract (transparent, borderless,
   unrounded, shadowless) so field.js frames them on hover exactly as it framed the
   strip, and the plate lives on a pseudo-element behind the word. */
.mtile{appearance:none; position:relative; z-index:0; flex:0 0 auto;
  background:transparent; border:0; margin:0; padding:11px 12px 12px; cursor:pointer;
  display:inline-flex; align-items:baseline;}   /* ~43px tall: the field's hover frame is a
  10px-cell ring, and under about four cells of height it reads as a band instead of a frame */
.mtile::before{content:""; position:absolute; inset:0; z-index:-1; clip-path:var(--step6);
  background:var(--ink); opacity:0;
  transition:opacity var(--hov), background-color var(--hov);}
.mtn{font-family:var(--serif); font-size:clamp(13.5px,1.05vw,16.5px); line-height:1.25;
  color:var(--ink-soft); white-space:nowrap; transition:color var(--hov);}
@media(hover:hover){
  .mtile:hover::before{opacity:.07;}
  .mtile:hover .mtn{color:var(--ink);}
}
/* the chosen one: the plate solid, the word in paper — 4.90:1 either way round */
.mtile[aria-pressed="true"]::before{background:var(--ember-deep); opacity:1;}
.mtile[aria-pressed="true"] .mtn{color:var(--paper);}
.mtile[aria-pressed="true"]:hover::before{opacity:1;}

/* the stage keeps the reference's proportion — its picture stands about two thirds
   of the height it floats in — which is also the room the enlarge needs to be worth
   doing: at 1024 the photograph grows about 1.6x before it meets the edge */
.gl .mview{position:relative; height:max(calc(var(--mph) + var(--cell)*9), min(60vh,640px));}
/* the mesh's canvas spans the whole stage, not the plane: the enlarge grows the
   PLANE inside a fixed backing store, so the photograph is never a scaled raster */
.gl .mgl{display:block; position:absolute; inset:0; width:100%; height:100%; pointer-events:none;}
/* the plane is a button — an invisible hit region exactly the size of the photograph
   the mesh is drawing beneath it. clicking enlarges; clicking again hands it back */
.gl .mplane{display:block; position:absolute; left:50%; top:50%; z-index:2;
  width:calc(var(--mpw)*var(--z,1)); height:calc(var(--mph)*var(--z,1)); transform:translate(-50%,-50%);
  appearance:none; background:transparent; border:0; padding:0; cursor:zoom-in; touch-action:pan-y;}
.gl .mplane.big{cursor:zoom-out;}
.gl .mfigs{position:absolute; left:50%; top:50%; z-index:3; display:block;
  width:var(--mpw); height:var(--mph); margin:0; transform:translate(-50%,-50%);
  pointer-events:none; user-select:none; -webkit-user-select:none;}
.gl .mfig{position:absolute; inset:0; visibility:hidden;}
.gl .mfig.is-cur,.gl .mfig.out-next,.gl .mfig.out-prev{visibility:visible;}
.gl .mfimg{display:none;}
/* the index: solid, huge, hung off the plane's top-left corner on the reference's
   own offsets (translate(-60%,-20%), line-height 80%), in the accent's display tier */
.gl .mfx{position:absolute; left:0; top:0; margin:0; overflow:hidden;
  transform:translate(-60%,-20%); font-family:var(--display); font-weight:400;
  font-size:var(--mixs); line-height:.8; letter-spacing:0; color:var(--ember);}
.gl .mftxt{position:absolute; bottom:0; left:105%; width:max-content;}
/* the category: SOLID EMBER (It.63b, user direction — it was the reference's hollow
   outline in ink, which only ever read where it stood on paper). it pulls back over
   the photograph's right edge so word and picture still interlock — 26%, not the
   reference's 35%, which is what keeps the longest of the twelve inside the stage.
   line-height is 1.3, not the reference's .8: its titles are five-letter all-caps
   and ours are not, and a clipped box has to keep the j in Projects, the g in
   Logistics and the y in Identity whole. --ember is the accent's DISPLAY tier and
   this is display type at 30px and up, so the tier holds. */
.gl .mfn{margin:0; overflow:hidden; font-family:var(--display); font-weight:400;
  font-size:var(--mfns); line-height:1.3; white-space:nowrap; transform:translateX(-26%);
  color:var(--ember);}
.gl .mfd{width:min(34ch,22vw); margin:calc(var(--cell)*1.5) 0 0;
  color:var(--ink-soft); font-size:clamp(14.5px,1.05vw,16.5px); line-height:1.55;}
/* every traveller is masked by its own box: the letters climb through .mfx/.mfn,
   the prose lines through .mln — parent clips, child moves, nothing else needed */
.gl .mfx .l,.gl .mfn .l{display:inline-block; will-change:transform;}
.gl .mln{display:block; overflow:hidden;}
.gl .mln > span{display:block; will-change:transform;}
/* the arrows: long and thin, lengthening toward their own direction on hover */
.gl .mnav{display:block;}
.gl .marw{position:absolute; top:50%; z-index:4; width:clamp(38px,3.4vw,52px); height:20px;
  appearance:none; background:transparent; border:0; padding:0; cursor:pointer;
  transform:translateY(-50%); color:var(--ink); transition:color var(--hov);}   /* It.107: the
    colour was changing on hover with NO transition on the base rule, so it snapped there and
    snapped back — an asymmetric return is the one thing the direction names outright */
.gl .marw svg{display:block; width:100%; height:100%; fill:currentColor;
  transition:transform var(--hov); transform:translateX(var(--tx,0px));}
.gl .marw-p{left:calc(50% - var(--mpw)/2 - var(--marm));}
.gl .marw-n{right:calc(50% - var(--mpw)/2 - var(--marm));}
@media(hover:hover){
  .gl .marw:hover{color:var(--ember-deep);}
  .gl .marw-p:hover svg{--tx:-9px;} .gl .marw-n:hover svg{--tx:9px;}
}
.gl .marw:active svg{--tx:0px;}

/* ---- arriving. the reference's opening timeline: the plane rises from below the
   stage on a sine settle while the mesh rides its own travelling wave, and a beat
   and a quarter later the words climb in and the arrows draw out to the sides ---- */
.gl .mgl{transform:translateY(150%); transition:transform 1.25s cubic-bezier(.39,.575,.565,1);}
.gl .mplane{transform:translate(-50%,-50%) translateY(150%); transition:transform 1.25s cubic-bezier(.39,.575,.565,1);}
.gl.lit .mgl{transform:none;}
.gl.lit .mplane{transform:translate(-50%,-50%);}
.gl .mshut{opacity:0; transition:opacity .6s ease .9s;}
.gl.lit .mshut{opacity:1;}
/* the twelve arrive with the ROW, not with the stage: they are on the page before
   anything is opened, so their entrance is the section's own reveal, dealt left to
   right so the band writes itself out from the button. .morow carries `reveal`, so
   `.in` is reveals.js's word for "this block has arrived" and nothing new is wired. */
.morow .mtile{opacity:0; transform:translateY(10px);
  transition:opacity .5s ease var(--cd,0s), transform .5s cubic-bezier(.165,.84,.44,1) var(--cd,0s);}
.morow.in .mtile{opacity:1; transform:none;}

/* ---- the word states. one parked position per direction, one resting position,
   one leaving position; memory.js only ever swaps the class and sets --d ---- */
.gl .mfx .l,.gl .mfn .l{transform:translateY(120%) rotate(-3deg);}
.gl .mln > span{transform:translateY(100%);}
.gl .mfig.pre-prev .l{transform:translateY(-120%) rotate(3deg);}
.gl .mfig.pre-prev .mln > span{transform:translateY(-100%);}
.gl .mfig.is-cur .l,.gl .mfig.is-cur .mln > span{transform:none;
  transition:transform 1s cubic-bezier(.77,0,.175,1) var(--d,0s);}
.gl .mfig.out-next .l{transform:translateY(-120%) rotate(3deg);}
.gl .mfig.out-next .mln > span{transform:translateY(-100%);}
.gl .mfig.out-prev .l{transform:translateY(120%) rotate(-3deg);}
.gl .mfig.out-prev .mln > span{transform:translateY(100%);}
.gl .mfig.out-next .l,.gl .mfig.out-next .mln > span,
.gl .mfig.out-prev .l,.gl .mfig.out-prev .mln > span{transition:transform 1s cubic-bezier(.77,0,.175,1) var(--d,0s);}
/* the arrows draw out from behind the plane and retreat the same way — the
   reference tweens x, so this does too, on the transform it already carries */
.gl .marw{opacity:0;}
.gl .marw-p{transform:translateY(-50%) translateX(100px);}
.gl .marw-n{transform:translateY(-50%) translateX(-100px);}
.gl.words .marw-p,.gl.words .marw-n{opacity:1; transform:translateY(-50%);
  transition:opacity 1s cubic-bezier(.77,0,.175,1), transform 1s cubic-bezier(.77,0,.175,1);}
/* enlarged: the words step aside and the arrows go with them, exactly as the
   reference's fullscreen does */
.gl.zoom .mfig.is-cur .l{transform:translateY(-120%) rotate(3deg);}
.gl.zoom .mfig.is-cur .mln > span{transform:translateY(-100%);}
.gl.zoom .marw-p{opacity:0; pointer-events:none; transform:translateY(-50%) translateX(-100px);}
.gl.zoom .marw-n{opacity:0; pointer-events:none; transform:translateY(-50%) translateX(100px);}

/* ---- narrow: the same stage, stacked. the category and its line move under the
   photograph and centre; the arrows take the floor; the chips become a strip ---- */
@media(max-width:899.98px){
  .mstage.gl{--mpw:min(66vw,320px); --mixs:clamp(56px,15vw,96px); --mfns:clamp(30px,7.4vw,46px); --marm:0px;}
  /* stacked: the button takes its own line and the twelve fill the measure under it.
     they do NOT become a scrolling strip — they are the section's control now, and
     nine of twelve hidden off the right edge is not a control, it is a rumour. */
  .morow{grid-template-columns:minmax(0,1fr); row-gap:calc(var(--cell)*2);}
  .mopen{grid-column:1; grid-row:1;}
  .morow .mrow{grid-column:1; grid-row:2;}
  .morow .mstage{grid-column:1; grid-row:3;}
  /* the chip padding is NOT reduced here on purpose: at the narrow type size that same
     11/12 lands the chip on 40px, which is the four cells the field's hover ring needs
     to read as a frame instead of a band — and it is the touch target besides */
  .gl .mview{height:calc(var(--mph) + var(--mtxh) + var(--cell)*9);}
  .gl .mplane,.gl .mfigs{top:calc(var(--cell)*2 + var(--mph)/2);}
  /* the block is centred on the plane but measured against the STAGE, so on a 320px
     screen it stops at the gutter instead of a hair past it */
  .gl .mftxt{position:absolute; bottom:auto; top:calc(100% + var(--cell)*2.5); left:50%;
    width:min(46ch,calc(100vw - var(--gutter)*2 - 12px)); transform:translateX(-50%); text-align:center;}
  .gl .mfn{transform:none; white-space:normal; line-height:1.24;}
  .gl .mfd{width:100%; margin-top:calc(var(--cell)*1.25); font-size:15px;}
  /* the index still hangs off the corner, but only as far as the smallest phone can
     hold it — at 320px a -34% overhang lost 8px of the 0 to the stage's own clip */
  .gl .mfx{transform:translate(-22%,-36%);}
  .gl .marw{top:auto; bottom:0;}
  .gl .marw-p{left:calc(50% - var(--cell)*7);} .gl .marw-n{right:calc(50% - var(--cell)*7);}
  .gl .marw-p,.gl .marw-n{transform:translateY(60px);}
  .gl.words .marw-p,.gl.words .marw-n{transform:none;}
  .gl.zoom .marw-p,.gl.zoom .marw-n{transform:translateY(60px);}
}
@media(max-width:479.98px){
  .mstage.gl{--mpw:min(74vw,290px);}
}
/* reduced motion takes the honest grid instead — memory.js never adds .gl, so none
   of the travel above is ever in the document. the arrow/minus keeps its two steps.
   the twelve are still the control, so they are simply THERE, unrevealed and unmoved,
   and the plate does not fly: memory.js parks it on the chosen word in one step. */
@media(prefers-reduced-motion:reduce){
  .mtn,.mtile::before,.mclose,.mobtn{transition:none;}
  .morow .mtile{opacity:1; transform:none; transition:none;}
  .mpuck{transition:none;}
}

/* ===== why — the page's one long-form argument, and it is made of nothing but
   words. no art, no card, no plate: the claim, three paragraphs that get shorter
   as they go, and a hairline band stating the calendar sync. the hierarchy is
   carried by SIZE and INK alone — lede at 22px in full ink, the qualifications
   at 18 in --ink-soft, the last line shortest — which is the only depth a
   text-only section is allowed on a page with no panels. ===== */
/* the kicker is a label across the top of the SECTION, not the first line of the
   title's column — it hugs its own text so the cursor's data-safe rect matches the
   glyphs rather than the whole row, exactly as .shead .kick did for it before */
#why .kick{width:fit-content; margin-bottom:14px;}
.why-body{max-width:56ch;}
.why-body p{margin:0 0 1.05em; color:var(--ink-soft); font-size:clamp(16px,1.15vw,18px); line-height:1.66;}
.why-body p:last-child{margin-bottom:0;}
/* the lede is the section's answer and carries the weight of one: a real step up
   in size and to full ink, on Newsreader's own 500 rather than a synthesised one */
.why-body .why-lede{font-size:clamp(18.5px,1.5vw,23px); line-height:1.5; color:var(--ink);
  font-weight:500; font-synthesis-weight:none; margin-bottom:1.15em; max-width:34ch;}
/* body copy takes the accent's TEXT tier (4.90:1, AA at any size) at the family's
   real 500 — --ember is legal from 18px up and stays on the h2 */
.why-body .accent, .why-sync-line .accent{color:var(--ember-deep); font-weight:500; font-synthesis-weight:none;}

/* It.78: the rectangle is gone by direction. `#why .wrap` carries no rule of its own
   again, so the section falls back to the shared page measure — and because the -11px
   padding compensation retired WITH the border it was compensating for, the copy stays
   on exactly the spine it was already on. the section is open paper once more, which
   is what every other section on this page is. */

/* the calendar sync sits under the title, in the title's own column, and the three
   platforms sit under it as the connected OPTIONS they are. the marks are the
   companies' own — Apple's apple, Google's G, Microsoft's four squares (It.87b) — on
   the line of their mono label. It.87 gives each of the three a capsule of its own; the
   `never on a plate` this line used to end with is superseded there, and the face the
   capsule takes is the reason that supersession costs the page nothing. */
/* It.81: the hairline that ruled the calendar band off from the title IS the line now —
   the same engine, the same reference, the same hover tell as #memory's.

   It.81b: and it keeps its OWN look — 1px of --line, the hairline it replaced, not
   #memory's 2.5px of ink. the first cut matched the weights on the argument that a
   pullable rule should be recognisable as pullable; shipped, that put a solid black bar
   under a 52px display title in a section with no other heavy element, and it read as
   the title's underline rather than the band's divider. **the affordance is carried by
   the hover tell, not by the weight** — the rule is quiet until a hand is on it and
   then it is --ember-deep, which is the moment the physics needs to be legible anyway.
   the two lines are the same OBJECT; they are not the same MARK.

   the border is GONE and its pixel moved into the padding, so the box below it does not
   shift by even one — a border occupies layout, an absolutely positioned line does not.
   the air is far tighter than #memory's: 33px up to the 52px title (or, below 1280, to
   the argument) and 25px down to the claim. so this instance runs a smaller band and a
   smaller pull — 30 and 44, against 60 and 88 — which keeps the 22px bow off the copy
   and keeps the grab strip out of .why-sync-line's glyphs, where the scramble lives. */
.why-sync{position:relative;
  margin-top:calc(var(--cell)*2.75); padding-top:calc(var(--cell)*2 + 1px);}
/* the half pixel is not a fudge: a 1px BORDER occupied y 0..1 from the box edge, while a
   1px STROKE centred on that edge straddles it and sits half a pixel high. this puts the
   line back exactly where the hairline drew. */
#why .mline{--grab-on:30px; --pull:44px; --mlw:1px; --mlc:var(--line); top:0.5px;}
.why-sync-line{margin:0; max-width:46ch; color:var(--ink-soft); font-size:clamp(15.5px,1.05vw,17px); line-height:1.62;}
/* the three platforms share ONE row. a grid of three auto columns rather than a
   wrapping flex row, because wrapping is exactly the failure to avoid here: a row
   that breaks 2-then-1 reads as an accident rather than a list, and three explicit
   columns cannot break. the tight width is 1280, where the two-column split first bites
   and the title's column is 526px — measured, not estimated, and it is the number the
   whole capsule was sized against: see the note on the geometry below. below 640, where
   three of them genuinely cannot share a phone's measure lying down, the columns go equal
   and the capsule STANDS UP instead of the row breaking. one row at every width, which
   is the direction. */
.synclist{list-style:none; margin:calc(var(--cell)*2) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(3,auto); justify-content:start;
  align-items:stretch; gap:var(--cell);}
/* It.87 (user direction): each platform is its own CAPSULE. It.77 set the three naked on
   the paper — mark, name, nothing else — on the argument that a text-only section may not
   grow plates; the direction supersedes that, and what the capsule must NOT become is the
   UI panel that argument was protecting against. so the face is built out of the vocabulary
   this page already owns for a small lifted thing (`.mem-c`, `.cv`): a TRANSLUCENT paper
   face, one hairline, an inset top highlight and a two-layer cast — a contact shade and a
   short soft lift, never one heavy drop, which is the difference between a token laid on
   paper and a control. it is spelled rgba rather than var(--paper-2) because translucent
   is what it IS: the page's dot-grid runs on under all three, and that is also what keeps
   the section clear of the opaque-panel rule that has held since v3/I2.

   ONE geometry for all three — same height floor, same padding, same corner, same
   hairline — so the only thing that differs across the row is the word. and because the
   word is set in MONO, the three widths differ by exactly one and two character cells
   (7.21px at this size): that is the typeface's own grid, not rag, which is why the row
   can hug its content instead of being forced to three equal columns it would have to
   pad out unevenly from the inside.

   THE HEIGHT IS 3 CELLS, and that is the whole sizing argument. the first cut ran 42px
   with the 22px mark and 11.5px name It.77 left on the paper, and it was wrong twice
   over: measured, the row came to 549px against a 526px title column at 1280 and hung
   22px out into the column gutter; and at 42px it was the tallest small element on a
   page whose chips are 22 (`.pxctl .lbl`), 24 (`.cv-scap`) and 36 (`.toplink`). 36 is
   `.toplink`'s own height and this page's chip height, the row comes to 510 with 16px to
   spare in the tight column, and the capsule stops reading like a button. a thing that
   does not fit is usually a thing sized by habit rather than by the page.

   no hover, no transition, no tell. nothing in this section is clickable and a capsule
   that answers a hand claims to be — these are statements of fact wearing a frame. */
.synclist li{display:flex; align-items:center; gap:7px;
  min-height:calc(var(--cell)*3); padding:0 14px 0 11px;
  border:1px solid var(--line); border-radius:999px;
  background:linear-gradient(180deg, rgba(255,253,247,.92), rgba(255,253,247,.68));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.8),
             0 1px 1px rgba(34,28,20,.03),
             0 5px 12px -9px rgba(34,28,20,.17);}
/* ONE box, three marks that are not one shape, and the sizing lives in the markup where
   each transform can answer for itself. the box comes down from 22 with the capsule: 20
   against a 36px pill is a 0.56 mark-to-box ratio, where 22 was 0.61 and filled it.
   It.87b: with Microsoft's four squares in place of the Outlook lockup all three are now
   COMPANY marks, and the set is balanced by WEIGHT rather than by measurement — a solid
   square is held to 0.86 of what an open ring gets, because at equal bounds a solid form
   reads larger. the happy side effect is that the ink now starts within 1.2px of the same
   inset in all three capsules, where the wide lockup sat 2.5px in from the other two. */
.synclogo{width:20px; height:20px; display:block; flex:none;}
/* 11px, down half a point with the capsule: it sits between the section's own kicker
   (10.5) and the body, which is where a label inside a frame belongs — and against a
   20px mark it holds the same optical weight 11.5 held against 22. */
.syncname{font-family:var(--mono); font-weight:500; font-size:11px; letter-spacing:.055em;
  line-height:1; color:var(--ink); white-space:nowrap;}
/* ---- ≤600 the capsule STANDS UP, and it is arithmetic that says so rather than taste.
   lying down, one capsule holding `Calendar` (57.6px in this mono, which is what
   `Outlook Calendar` breaks to) beside its 20px mark, plus its own gap, padding and
   border, wants 111.6px of column; three of those and their gaps want 359 on a 360px
   phone, which has 312 to give. the same content stood upright wants 75.6 and three fit
   in 251 with 61px to spare. so the row survives — which is the direction — by turning
   the element, never by shrinking the mark or the word. measured, lying down stops
   fitting at 558px of viewport; the turn is taken at 600 so it is never taken at the
   edge of failing.
   the corner turns with it. 999px is the shape of a HORIZONTAL label; a stadium stood on
   end stops reading as one and starts reading as a blob, so here the capsule takes the
   page's own card radius and becomes a small tile. same face, same hairline, same cast,
   one property different. ---- */
@media(max-width:600px){
  /* the cap is what stops the turn producing SLABS. equal columns are right — three
     upright tiles of different widths would read as an accident — but 1fr stretches to
     whatever is there, and at 600 that was a 178px-wide tile holding a 20px mark over a
     115px word, which is mostly air. 420 is the row's own natural width: a tile wants
     133 (the longest name, its 8px sides and its border) and three of those plus two
     gaps come to 417. so the tiles hold 134 from 480 up to 600 and only start giving
     below that, where the phone is the thing doing the deciding. */
  .synclist{grid-template-columns:repeat(3,minmax(0,1fr)); gap:calc(var(--cell)*.75); max-width:420px;}
  .synclist li{flex-direction:column; justify-content:center; gap:8px;
    padding:var(--cell) 8px; border-radius:var(--card-radius);}
  .syncname{white-space:normal; text-align:center; line-height:1.3;}   /* "Outlook" over "Calendar" — the row survives, the name gives */
}
/* and they all give at the same width. measured, `Outlook Calendar` stops fitting on
   one line at 519 and `Apple Calendar` not until 474 — a 45px band where one name is
   set on one line beside two on two, which reads as a mistake. capping the name forces
   the break at the same word for all three, and 9.5ch clears `Calendar` (60.2px) while
   `Apple Calendar` (105px) cannot fit it. */
@media(max-width:520px){ .syncname{max-width:9.5ch;} }

/* the two-column composition, stated after the base rules it overrides (a wide
   override placed above its base silently loses the cascade — this file has been
   bitten three times). the ROWS are the whole point of the arrangement: the kicker
   takes a full-width row of its own, so the title and the argument both begin on
   row 2 and their first lines start on the same line. putting the kicker in the
   title's column instead is what pushed the title a kicker's height below the
   argument, which is the misalignment this replaces. */
@media(min-width:1280px){
  /* .86 rather than an even split, and the reason is typographic before it is
     compositional: the argument reads at ~50ch instead of ~63 (nearer this page's
     own .why-body cap), and the 52px title gets the wider column it is entitled to.
     it also closes most of the trailing rag inside the frame — but rag is quantised
     by line height here, so it is a consequence and never the argument. */
  #why .wrap{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.86fr);
    column-gap:clamp(48px,6vw,110px); align-items:start;}
  #why > .wrap > .kick{grid-column:1 / -1; grid-row:1; margin-bottom:calc(var(--cell)*2.5);}
  #why .shead{grid-column:1; grid-row:2; margin-bottom:0;}
  #why .why-body{grid-column:2; grid-row:2 / span 2;}   /* starts on the title's own row */
  /* sharing a row puts the two BOXES level, which is not the same as putting the two
     lines level: Calistoga at 52/1.06 carries far less space above its caps than
     Newsreader at 23/1.5 does. measured, the title's cap-top sat 12px high at 1536
     and 10px at 1280 — and because both sizes are clamps on vw, that difference is a
     near-constant .217–.231 of the title's OWN size across the whole range, so one em
     nudge holds everywhere to within half a pixel. cap-tops, not baselines: a 52px
     title baseline-aligned to 23px copy reads as floating, not as aligned. */
  #why .shead h2{margin-top:.225em;}
  #why .why-sync{grid-column:1; grid-row:3;}
}

/* the proximity scramble — assets/js/scramble.js, built to the reference at
   ~/Website_resource/Text Animations/4. the word never breaks (nowrap) and every
   character keeps the width it was MEASURED at before the split, centred in it,
   so swapping a glyph for a dot cannot move a single line box. the split exists
   only for a hand: with reduced motion, or on a device with no fine pointer, the
   script returns before touching the DOM and these rules match nothing.

   there is deliberately no colour rule here. every colour the effect shows is
   written inline by the engine, one character at a time, because both of them
   are computed live and neither can be spelled as a selector: the DISSOLVED
   glyphs are banded through field.js's own ladder off the hand's proximity and
   the marquee's travelling phase, and the RESOLVED character settles from
   --ember-deep back to whatever these stylesheets authored, in the three
   quantised steps `.ltile:hover .ln1` already uses to answer a cursor.
   the split between them is the contrast contract: the ladder — most of which
   is far under AA on paper — only ever paints a '.' or a ':', which is not
   readable content; a legible character in this section holds --ink, --ink-soft
   or --ember-deep and nothing else. the engine restores by clearing the inline
   colour rather than writing a value back, so the authored cascade below is
   what every character returns to, accents included. */
[data-scramble] .sw{white-space:nowrap;}
[data-scramble] .sc{display:inline-block; text-align:center;}
/* the split copy is hidden from assistive tech and a plain twin carries the
   sentence, so a screen reader never meets the paragraph one character at a
   time; the twin is unselectable so copying the paragraph is not the paragraph twice */
[data-scramble] > .vh{-webkit-user-select:none; user-select:none;}

/* ===== languages — twenty-two names on a turning circle, and one entry beside them =====
   THE SECTION IS TWO THINGS AND THE STYLESHEET BUILDS BOTH OF THEM WITH NO JS AT ALL.
   left, the twenty-two; right, a dictionary entry for whichever one is in hand. with a
   blocked script, a stylesheet that lands before the engine, or a browser that never
   runs it, the left is a wrapped wall of names and the right is the entry for English —
   complete, readable, and the same two columns the live section is. everything circular
   is scoped to `.lstage.live`, which languages.js adds only once it has measured the
   box, so the wheel can never be half-built.
   the entry's shape is the reference's, top to bottom and nothing added: headword,
   pronunciation and language, rule, body. the reference sets all four in ink; the one
   thing that carries the language's own colour is the RULE, because the rule is the only
   part of that layout with no words in it, and a colour that has to stay legible is
   better spent on a 2px bar than on a headword whose contrast then has to be argued. */
/* the air above the circle was the loosest thing in the section (user direction), and it
   comes from THREE places rather than one: the section head's own bottom margin, this gap,
   and the band of empty box inside the wheel's bounding square that the ring only fills
   when the longest name happens to point that way. the first two are cut here; the third
   cannot be cropped without clipping `Bahasa Indonesia` at twelve o'clock, which is why
   the head's margin is overridden for this section instead of the wheel being pulled up
   under it. */
.lstage{display:grid; grid-template-columns:1fr; gap:calc(var(--cell)*2); align-items:start;}
#languages .shead{margin-bottom:calc(var(--cell)*1.25);}
#languages{padding-top:calc(var(--cell)*5.5);}
.lwheel{position:relative;}
.lfield{display:flex; flex-wrap:wrap; gap:10px;}
.ltile{appearance:none; background:transparent; border:0; padding:12px 16px; cursor:pointer;
  display:flex; flex-direction:column; align-items:flex-start; gap:3px; text-align:left;}
.ltile .ln1{font-family:var(--serif-lang); font-size:18px; line-height:1.3; color:var(--ink); transition:color var(--hov);}
.ltile .ln2{font-family:var(--mono); font-size:9.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted);}
.ltile:hover .ln1, .ltile:focus-visible .ln1{color:var(--ember-deep);}

/* THE SECTION IS TWO COLOURS NOW AND IT USED TO BE TWENTY-THREE (It.98, user direction).
   the twenty-two pastels, the `.lc-*` classes that carried them, `--lrgb` and `--lside`
   are DELETED rather than left unread — the solved palette is preserved verbatim at
   design/lang-palette-it97-retired.css, which is this repo's way of retiring a thing
   (seq-it61-removed.html, bonsai-map-gen-it46.py) and is the only reason it is safe to
   take a token nothing reads out of the sheet.
   what replaces it: every name on the wheel is --ink, and the ONE that is not is the one
   the entry beside it is about. that name, the entry's headword and the entry's rule are
   all the same token, --ember-deep, so "the language you are reading" is stated once in
   one colour on both sides of the stage and cannot drift apart.
   --ember-deep AND NOT --ember, and the reason is arithmetic rather than taste: the names
   are solved per layout and run 13px on a phone to about 30px at 1920. --ember is 3.6:1
   and legal only from 18px, so it would be an accent with a floor under a size that has to
   be free to move; --ember-deep is 4.9:1, AA at any size, and holds at both ends of that
   ramp. it is also the accent #how and #trust already wear. */

/* ---- the entry, right column. identical in both states — the engine changes its WORDS,
   never its shape and, since It.98, never its colour either ---- */
.lname{margin:0; font-family:var(--serif-lang); font-weight:500; text-wrap:balance;
  font-size:clamp(34px,4.4vw,54px); line-height:1.04; letter-spacing:-.005em; color:var(--ember-deep);
  -webkit-text-stroke:.012em currentColor;}   /* the same answer the names take: Newsreader ships 400/500 and nothing heavier, so weight past 500 is a browser-synthesised smear and the stroke thickens the real curve instead — identically in all twenty-two scripts */
.lpron{margin:calc(var(--cell)*1) 0 0; font-family:var(--serif); font-size:clamp(15px,1.25vw,19px);
  line-height:1.45; color:var(--ink-soft);}
.lipa{font-style:normal; -webkit-font-feature-settings:"kern"; letter-spacing:.005em;}
.lin{color:var(--muted); margin-left:.6em;}
.lrule{margin:calc(var(--cell)*1.5) 0 0; height:2px; background:var(--ember-deep); transform-origin:0 50%;}
/* THE BODY IS THE DEFINITION, and at It.97 it is a paragraph rather than a greeting (user
   direction). the ref images set their body as plain black text under the rule, at reading
   size and reading measure, and that is exactly what this is: --ink, the page's body face,
   58ch — long enough to stand against a 720px circle and never longer than a line anyone
   reads twice. it is deliberately the ONLY part of this section with no colour in it;
   the wheel is where the colour lives. */
.lbody{margin:calc(var(--cell)*1.5) 0 0; max-width:58ch; color:var(--ink);
  font-family:var(--serif); font-size:clamp(16.5px,1.28vw,19.5px); line-height:1.62;}
/* It.97 REMOVED the ember greeting that stood here from It.1 to It.96 — `Hello, good
   morning.` in twenty-two scripts, and the mono line under it — by direction, replaced by
   the paragraph above. that is the one piece of long-standing bound copy this iteration
   deletes, and it is recorded here rather than quietly dropped. nothing in this section
   carries --ember any more: the accent's only job on this page is chrome, and the twenty-
   two pastels are what this section is coloured with. */
.lhint, .lclose{display:none;}     /* neither means anything until there is a circle to move over */

/* ---- live: the wall becomes the wheel ---- */
/* the two columns split 1.08 / 0.92 rather than evenly: the circle is sized off
   min(width,height) of a SQUARE box, so every pixel it gains is a pixel of type for all
   twenty-two names, while the entry is a text column that stops needing width at about
   its own measure. the crossover to one column is at 1120 and not the page's usual 900,
   because a circle in half of 880px puts the names at 16px — see the packing in
   layout() — and a wheel you cannot read is worse than a wheel you have to scroll to. */
@media(min-width:1120px){ .lstage{grid-template-columns:1.22fr .78fr; gap:calc(var(--cell)*3.5); align-items:center;} }
/* NO PERSPECTIVE, and its absence is the point. It.96 tilted the disc in space toward the
   cursor; It.97 replaced that whole rig with the reference's DRAG, and It.98 takes the
   drag away too (user direction: no click, no drag, no hold) — so there is nothing 3D left
   to project and nothing to grab.
   THE GRAB CURSOR GOES WITH THE GESTURE IT PROMISED. an open hand over a circle that
   answers a bare hover is a cursor telling the reader to do something they do not have to
   do, and the one thing worse than a missing affordance is an affordance for a gesture the
   page no longer has. what is left is the page's own arrow over the paper and `pointer`
   over a name, which is exactly what those two things now mean. */
.lstage.live .lwheel{aspect-ratio:1; width:100%; max-width:760px; margin-inline:auto;
  touch-action:pan-y;   /* a vertical page scroll stays the page's: on touch the ring turns on its own and a tap picks — nothing here ever takes the gesture */
  -webkit-user-select:none; user-select:none;}
@media(max-width:1119px){ .lstage.live .lwheel{max-width:min(100%,660px);} }
/* on a phone the circle is the one thing on the page that may leave the measure. it is a
   graphic, not a column of prose, and the twenty-two names it has to hold are the same
   twenty-two at 390 as at 1920 — so the gutter is given back to the radius rather than
   the names being shrunk into it. this cannot overflow the document: the wrap is already
   inset by exactly this much, so the negative margin lands the wheel on the viewport edge
   and not past it. */
@media(max-width:680px){
  .lstage.live .lwheel{width:calc(100% + var(--gutter)*2); max-width:none;
    margin-inline:calc(var(--gutter)*-1);}
  .lstage.live .lhint{padding:0 var(--gutter);}   /* the CIRCLE may reach the viewport edge; a line of type may not */
}
/* the field is the disc. ONE element carries the tilt, the lean and the scale, written as
   a single transform string by the engine — and it is deliberately NOT preserve-3d.
   flattening is not a compromise here, it is the geometry: the wheel IS a flat disc being
   turned in space, so its children belong in its plane. it is also the hit-testing
   invariant this section learned the hard way at It.82 — Chrome hit-tests a preserve-3d
   grandchild at its UNPROJECTED rect, and 2 names of 22 answered a click. flattened, all
   twenty-two do. */
.lstage.live .lfield{position:absolute; left:0; right:0; top:0; bottom:var(--lfoot,30px);
  display:block; will-change:transform;}
/* a name is one line of type and nothing else — no plate, no frame, no second line. the
   ENGLISH name is still in the button for assistive tech and still the second half of its
   accessible name; it is just not drawn, because the entry beside the wheel is where it
   is read now. that is the one thing this composition gets for free that the wall could
   not: the wheel says the language in its own script, the entry says everything else. */
.lstage.live .ltile{position:absolute; left:0; top:0; margin:0; padding:0;
  display:block; text-align:left; transform-origin:0 50%;
  backface-visibility:hidden; will-change:transform;}
/* THE HIT AREA IS NOT PADDING, AND THAT IS A PACKING DECISION BEFORE IT IS A POINTER ONE.
   the ring's type size is solved against what a name MEASURES, so every pixel of padding
   is a pixel of radius the longest name cannot have — and it is paid twice, once along
   the spoke and once again at the hub, where the height is divided by 2·sin(pi/22) to
   become radius. measured: padding of .26/.40em took the names from 28px to 22px at 1920
   and from 14px to 11px at 390, where 11px is not a language, it is a texture.
   an absolutely positioned ::after does not enter its parent's layout at all, so the
   button can be exactly as big as its word and still be as easy to hit as one with air
   round it. it is wider than it is tall on purpose: along the spoke there is nothing to
   collide with, and at the hub there are five pixels between one name and the next. */
.lstage.live .ltile::after{content:""; position:absolute; inset:-6px -9px; z-index:-1;}
/* THE FACE IS CALISTOGA, and that is the answer to "bold" on a page that has no bold.
   Newsreader ships 400 and 500 and nothing heavier, so a 600 here is a browser-synthesised
   smear — the mistake this site shipped once already with the italic (It.72). Calistoga is
   heavy BY DESIGN at its only weight, so the names get mass without a single faked stroke,
   and it is the page's own display face rather than a new one.
   the tracking comes IN a hair, because a display serif set at 22-34px and then bent round
   a circle reads loose at 0 — the letters on the outside of a spoke are already fanning.
   --lstroke is the edge: .014em under Calistoga, and raised per script below. */
.lstage.live .ltile .ln1{font-size:calc(var(--lfs,22) * 1px); line-height:1.06; font-weight:400;
  font-family:var(--display-lang); letter-spacing:-.008em;
  color:var(--ink); -webkit-text-stroke:var(--lstroke,.014em) currentColor;
  white-space:nowrap; transition:color .2s steps(3);}
/* the five scripts Calistoga has no glyphs for fall through to the system faces, which are
   REGULAR where Calistoga is heavy — so they take more edge instead of a faked weight. the
   stroke is the one lever that thickens the curve a font actually drew, identically in
   every script, and it is why this is a stroke and not a font-weight. */
.lstage.live .ltile[lang="th"], .lstage.live .ltile[lang="zh"], .lstage.live .ltile[lang="ja"],
.lstage.live .ltile[lang="ko"], .lstage.live .ltile[lang="ar"], .lstage.live .ltile[lang="hi"],
.lstage.live .ltile[lang="he"]{--lstroke:.040em;}
.lstage.live .ltile .ln2{position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;}
/* THE BEND, and it is the reference's shader rather than a decoration.
   the reference rotates every VERTEX of a word about the word's own anchor by an angle
   proportional to that vertex's distance along the word (`uSpeed * position.x *
   uAmplitude`), so a word flexes into an arc when the ring accelerates and lies straight
   when it coasts. the DOM equivalent is one character at a time, and it is arithmetic,
   not a frame loop: --bend is written ONCE per frame on the field, --d and --ox are
   written once per layout per character, and the browser multiplies them. a character at
   distance d turns by bend·d and therefore rises by about bend·d², which is the parabola
   the shader draws and the reason a per-word skew is not a substitute — a skew is linear.
   the origin is the WORD's anchor, put there by negating the character's own offset, so
   all of a word's characters turn about one point on the circle. */
.lch{display:inline-block; text-align:center;
  transform:rotate(calc(var(--bend,0) * var(--d,0) * 1deg));
  transform-origin:calc(var(--ox,0) * -1px) 50%;}
/* under the hand a name takes the page's own accent — the same tell, at the same speed,
   as every other hover on this page. the MOVEMENT is the engine's: the word lifts toward
   you and grows from its inner end, so it opens outward along its own spoke instead of
   pushing into its neighbours. */
.lstage.live .ltile:hover .ln1, .lstage.live .ltile:focus-visible .ln1{
  color:var(--ember-deep); -webkit-text-stroke-color:var(--ember-deep);}
.lstage.live .ltile:hover, .lstage.live .ltile:focus-visible{z-index:3;}
/* the lifted name is IN FRONT, and with absolutely positioned siblings paint order is DOM
   order and nothing else — so this is what puts it there. the one at the reading line
   gets the same, because it is the name the entry is about. */
.lstage.live .ltile.lat{z-index:2;}
/* AND IT IS THE ONE ORANGE THING ON THE WHEEL. `.lat` is written by the engine onto
   whichever name is on the reading line, which is by definition the name the entry beside
   it is describing — so the highlight and the entry are the same fact stated twice, on
   opposite sides of the stage, in one token. it takes the same colour a hovered name
   takes, deliberately: hovering is the beginning of picking, and a pick makes the hovered
   name the active one, so the two states sharing a colour means nothing changes hue at the
   moment of the click. the stroke below is what still tells them apart. */
.lstage.live .ltile.lat .ln1{--lstroke:.030em;
  color:var(--ember-deep); -webkit-text-stroke-color:var(--ember-deep);}
.lstage.live .ltile.lat[lang="th"], .lstage.live .ltile.lat[lang="zh"], .lstage.live .ltile.lat[lang="ja"],
.lstage.live .ltile.lat[lang="ko"], .lstage.live .ltile.lat[lang="ar"], .lstage.live .ltile.lat[lang="hi"],
.lstage.live .ltile.lat[lang="he"]{--lstroke:.055em;}

/* the hint sits UNDER the circle, not in it: the hub is empty in the reference and an
   empty hub is what makes twenty-two spokes read as radiating rather than as a ring of
   labels. --lfoot is its band, and the field is held out of it. */
.lstage.live{--lfoot:24px;}
.lstage.live .lhint{display:flex; position:absolute; left:0; right:0; bottom:0; z-index:1;
  height:var(--lfoot); align-items:flex-end; justify-content:center; margin:0;
  text-align:center; pointer-events:none; transition:opacity .24s steps(4);}
.lstage.live.picked .lhint{opacity:0;}
/* the close is the page's own quiet tell, not a new idea: mono, muted, ember-deep under a
   hand at the .18s steps(3) every other hover on this page answers with. it appears only
   while a name is being held, because that is the only state there is anything to leave. */
.lstage.live .lclose{display:grid; place-items:center; position:absolute; right:0; top:0; z-index:4;
  width:34px; height:34px; padding:0; appearance:none; background:transparent; border:0; cursor:pointer;
  font-family:var(--mono); font-size:19px; line-height:1; color:var(--muted);
  opacity:0; pointer-events:none; transition:opacity .24s steps(4), color var(--hov);}
.lstage.live.picked .lclose{opacity:1; pointer-events:auto;}
.lstage.live .lclose:hover, .lstage.live .lclose:focus-visible{color:var(--ember-deep);}

/* ---- the entry changes, and it changes in the page's own steps ----
   the circle turns at one name every 2.9s, so the entry has to hand over four times a
   minute without ever reading as a slideshow. it is one gesture, staggered down the
   column in the order the eye reads it: the rule wipes out first and comes back last, in
   the NEW colour, so the colour arriving is the last thing that happens and the thing
   that tells you the hand-over is finished. everything else dips a few pixels in the
   direction it will come back from. stepped, like every other transition on this page —
   a smoothly crossfading panel would be the one piece of this section not speaking the
   site's motion language. */
.lname, .lpron, .lbody{transition:opacity .26s steps(4), transform .26s steps(4);}
.lrule{transition:transform .42s cubic-bezier(.16,1,.3,1) .10s;}   /* the COLOUR is deliberately not transitioned: it is swapped in the same style change that lifts .lturn, at which instant the rule is still at scaleX(0) and invisible, so it grows back already wearing the new language's ink. give it a duration and it spends that duration visibly being the previous language. */
.lplate.lturn .lname{opacity:0; transform:translateY(-9px);}
.lplate.lturn .lpron{opacity:0; transform:translateY(-7px); transition-delay:.03s;}
.lplate.lturn .lrule{transform:scaleX(0); transition:transform .18s steps(4);}
.lplate.lturn .lbody{opacity:0; transform:translateY(9px); transition-delay:.06s;}

/* reduced motion: the composition is the design and it stays — the circle is still a
   circle, the entry is still an entry. what stops is every bit of movement: no spin, no
   hand, no bend, no stagger. the engine writes one rest frame and returns, and picking a
   name re-lays the wheel in place rather than easing to it. */
@media(prefers-reduced-motion:reduce){
  .lname, .lpron, .lrule, .lbody, .lstage.live .lhint, .lstage.live .lclose,
  .lstage.live .ltile .ln1{transition:none;}
  .lch{transform:none;}
}

/* ===== trust — transparent: ink text, ember indices, hairline dividers; the field flows behind ===== */
.trust{padding:calc(var(--cell)*7) 0;}
.tcards{display:grid; grid-template-columns:1fr; gap:calc(var(--cell)*2);}
@media(min-width:900px){ .tcards{grid-template-columns:repeat(3,1fr);} }
/* It.88: all three rules over the promises ARE the physics line now — the same engine,
   the same reference and the same hover tell as #why's and #memory's, and this instance
   is #why's token for token (1px of --line, 44 of pull, a 30px band), because the air
   was made to be the same rather than the motion made to fit.

   the borders are GONE and their pixel moved into the padding, so no card shifts by even
   one — a border occupies layout, an absolutely positioned line does not. that padding
   also went 18 -> 24 (--cell*1.5 -> *2, still on the grid): the bow is pull/2 = 22px, and
   the 19px the border left under the rule could not hold it. at 25 the clearance to the
   chip is 3px, which is exactly the clearance #why's line keeps over its own claim.
   above, the section head leaves 48px (36 below 1280) against #why's 33 — the loose side.

   below 900 the promises stop being three columns and stack 24px apart, which is less
   air than the bow needs, so the grab strip goes and the rule simply stands. same
   reasoning as .morow's phone rule, and the same result a phone gets anyway: mline.js
   binds no listener without a fine pointer. */
.tcard{position:relative; padding-top:calc(var(--cell)*2 + 1px);}
#trust .mline{--grab-on:30px; --pull:44px; --mlw:1px; --mlc:var(--line); top:0.5px;}
@media(max-width:899.98px){ #trust .mline-hit{display:none;} }
/* the number and the promise are one row. the size lives HERE, not on the h3, because
   the chip beside it is written in the row's em — see the stage-number block in #how. */
.tcard .tl{font-size:24px; margin:0 0 12px;}
/* It.88b (user direction): the promise is the accent, not ink. --ember-deep and NOT
   --ember, for two reasons that agree: it is the token the chip beside it is filled with
   and the token #how's paired stage title already carries, so the row is one colour and
   the two numbered sections stay a pair — and it is 4.9:1, AA at ANY size, so the claim
   survives a future resize. --ember is the brighter orange but it is the display tier,
   legal only at ≥18px; this title is 24-28px today and would be a silent contrast
   regression the first time it came down. */
.tcard h3{font-size:1em; color:var(--ember-deep); margin:0;}
.tcard p{margin:0; color:var(--ink-soft); font-size:15.5px; line-height:1.6;}
@media(min-width:1280px){ .tcard .tl{font-size:clamp(24px,1.6vw,28px);} }   /* three columns of real width */

/* ===== pricing — rebuilt whole at It.99 (user direction). two things changed and
   both of them are this section catching up with the rest of the page.

   (1) THE BILLING SWITCH IS #memory's CHIP ROW AT TWO. it used to be a mono pair
   under a 2px ember rule — which is exactly the marker the twelve category chips
   carried until It.63c moved them to a plate, and this switch was simply left
   behind. so it takes the same three states the twelve take (nothing at rest, a
   whisper of ink under the hand, the solid ember-deep plate with a paper word when
   chosen), the same small stepped corner, every change in the same steps(3) — and
   the same ONE ORANGE OBJECT THAT TRAVELS, carrying a clone of the type at each end
   so the swap is a wipe and no word ever changes colour anywhere it can be seen.
   the arithmetic behind that wipe is written out once, at the plate in #memory; this
   is that mechanism over a shorter distance, so the curve is identical and only the
   durations are shorter.

   (2) THE PAIR HAS AN OUTLINE AGAIN AND IT IS DRAWN IN WebGL. It.22 made every card
   on this page frameless and this is a directed reversal of that for these two only:
   the outline IS the section's visual idea now. what draws it is a transcription of
   ~/Website_resource/Webgl_&_ThreeJS_Effects/10 (Codrops "SDF Lens Blur") off
   three.js — a rounded rect stroked from a signed-distance field, whose stroke
   SOFTNESS is driven by a second field, a circle at the damped pointer. under the
   hand the border melts open into a wide flare; away from it, it is a hairline.
   nothing else moves — no tilt, no lift, no scale, no shadow — which is the whole
   reason the effect survives being put around real reading matter. the interiors
   stay transparent, so the ember field still runs through the cards exactly as it
   runs through everything else on this page. ===== */

/* ---- the switch ---- */
.ptoggle{position:relative; display:inline-flex; align-items:baseline; gap:2px;
  margin:calc(var(--cell)*2) 0 0; padding:0;}
/* the chip. same construction as a memory category chip, declared separately rather
   than by joining that selector list: the sheet is read by the verifiers with a
   first-match lookup, and one rule answering for two sections is one rule that can
   quietly answer wrong. */
.ptile{appearance:none; position:relative; z-index:0; flex:0 0 auto;
  background:transparent; border:0; margin:0; padding:11px 16px 12px; cursor:pointer;
  display:inline-flex; align-items:baseline;}
.ptile::before{content:""; position:absolute; inset:0; z-index:-1; clip-path:var(--step6);
  background:var(--ink); opacity:0;
  transition:opacity var(--hov), background-color var(--hov);}
.ptn{font-family:var(--serif); font-size:clamp(13.5px,1.05vw,16.5px); line-height:1.25;
  color:var(--ink-soft); white-space:nowrap; transition:color var(--hov);}
@media(hover:hover){
  .ptile:hover::before{opacity:.07;}
  .ptile:hover .ptn{color:var(--ink);}
}
/* the chosen one: the plate solid, the word in paper — 4.90:1 either way round */
.ptile[aria-pressed="true"]::before{background:var(--ember-deep); opacity:1;}
.ptile[aria-pressed="true"] .ptn{color:var(--paper);}
.ptile[aria-pressed="true"]:hover::before{opacity:1;}

/* the travelling plate. it is not a decoration OF the chosen chip, it IS that chip's
   plate in transit — same token, same silhouette — so when it lands the chip's own
   ::before takes over pixel-identical in the frame this hides in, and there is nothing
   to cross-fade. the size trails the position by 40ms, which is the character: the box
   leaves before it has finished becoming the next word's box. */
.ppuck{position:absolute; left:0; top:0; width:0; height:0; z-index:3; opacity:0;
  background:var(--ember-deep); clip-path:var(--step6); pointer-events:none;
  transform:translate3d(var(--pkx,0px),var(--pky,0px),0);
  transition:transform .42s cubic-bezier(.77,0,.175,1),
    width .38s cubic-bezier(.77,0,.175,1) .04s, height .38s cubic-bezier(.77,0,.175,1) .04s;}
.ppuck.on{opacity:1;}
/* the type the plate carries: a whole clone of each end, parked at its own place in the
   row and clipped by the plate. the transform here is the plate's own NEGATED on the
   same curve, so along one shared easing the two cancel at every instant and the copy
   holds still while the box slides off one word and onto the other. */
.ppin{position:absolute; left:0; top:0; white-space:nowrap; pointer-events:none;
  transform:translate3d(calc(var(--wx,0px) - var(--pkx,0px)), calc(var(--wy,0px) - var(--pky,0px)), 0);
  transition:transform .42s cubic-bezier(.77,0,.175,1);}
.ppin *{pointer-events:none;}
.ppin > *{opacity:1; transform:none; transition:none; background:transparent;}
.ppin .ptile::before{display:none;}     /* insurance: a cloned chip is always unpressed */
.ppin .ptn{color:var(--paper);}
/* while a plate is in the air both chips swap state instantly, because both swaps
   happen under it. the class is lifted a forced style flush after the landing, never
   in the same style-change event, or the swap it protects transitions after all. */
.ptoggle.p-fly .ptile::before,.ptoggle.p-fly .ptn{transition:none;}

/* ---- the pair ----
   this box is the positioning parent for the outline's canvas, which bleeds well past
   it on every side, so it isolates: without a stacking context of its own the canvas
   would sit at the mercy of whether the reveal's transform happens to be present. the
   two siblings that the bleed reaches over are lifted above it by one. */
.pcards{display:grid; position:relative; isolation:isolate; grid-template-columns:1fr;
  gap:calc(var(--cell)*2); max-width:900px; margin:0 auto;}
@media(min-width:680px){ .pcards{grid-template-columns:1fr 1fr; gap:calc(var(--cell)*6);} }
@media(min-width:1280px){ .pcards{max-width:1000px;} }   /* wide: a decision, not a wall — it stays a centred pair */
#pricing .shead,.pfoot{position:relative; z-index:1;}

/* the outline's canvas spans the PAIR, not one card: one context, one draw call, and
   the two flares meet in the gutter instead of being cut off at a card's edge. it
   bleeds --pbleed on every side because the reference's lens spreads the border up to
   about 0.83 of a card's half-width OUTWARD, and a canvas cropped at the card box
   would slice that flare square. pricing.js sets the token from the measured card.
   a scripted canvas must state its CSS size outright — left to `auto` it resolves to
   its own width/height attributes, which the script just wrote from the measured box,
   and the two compound each other every resize. */
.pgl{position:absolute; display:block; z-index:-1; pointer-events:none;
  left:calc(var(--pbleed-x,120px)*-1); top:calc(var(--pbleed,120px)*-1);
  width:calc(100% + var(--pbleed-x,120px)*2); height:calc(100% + var(--pbleed,120px)*2);}
#pricing:not(.gl) .pgl{display:none;}

/* the card. the border here is what the page shows when there is no WebGL context to
   draw into and when motion is not welcome — same colour, same corner, same width as
   the shader's hairline, so the two paths are one design and not two. with a context
   the colour goes transparent and the width stays, which keeps the layout identical
   between them down to the pixel. */
.pcard{position:relative; z-index:1; background:transparent; text-align:left;
  display:flex; flex-direction:column;
  padding:calc(var(--cell)*3.5) calc(var(--cell)*3);
  border:1.4px solid var(--ember); border-radius:var(--radius);}
.pcard.max{border-width:1.9px;}     /* the only hierarchy between them: a slightly firmer line */
#pricing.gl .pcard{border-color:transparent;}
/* the plan name is the accent (user direction). --ember and NOT --ember-deep: this is
   26px and up, the display tier, which is the only tier the brighter orange is legal
   on — and it is the tier the section's own h2 is already set in. */
.pcard .phead h3{font-size:clamp(26px,2.1vw,32px); line-height:1.12; margin:0; color:var(--ember);}
/* two lines are RESERVED here whether or not the descriptor takes them. the two are
   different lengths and wrap at different widths, and without this the price, the
   rule and the whole feature list in one card sit a line lower than in the other —
   which on a pair of outlined boxes standing side by side reads as a mistake. */
.pcard .pdesc{margin:10px 0 0; min-height:calc(1.6em*2);}
.pcard .price{margin:calc(var(--cell)*2.5) 0 0; display:flex; align-items:baseline;}
.pcard .pval{font-family:var(--display); font-size:clamp(44px,3.3vw,54px); line-height:1; color:var(--ink);}
.pcard .pper{font-family:var(--mono); font-size:11px; letter-spacing:.1em; color:var(--muted); margin-left:7px;}
.pcard .palt{margin:10px 0 0; font-family:var(--mono); font-size:10.5px; letter-spacing:.1em; color:var(--muted);}

/* ---- the billing swap ----
   BOTH states are in the document and both occupy the SAME grid cell. that single
   fact is what makes "no layout shift" a property of the box rather than a promise
   about the content: the cell is as tall as the taller state at every width, so the
   rule and the feature list under it have nowhere to move to when the switch is
   thrown, and it holds even though the annual state carries a capsule the monthly
   one does not. it is the same reservation `.pdesc` makes two lines above.
   `display:none` cannot do this — a box that is not generated cannot be measured,
   cannot be transitioned, and hands the height back to whichever state is live. */
.pswap{display:grid; grid-template-columns:minmax(0,1fr);}
/* visibility, not opacity, is what takes the parked state out of the a11y tree and
   out of a text selection. it flips a step AFTER the parts have finished leaving,
   and INSTANTLY on the way in — a transition on visibility is a delay, not a fade. */
.pstate{grid-area:1/1; min-width:0; visibility:hidden; transition:visibility 0s linear .2s;}
.ps-mo{visibility:visible; transition:visibility 0s;}
#pricing.annual .ps-mo{visibility:hidden; transition:visibility 0s linear .2s;}
#pricing.annual .ps-yr{visibility:visible; transition:visibility 0s;}

/* the four parts the switch is about — price, billing period, the savings capsule and
   the supporting line — animate one at a time off a single index. the numbers ARE the
   order they are read in, so the stagger is the reading order and not a decoration. */
.pswap .pval{--d:0;}
.pswap .pper{--d:1;}
.pswap .psave{--d:2;}
.pswap .palt{--d:3;}
/* PARKED. everything sits 9px BELOW its place: the state that is leaving sinks out and
   the state that is arriving rises into the same slot, which is the one motion a price
   is allowed to make. leaving is quick and nearly together (20ms apart) — a slow exit
   just reads as lag — and it is over at 200ms, which is where the parent's visibility
   flips and 220ms before the plate finishes its own flight. */
.pstate .price > *,.pstate > .palt{opacity:0; transform:translate3d(0,9px,0);
  transition:opacity .14s linear calc(var(--d,0)*.02s),
             transform .14s cubic-bezier(.55,0,1,.45) calc(var(--d,0)*.02s);}
/* LIVE. the arrival is the plate's own curve and duration family (power4.inOut, the
   easing the switch already flies on) held back .15s so the two states never cross,
   and dealt out 30ms apart. the last part settles at 520ms — 100ms after the plate
   lands, so the card reads as following the switch rather than racing it. */
.ps-mo .price > *,.ps-mo > .palt{opacity:1; transform:none;
  transition:opacity .24s linear calc(.15s + var(--d,0)*.03s),
             transform .28s cubic-bezier(.77,0,.175,1) calc(.15s + var(--d,0)*.03s);}
#pricing.annual .ps-mo .price > *,#pricing.annual .ps-mo > .palt{opacity:0; transform:translate3d(0,9px,0);
  transition:opacity .14s linear calc(var(--d,0)*.02s),
             transform .14s cubic-bezier(.55,0,1,.45) calc(var(--d,0)*.02s);}
#pricing.annual .ps-yr .price > *,#pricing.annual .ps-yr > .palt{opacity:1; transform:none;
  transition:opacity .24s linear calc(.15s + var(--d,0)*.03s),
             transform .28s cubic-bezier(.77,0,.175,1) calc(.15s + var(--d,0)*.03s);}

/* the savings capsule. it is not a new object: mono 500 at .14em, uppercased by the
   sheet so the document still holds sentence case, on the page's own stepped corner
   with the button's own fill — the `.mk-k` / `.rc-i` capsule this site already has,
   at pricing's scale. --ember-deep NOT --ember, because 9.5px is the text tier and
   only the deep one is legal there: --paper on it is 4.90:1, AA at any size.
   it rides INSIDE the price row, so the height it needs is height the 54px figure
   beside it has already taken and the row does not grow to hold it. */
.psave{flex:none; align-self:center; margin-left:12px;
  font-family:var(--mono); font-weight:500; font-size:9.5px; line-height:1;
  letter-spacing:.14em; text-transform:uppercase; white-space:nowrap;
  padding:6px 9px 5px; background:var(--ember-deep); color:var(--paper);
  clip-path:var(--step6);}
.pfeat{list-style:none; margin:calc(var(--cell)*2.5) 0 0; padding:calc(var(--cell)*2) 0 0; border-top:1px solid var(--line-2);}
.pfeat li{position:relative; padding:7px 0 7px 20px; font-size:15px; line-height:1.5; color:var(--ink-soft);}
.pfeat li::before{content:""; position:absolute; left:0; top:14px; width:7px; height:7px; background:var(--ember);}
/* NINE cells of air, not three. the flare blooms DOWNWARD out of a card's bottom edge
   as readily as it blooms up out of the top one, and this line is the only reading
   matter standing in that path — mono 10.5 in --muted, which is 4.71:1 on bare paper
   and therefore has no contrast to spare. distance is the whole defence, and it was
   measured: at three cells the worst wash under it is a=0.095 and the line reads
   4.19:1; at nine it is out of the flare's way. nine is also this page's own section
   rhythm, so the air is not a patch, it is the grid. */
.pfoot{margin:calc(var(--cell)*9) auto 0; text-align:center; max-width:62ch;}
#pricing .honest{margin-top:calc(var(--cell)*1.5);}
@media(max-width:679.98px){
  .pcard{padding:calc(var(--cell)*3) calc(var(--cell)*2.5);}
  .pcards{gap:calc(var(--cell)*2.5);}
}
/* reduced motion: pricing.js never adds .gl, so the shader is not in the document at
   all and the card keeps the CSS hairline it ships with. the plate does not fly —
   it is parked on the chosen word in one step. */
@media(prefers-reduced-motion:reduce){
  .ptn,.ptile::before{transition:none;}
  .ppuck,.ppin{transition:none;}
  /* the swap still swaps — it just arrives. the parked/live pair is still opacity and
     transform, so the two states remain stacked and the card still cannot shift. */
  .pstate,.pstate .price > *,.pstate > .palt{transition:none;}
}

/* ===== cta — the message lives in the field marquee above the lone button ===== */
.cta{padding:calc(var(--cell)*12) 0;}
.cta .cta-btnrow{margin:0;}

/* ===== footer toy: stack your week ===== */
#stacker{position:relative; height:calc(var(--cell)*9); margin:0 8px; background:var(--ink); color:var(--paper);
  border-radius:var(--radius); overflow:hidden; transition:height .55s cubic-bezier(.16,1,.3,1);}
#stacker.playing{height:min(52vh,390px);}
#stackcv{position:absolute; inset:0; width:100%; height:100%; display:block;}
/* It.111 — the cabinet wears its lights. two 4px courses of the ladder's own colours run
   the top and bottom edges, chasing in 12px steps (steps(6), one full tile per cycle —
   cell-by-cell, never a slide) the way an arcade marquee chases its bulbs. they are the
   one loud thing the panel does, and they are 8px of a 108px panel; the game canvas
   between them does the real selling. static — same colours, no chase — under reduced
   motion, and the strips sit UNDER the hud so no control ever stands on a moving ground. */
#stacker::before, #stacker::after{content:""; position:absolute; left:0; right:0; height:4px; z-index:2; pointer-events:none;
  background:repeating-linear-gradient(90deg, var(--gold) 0 12px, var(--ember) 12px 24px, var(--sage) 24px 36px, var(--sky) 36px 48px, var(--rust) 48px 60px, var(--glow) 60px 72px);
  background-size:72px 4px; animation:stchase 1.9s steps(6) infinite;}
#stacker::before{top:0;}
#stacker::after{bottom:0; animation-direction:reverse;}
@keyframes stchase{to{background-position-x:72px;}}
.tt-teaser{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:4;
  transition:opacity .35s, transform .35s, background var(--hov);}
#stacker.playing .tt-teaser{opacity:0; pointer-events:none; transform:translate(-50%,-50%) scale(.86);}
#stack-hud{position:absolute; inset:0; z-index:3; pointer-events:none; font-family:var(--mono);}
.st-score{position:absolute; top:18px; left:22px; margin:0; display:flex; flex-direction:column; gap:2px;}
.st-score #st-score{font-size:clamp(18px,2.4vw,26px); letter-spacing:.08em; color:var(--paper); display:inline-block;}   /* paper, not gold: the band rungs are never text, however tempting the prize */
.st-score.tick #st-score{animation:stpop .38s steps(3);}   /* a cleared row ticks the number: three stepped frames, size only */
@keyframes stpop{0%{transform:scale(1.35);} 100%{transform:scale(1);}}
.st-score .micro{color:rgba(247,241,230,.55);}
.st-pad{position:absolute; left:50%; bottom:16px; transform:translateX(-50%); display:grid; grid-template-columns:repeat(4,52px); gap:8px; pointer-events:auto;}
.st-pad button, .st-close{border:0; background:var(--paper); color:var(--ink); cursor:pointer; display:grid; place-items:center;
  pointer-events:auto; font-size:18px; line-height:1; clip-path:var(--step6); transition:background var(--hov), color var(--hov);}
.st-pad button{width:52px; height:52px;}
.st-pad button:hover, .st-close:hover{background:var(--ember-deep); color:var(--paper);}
.st-close{position:absolute; top:18px; right:20px; width:38px; height:38px; z-index:6;}
.st-over{position:absolute; inset:0; z-index:5; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:16px; text-align:center; pointer-events:auto; background:rgba(34,28,20,.88);}
.st-over[hidden]{display:none;}
.st-over .st-ttl{font-family:var(--display); font-size:clamp(24px,3.4vw,36px); color:var(--paper); margin:0;}
@media(max-width:680px){
  #stacker.playing{height:min(58vh,440px);}
}

/* ===== site footer (contract) ===== */
.sitefoot{padding:calc(var(--cell)*5) var(--gutter) calc(var(--cell)*2); border-top:1px solid var(--line); margin-top:calc(var(--cell)*4);}
.foot-grid{max-width:var(--maxw); margin:0 auto; display:grid; grid-template-columns:1fr; gap:calc(var(--cell)*3);}
@media(min-width:680px){ .foot-grid{grid-template-columns:1.4fr 1fr 1fr 1fr;} }
.foot-brand .foot-one{margin:10px 0 0; font-size:15px; color:var(--ink-soft);}
.foot-col{display:flex; flex-direction:column; gap:9px; align-items:flex-start;}
.foot-col .kick{margin-bottom:5px;}
.foot-col a{font-size:14.5px; color:var(--ink-soft); text-decoration:none; transition:color .15s;}
.foot-col a:hover{color:var(--ember-deep);}
.foot-base{max-width:var(--maxw); margin:calc(var(--cell)*3) auto 0; padding-top:calc(var(--cell)*1.5);
  border-top:1px solid var(--line-2); display:flex; flex-wrap:wrap; gap:12px calc(var(--cell)*2); align-items:baseline;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted);}
.foot-base .foot-tag{margin-left:auto; font-family:var(--serif); font-style:italic; font-size:15px; letter-spacing:0;
  text-transform:none; color:var(--ember-deep);}   /* the sign-off closes the page in the brand's voice */

/* ===== legal pages (subpage contract) ===== */
.legal{max-width:68ch; margin:0 auto; padding:calc(var(--cell)*8) var(--gutter) calc(var(--cell)*6);
  font-family:var(--serif); font-size:17px; line-height:1.7; color:var(--ink-soft);}
.legal h1{font-size:40px; line-height:1.1; margin:0 0 calc(var(--cell)*2);}
.legal h2{font-size:24px; line-height:1.2; margin:0 0 14px;}
.legal section{border-top:1px solid var(--line); padding:calc(var(--cell)*2) 0;}
.legal section:first-of-type{border-top:0;}
.legal .updated{font-family:var(--mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);}
.legal p{margin:0 0 1em;}
.legal ul{margin:0 0 1em; padding-left:1.2em;}

/* ===== reduced motion: everything calm, information parity absolute ===== */
@media(prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  *, *::before, *::after{animation:none !important; transition:none !important;}
  .reveal{transform:none;}
  .split{opacity:1;}
  .split .ln > span{transform:none;}
  /* the card stands as the whole written record: no tape, no trail, no lean. demo.js
     returns before it touches a node here, so this is the markup's own state — these
     rules only guarantee it, in case a stale engine ever reaches this media query */
  .demo-wake{display:none;}
  .su{opacity:1;}
  .cr{display:none;}
  .cv{transform:none;}
  .cv-mv{transform:none;}
  .cv.js .cv-state, .cv.js .cv-proof{opacity:1; transform:none;}
  .cv.js .cv-ask, .cv.js .cv-reply{height:auto; overflow:visible;}
  .cv-sr i{transform:scaleX(1);}
  .toplink{transition:none;}
  /* the opening sequence stops being scroll-driven and simply stacks: all three beats
     readable, and the art props stop being absolutely placed so nothing overflows */
  .seq{height:auto;}
  .seq-stage{position:static; height:auto; overflow:visible; padding:calc(var(--cell)*5) 0;}
  .seq-rail{display:none;}
  .seq-panels{min-height:0;}
  .seq-panel{position:static; grid-template-columns:1fr; gap:calc(var(--cell)*2);
    opacity:1; pointer-events:auto; margin-bottom:calc(var(--cell)*5);}
  .seq-panel:last-child{margin-bottom:0;}
  .seq-panel .seq-text, .seq-panel .seq-art{transform:none;}
  .seq-art{height:auto;}
  [data-seq="0"] .seq-art{height:min(46svh,380px);}
  .seq-artin{position:static;}
  .seq-mk{position:static; left:auto; top:auto; transform:none; width:100%; gap:calc(var(--cell)*1.5);}
  .seq-mem{position:static; inset:auto; margin:0; height:auto; width:100%;}
  .mem-c{display:none;}
  .mem-f{position:static; left:auto; right:auto; bottom:auto; margin-top:12px;}
}

/* ===== small screens ===== */
@media(max-width:720px){
  .sec{padding:calc(var(--cell)*4.5) 0;}
  .hero{min-height:0; padding-top:calc(var(--cell)*4);}
  .cta{padding:calc(var(--cell)*8) 0;}
  .track-wrap{margin-left:0; margin-right:0;}
}
