/* Spark — shared design system.
   One sheet for the whole site: the product landing page and the technical chapters.

   The chapters were written against a class contract (.spread / .rail / .body / .rise /
   .subnav / table.abi / .note / .steps / .trio). That contract is preserved verbatim below,
   so re-theming the site re-themes every chapter without touching a single chapter file.

   Three faces, each with one job:
     Bricolage Grotesque — display and UI. Distinctive, variable, does the product voice.
     Fraunces           — long-form chapter prose only. Kept because it reads beautifully
                          at length, and the chapters are long.
     JetBrains Mono     — anything mechanical: code, labels, status, measurements.

   Colour carries meaning and is not decorative:
     ember   the spark; primary accent, headings, emphasis
     signal  BUILT / verified / live
     violet  the adaptive + AI thread, reserved — never used for ordinary emphasis
     dim     PLANNED / not yet real
*/
:root{
  --void:#08090d; --surface:#0f1118; --raised:#141722;
  --line:#1e2130; --line2:#2a2e40;
  --bone:#ece7dd; --paper:#cdc8bf; --dim:#8a8880; --faint:#5c5b56;

  --ember:#ff8a3d; --ember-hi:#ffb072; --ember-dim:#a3562294;
  --signal:#5fd4b4; --signal-dim:#5fd4b433;
  --violet:#9d7bff; --violet-dim:#9d7bff2e;
  --alert:#ff5c47;

  --measure:640px;
  --ease:cubic-bezier(.2,.7,.2,1);
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  background:var(--void); color:var(--paper);
  font-family:"Bricolage Grotesque","Helvetica Neue",sans-serif;
  font-size:18px; line-height:1.62; font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

/* Atmosphere: an ember pool up top, a cool one below, over a faint grid that fades out.
   Fixed and pointer-events:none so it never participates in layout or hit-testing. */
.bg{position:fixed; inset:0; z-index:-3; pointer-events:none;
  background:
    radial-gradient(1100px 700px at 78% -8%, rgba(255,138,61,.13), transparent 60%),
    radial-gradient(900px 760px at 6% 108%, rgba(157,123,255,.08), transparent 60%),
    radial-gradient(700px 500px at 50% 40%, rgba(95,212,180,.035), transparent 65%),
    linear-gradient(180deg,#0a0b11 0%,#08090d 55%,#08090d 100%);}
.board{position:fixed; inset:-2px; z-index:-2; pointer-events:none; opacity:.55;
  background-image:
    linear-gradient(rgba(236,231,221,.02) 1px,transparent 1px),
    linear-gradient(90deg,rgba(236,231,221,.02) 1px,transparent 1px);
  background-size:56px 56px;
  -webkit-mask-image:radial-gradient(120% 90% at 50% 20%,#000 0%,transparent 76%);
          mask-image:radial-gradient(120% 90% at 50% 20%,#000 0%,transparent 76%);}
.grain{position:fixed; inset:0; z-index:-1; pointer-events:none; opacity:.04; mix-blend-mode:overlay}

.wrap{max-width:1080px; margin:0 auto; padding:0 30px}
a{color:var(--signal); text-decoration:none; border-bottom:1px solid rgba(95,212,180,.3); transition:border-color .2s, color .2s}
a:hover{border-color:var(--signal); color:#8ce4cb}
em{font-style:italic; color:var(--bone)}
strong{font-weight:600; color:#fff}
.mono{font-family:"JetBrains Mono",monospace}
.dim{color:var(--dim)}

/* ============================ STATUS ============================
   Every capability claim on this site carries one of these. The site describes a system
   under active development; a reader must be able to tell, at a glance and without reading
   prose, what runs today from what is designed but not yet real. */
.st{display:inline-flex; align-items:center; gap:6px; flex:none;
  font-family:"JetBrains Mono",monospace; font-size:9.5px; font-weight:500;
  letter-spacing:.15em; text-transform:uppercase;
  padding:3px 9px; border-radius:20px; border:1px solid; white-space:nowrap}
.st::before{content:""; width:5px; height:5px; border-radius:50%; background:currentColor; flex:none}
.st.built{color:var(--signal); border-color:var(--signal-dim); background:rgba(95,212,180,.06)}
.st.wip{color:var(--ember); border-color:rgba(255,138,61,.28); background:rgba(255,138,61,.06)}
.st.planned{color:var(--dim); border-color:var(--line2); background:rgba(236,231,221,.02)}
.st.adaptive{color:var(--violet); border-color:var(--violet-dim); background:rgba(157,123,255,.07)}

/* ============================ SITE NAV ============================ */
.topnav{position:sticky; top:0; z-index:40; -webkit-backdrop-filter:blur(11px); backdrop-filter:blur(11px);
  background:rgba(8,9,13,.8); border-bottom:1px solid var(--line)}
.topnav .in{max-width:1080px; margin:0 auto; padding:0 30px; height:58px;
  display:flex; align-items:center; gap:28px}
.topnav .brand{display:flex; align-items:center; gap:9px; margin-right:auto; border:0; color:var(--bone)}
.topnav .brand svg{width:22px; height:22px; flex:none}
.topnav .brand b{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:17px;
  letter-spacing:-.02em; color:var(--bone)}
.topnav .brand span{font-family:"JetBrains Mono",monospace; font-size:9.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--faint); padding-left:3px}
.topnav a.nl{font-family:"JetBrains Mono",monospace; font-size:11px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--dim); border:0}
.topnav a.nl:hover{color:var(--ember-hi)}
.topnav a.nl.on{color:var(--bone)}
@media (max-width:820px){
  .topnav .in{gap:16px; overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch}
  .topnav .in::-webkit-scrollbar{display:none}
  .topnav .brand{margin-right:12px}
  .topnav a.nl{white-space:nowrap}
}

/* ============================ HERO ============================ */
header{position:relative; padding:118px 0 96px}
header.tall{min-height:92svh; display:flex; flex-direction:column; justify-content:center}
h1{font-family:"Bricolage Grotesque",sans-serif; font-weight:600;
  font-size:clamp(38px,7.4vw,88px); line-height:1.02; letter-spacing:-.035em; color:var(--bone)}
h1 .br{color:var(--ember-hi)}
h1 .vi{color:var(--violet)}
.kicker{display:inline-flex; align-items:center; gap:10px; margin-bottom:26px;
  font-family:"JetBrains Mono",monospace; font-size:10.5px; letter-spacing:.22em;
  text-transform:uppercase; color:var(--ember); padding:6px 13px; border-radius:20px;
  border:1px solid rgba(255,138,61,.25); background:rgba(255,138,61,.05)}
.lede{margin-top:26px; font-size:clamp(18px,2.2vw,23px); font-weight:350; color:#bdb8b0;
  max-width:56ch; line-height:1.5}
.lede i{font-style:italic; color:var(--ember-hi)}
.cta{display:flex; flex-wrap:wrap; gap:12px; margin-top:38px}
.btn{display:inline-flex; align-items:center; gap:9px; padding:12px 21px; border-radius:9px;
  font-family:"JetBrains Mono",monospace; font-size:11.5px; letter-spacing:.1em;
  text-transform:uppercase; border:1px solid var(--line2); color:var(--bone);
  background:var(--raised); transition:border-color .2s, background .2s, transform .12s}
.btn:hover{border-color:var(--ember-dim); background:#1a1d2a; transform:translateY(-1px)}
.btn.pri{background:linear-gradient(180deg,#ff9550,#f0762b); color:#1a0d04; border-color:#ff9550;
  font-weight:700}
.btn.pri:hover{background:linear-gradient(180deg,#ffa768,#ff8a3d); color:#1a0d04}

/* ---- the hero: text beside the breach ----
   Two columns wide, stacked narrow. The graphic comes second in the DOM so a screen reader and a
   no-CSS render both reach the headline first. */
.hero{display:grid; grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr); gap:44px;
  align-items:center; max-width:1080px; margin:0 auto; padding:0 30px}
/* The headline shares its row with the graphic now, so it cannot take the full-width
   display size without wrapping into four ragged lines. */
.hero h1{font-size:clamp(33px,4.5vw,60px)}
@media (max-width:940px){
  .hero{grid-template-columns:1fr; gap:16px}
  .breach{max-width:440px; margin-inline:auto}
}

/* ---- THE BREACH ----
   After Banksy's 2005 trompe-l'oeil on the West Bank barrier: a section of concrete painted as
   though broken open, with a bright, ordinary place visible through the gap. The force of that
   image is its quiet — it is a *view*, not an explosion. An earlier attempt here was a starburst
   with lens flare and blast debris, which is the opposite register and read as stock artwork.

   So: cold grey slabs, a fractured opening, and warm light from a landscape on the far side.
   Nothing bursts. The only motion is the far-side lights and a few packets drifting out.

   Inspired by, not a reproduction of, that work — the geometry here is generated. */
.breach{position:relative; width:100%}
/* No rounded corners and no drop shadow: those made it read as a UI card sitting on the page
   rather than as a wall the page is looking at. It is a crop of something larger. */
.breach-svg{display:block; width:100%; height:auto; overflow:hidden}

/* Light on the far side shifts slowly, the way daylight does. Very low amplitude: a wall that
   visibly pulses stops reading as concrete. */
/* No glow on the concrete. The reference wall is flat and unlit; light spilling onto it washes
   out the only thing carrying the image, which is saturated colour against dead grey. */

/* Distant lights, flickering out of step with each other. */
.b-lights .l0{animation:b-twinkle 4.3s ease-in-out infinite}
.b-lights .l1{animation:b-twinkle 5.7s ease-in-out .6s infinite}
.b-lights .l2{animation:b-twinkle 6.9s ease-in-out 1.3s infinite}
.b-lights .l3{animation:b-twinkle 5.1s ease-in-out 2.0s infinite}
.b-lights .l4{animation:b-twinkle 7.5s ease-in-out .9s infinite}
.b-lights .l5{animation:b-twinkle 4.9s ease-in-out 2.7s infinite}
@keyframes b-twinkle{0%,100%{opacity:.35}50%{opacity:1}}

/* Sparks lifting out of the gap: embers off a bonfire, not packets in a diagram. Each rises
   on its own wandering curve, flickering out of step, so the effect never looks like a loop.
   `offset-path` does the travel; a separate flicker animation does the life of the ember. */
.b-sparks .s{offset-rotate:0deg; opacity:0}
@keyframes sp-rise{
  0%{offset-distance:0%; opacity:0; transform:scale(.5)}
  8%{opacity:1; transform:scale(1)}
  70%{opacity:.85}
  100%{offset-distance:100%; opacity:0; transform:scale(.35)}}
@keyframes sp-flick{0%,100%{filter:brightness(1)}50%{filter:brightness(1.75)}}

/* The bands beyond the gap drift, slowly and out of phase. Long durations and small amplitude:
   the glimpse should feel alive without drawing the eye away from the copy. */
.b-aurora ellipse{transform-box:fill-box; transform-origin:center}
.b-aurora .a0{animation:au-drift 29s ease-in-out infinite}
.b-aurora .a1{animation:au-drift 37s ease-in-out -9s infinite}
.b-aurora .a2{animation:au-drift 33s ease-in-out -17s infinite}
.b-aurora .a3{animation:au-drift 41s ease-in-out -5s infinite}
@keyframes au-drift{0%,100%{transform:translateX(-9px) scaleY(.94); opacity:.78}
                    50%{transform:translateX(11px) scaleY(1.06); opacity:1}}
.b-sparks .s0{offset-path:path("M321,244 Q367,154 402,44");animation:sp-rise 6.2s linear 11.9s infinite,sp-flick 0.8s ease-in-out 1.9s infinite}
.b-sparks .s1{offset-path:path("M321,244 Q340,107 443,-60");animation:sp-rise 12.7s linear 10.2s infinite,sp-flick 1.0s ease-in-out 0.4s infinite}
.b-sparks .s2{offset-path:path("M321,244 Q289,151 256,37");animation:sp-rise 8.2s linear 5.1s infinite,sp-flick 1.7s ease-in-out 0.2s infinite}
.b-sparks .s3{offset-path:path("M321,244 Q284,110 223,-53");animation:sp-rise 6.2s linear 4.0s infinite,sp-flick 1.2s ease-in-out 0.2s infinite}
.b-sparks .s4{offset-path:path("M321,244 Q338,116 342,-40");animation:sp-rise 10.7s linear 1.4s infinite,sp-flick 1.9s ease-in-out 1.8s infinite}
.b-sparks .s5{offset-path:path("M321,244 Q281,103 199,-68");animation:sp-rise 6.8s linear 11.3s infinite,sp-flick 1.2s ease-in-out 1.1s infinite}
.b-sparks .s6{offset-path:path("M321,244 Q324,121 316,-30");animation:sp-rise 10.5s linear 8.8s infinite,sp-flick 0.9s ease-in-out 0.7s infinite}
.b-sparks .s7{offset-path:path("M321,244 Q351,134 464,0");animation:sp-rise 8.9s linear 3.6s infinite,sp-flick 1.0s ease-in-out 1.0s infinite}
.b-sparks .s8{offset-path:path("M321,244 Q342,118 385,-35");animation:sp-rise 8.5s linear 3.2s infinite,sp-flick 1.2s ease-in-out 1.7s infinite}
.b-sparks .s9{offset-path:path("M321,244 Q282,131 162,-7");animation:sp-rise 9.1s linear 9.7s infinite,sp-flick 0.8s ease-in-out 1.0s infinite}
.b-sparks .s10{offset-path:path("M321,244 Q383,153 481,42");animation:sp-rise 7.3s linear 2.3s infinite,sp-flick 1.3s ease-in-out 1.5s infinite}
.b-sparks .s11{offset-path:path("M321,244 Q361,115 383,-41");animation:sp-rise 7.2s linear 2.1s infinite,sp-flick 1.9s ease-in-out 0.7s infinite}
.b-sparks .s12{offset-path:path("M321,244 Q299,110 282,-54");animation:sp-rise 8.2s linear 6.0s infinite,sp-flick 1.4s ease-in-out 1.4s infinite}
.b-sparks .s13{offset-path:path("M321,244 Q346,121 414,-30");animation:sp-rise 9.4s linear 6.3s infinite,sp-flick 1.3s ease-in-out 0.4s infinite}
.b-sparks .s14{offset-path:path("M321,244 Q325,160 328,58");animation:sp-rise 6.9s linear 9.5s infinite,sp-flick 1.6s ease-in-out 0.1s infinite}

/* ---- the adaptive motif: the same payload, re-framed on every pass ----
   Twelve cells; four of them widen a gap in a staggered loop, so the split point walks along
   the string. This is the polymorphism claim rendered literally — nothing about the bytes
   changes, only where they are cut. A sweep passes over, finding a different shape each time. */
.morph{position:relative; margin-top:52px; max-width:560px; padding:22px 0}
/* Gap in em and a floor low enough that eleven cells plus the opened cut still fit a 320px
   viewport: 11 cells x 1.75em + 10 gaps x .3em + one 1.55em cut = 23.8em, which must stay under
   the 260px of content width a 320px screen leaves after the wrapper's gutters. */
.cells{display:flex; gap:.3em; font-family:"JetBrains Mono",monospace; font-weight:500;
  font-size:clamp(10px,3vw,17px)}
.cells i{position:relative; width:1.75em; height:2.3em; display:grid; place-items:center;
  border-radius:5px; font-style:normal; color:#d9cfc4; border:1px solid var(--line2);
  background:var(--raised)}
/* The blade that lands in the opened gap — this is what makes the cut legible at a glance.
   It sits in the margin the keyframes open, so the two are timed identically. */
.cells i::after{content:""; position:absolute; right:-.78em; top:-3px; bottom:-3px; width:2px;
  border-radius:2px; background:var(--ember); box-shadow:0 0 11px 1px rgba(255,138,61,.6);
  opacity:0; transform:scaleY(.3)}
.cells i.c1{animation:cut1 9s var(--ease) infinite}
.cells i.c2{animation:cut2 9s var(--ease) infinite}
.cells i.c3{animation:cut3 9s var(--ease) infinite}
.cells i.c1::after{animation:blade1 9s var(--ease) infinite}
.cells i.c2::after{animation:blade2 9s var(--ease) infinite}
.cells i.c3::after{animation:blade3 9s var(--ease) infinite}
/* Every stop declares every animated property. Naming a property at only some keyframes makes
   the browser hold that value across the whole cycle instead of pulsing it — which had all
   three cells reading as permanently cut. */
@keyframes cut1{
  0%   {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  22%  {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  30%  {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  100% {margin-right:0;      border-color:var(--line2); color:#d9cfc4}}
@keyframes cut2{
  0%   {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  30%  {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  38%  {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  55%  {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  63%  {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  100% {margin-right:0;      border-color:var(--line2); color:#d9cfc4}}
@keyframes cut3{
  0%   {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  63%  {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  71%  {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  90%  {margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  98%  {margin-right:0;      border-color:var(--line2); color:#d9cfc4}
  100% {margin-right:0;      border-color:var(--line2); color:#d9cfc4}}
@keyframes blade1{
  0%{opacity:1;transform:scaleY(1)} 22%{opacity:1;transform:scaleY(1)}
  30%{opacity:0;transform:scaleY(.3)} 100%{opacity:0;transform:scaleY(.3)}}
@keyframes blade2{
  0%{opacity:0;transform:scaleY(.3)} 30%{opacity:0;transform:scaleY(.3)}
  38%{opacity:1;transform:scaleY(1)} 55%{opacity:1;transform:scaleY(1)}
  63%{opacity:0;transform:scaleY(.3)} 100%{opacity:0;transform:scaleY(.3)}}
@keyframes blade3{
  0%{opacity:0;transform:scaleY(.3)} 63%{opacity:0;transform:scaleY(.3)}
  71%{opacity:1;transform:scaleY(1)} 90%{opacity:1;transform:scaleY(1)}
  98%{opacity:0;transform:scaleY(.3)} 100%{opacity:0;transform:scaleY(.3)}}
.sweep{position:absolute; left:0; right:0; top:14px; bottom:14px; pointer-events:none; overflow:hidden; border-radius:6px}
.sweep::after{content:""; position:absolute; top:0; bottom:0; width:80px;
  background:linear-gradient(90deg,transparent,rgba(95,212,180,.16),transparent);
  animation:sweep 9s linear infinite}
@keyframes sweep{0%{transform:translateX(-90px)}100%{transform:translateX(620px)}}
.mcap{margin-top:18px; font-family:"JetBrains Mono",monospace; font-size:10.5px;
  letter-spacing:.05em; color:var(--faint); line-height:1.7}
.mcap b{color:var(--ember); font-weight:500}

/* ---- the book's motif: one fixed cut, animated once ----
   Distinct from `.morph` above on purpose. The landing page shows the cut *moving* (the
   polymorphism claim); Opening Book shows a single cut landing (the fragmentation claim).
   Kept as its own component so neither can be changed by accident while editing the other.
   Everything is em-based off one clamp: eleven fixed cells otherwise set the min-content
   width of the whole hero column and silently widen it past a small phone. */
.motif{margin-top:56px; max-width:560px}
.host{display:flex; align-items:stretch; gap:.3em; font-family:"JetBrains Mono",monospace;
  font-weight:500; font-size:clamp(11px,3.2vw,22px)}
.host .cell{width:1.7em; height:2.2em; display:grid; place-items:center; border-radius:5px;
  color:#d9cfc4; background:var(--raised); border:1px solid var(--line2)}
.host .gap{position:relative; width:2px; align-self:stretch; margin:0 .55em;
  background:var(--ember); border-radius:2px; box-shadow:0 0 12px 1px rgba(255,138,61,.55);
  transform:scaleY(0); transform-origin:center; opacity:0}
.host.cut .gap{animation:cut 1.1s .7s var(--ease) forwards}
.host.cut .seg2{animation:shove 1.1s .7s var(--ease) forwards}
.seg,.seg2{display:flex; gap:.3em}
@keyframes cut{to{transform:scaleY(1); opacity:1}}
@keyframes shove{from{transform:translateX(-13px)} to{transform:translateX(0)}}
.scroll{margin-top:48px; font-family:"JetBrains Mono",monospace; font-size:10.5px;
  letter-spacing:.22em; text-transform:uppercase; color:var(--faint)}

/* ============================ SECTIONS ============================ */
section{padding:78px 0; border-top:1px solid var(--line)}
.shead{max-width:1080px; margin:0 auto 40px; padding:0 30px}
.shead .eyebrow{display:block; font-family:"JetBrains Mono",monospace; font-size:10.5px;
  letter-spacing:.22em; text-transform:uppercase; color:var(--ember); margin-bottom:15px}
.shead h2{font-family:"Bricolage Grotesque",sans-serif; font-weight:600;
  font-size:clamp(27px,4.2vw,42px); letter-spacing:-.028em; line-height:1.08; color:var(--bone)}
.shead h2 .it{color:var(--ember-hi)}
.shead h2 .vi{color:var(--violet)}
.shead p{margin-top:16px; max-width:66ch; color:#b6b1a9; font-size:17.5px}

/* ---- figures ----
   Diagrams are hand-authored inline SVG rather than generated: they inherit the palette
   through currentColor and CSS variables, stay sharp at any size, and cost no build step.
   A `min-width` keeps them legible on a phone by scrolling in their own box instead of
   shrinking the type to nothing. */
.fig{max-width:1080px; margin:0 auto; padding:0 30px}
.fig .box{overflow-x:auto; -webkit-overflow-scrolling:touch;
  border:1px solid var(--line); border-radius:14px; background:rgba(15,17,24,.55); padding:26px 24px}
.fig svg{display:block; width:100%; height:auto}
.fig svg.wide{min-width:720px}
.fig svg.mid{min-width:560px}
.figcap{margin-top:14px; font-family:"JetBrains Mono",monospace; font-size:11px; line-height:1.7;
  color:var(--faint); letter-spacing:.02em}
.figcap b{color:var(--dim); font-weight:500}

/* ---- feature grid ---- */
.grid{max-width:1080px; margin:0 auto; padding:0 30px;
  display:grid; grid-template-columns:repeat(3,1fr); gap:16px}
.grid.two{grid-template-columns:repeat(2,1fr)}
.card{position:relative; border:1px solid var(--line); border-radius:14px; padding:24px;
  background:linear-gradient(180deg,rgba(236,231,221,.028),rgba(236,231,221,.006));
  transition:border-color .25s, transform .25s var(--ease)}
.card:hover{border-color:var(--line2); transform:translateY(-2px)}
.card .top{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:14px}
.card h3{font-family:"Bricolage Grotesque",sans-serif; font-size:18px; font-weight:600;
  letter-spacing:-.015em; color:var(--bone)}
.card p{font-size:15px; line-height:1.58; color:#a9a49c; margin:0}
.card p + p{margin-top:9px}
.card .meta{margin-top:14px; font-family:"JetBrains Mono",monospace; font-size:10.5px;
  letter-spacing:.06em; color:var(--faint)}
.card.hl{border-color:var(--violet-dim); background:linear-gradient(180deg,rgba(157,123,255,.06),rgba(157,123,255,.012))}
@media (max-width:900px){ .grid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:620px){ .grid,.grid.two{grid-template-columns:1fr} }

/* ---- the adaptive loop: numbered stages ---- */
.loop{max-width:1080px; margin:0 auto; padding:0 30px; display:grid;
  grid-template-columns:repeat(4,1fr); gap:0}
.lp{position:relative; padding:26px 22px 26px 0}
.lp:not(:last-child)::after{content:""; position:absolute; right:14px; top:34px; width:8px; height:8px;
  border-top:1px solid var(--line2); border-right:1px solid var(--line2); transform:rotate(45deg)}
.lp .n{font-family:"JetBrains Mono",monospace; font-size:11px; font-weight:700; color:var(--violet);
  letter-spacing:.14em; display:block; margin-bottom:11px}
.lp h4{font-family:"Bricolage Grotesque",sans-serif; font-size:16.5px; font-weight:600;
  color:var(--bone); margin-bottom:8px; letter-spacing:-.012em}
.lp p{font-size:14.5px; line-height:1.55; color:#a09b94; margin:0 0 12px}
@media (max-width:900px){
  .loop{grid-template-columns:1fr 1fr}
  .lp:nth-child(2)::after{display:none}
}
@media (max-width:560px){
  .loop{grid-template-columns:1fr}
  .lp::after{display:none!important}
  .lp{padding:20px 0; border-bottom:1px solid var(--line)}
}

/* ---- transports / capability rows ---- */
.rows{max-width:1080px; margin:0 auto; padding:0 30px}
.row{display:grid; grid-template-columns:190px 1fr auto; gap:22px; align-items:start;
  padding:18px 0; border-bottom:1px solid var(--line)}
.row:last-child{border-bottom:0}
.row .nm{font-family:"JetBrains Mono",monospace; font-size:13px; color:var(--bone); font-weight:500}
.row .ds{font-size:15px; color:#a9a49c; line-height:1.55}
@media (max-width:700px){
  .row{grid-template-columns:1fr auto; gap:6px 14px}
  .row .nm{grid-column:1}
  .row .st{grid-column:2; grid-row:1}
  .row .ds{grid-column:1 / -1}
}

/* ============================ CHAPTER LAYOUT ============================
   The original contract, retained so chapter files need no edits. Long-form prose switches
   to Fraunces here and only here. */
.subnav{position:sticky; top:58px; z-index:30; -webkit-backdrop-filter:blur(9px); backdrop-filter:blur(9px);
  background:rgba(8,9,13,.82); border-bottom:1px solid var(--line)}
.subnav .in{max-width:1080px; margin:0 auto; padding:12px 30px; display:flex; align-items:center; gap:22px;
  font-family:"JetBrains Mono",monospace; font-size:10.5px; letter-spacing:.16em; text-transform:uppercase}
.subnav a{color:var(--dim); border:0}
.subnav a:hover{color:var(--ember-hi)}
.subnav .home{color:var(--ember); margin-right:auto}
.subnav .home:hover{color:var(--ember-hi)}
/* A chapter's worth of links in a flex row overflows a phone (chapters run to nine). Scroll
   rather than wrap: wrapping doubles the sticky bar's height and eats the viewport on exactly
   the screens with least of it. `margin-right:auto` on .home is dropped — it would push the
   rest off once scrolling. Verified to stay one row at 320px. */
@media (max-width:700px){
  .subnav .in{overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; gap:18px}
  .subnav .in::-webkit-scrollbar{display:none}
  .subnav a{white-space:nowrap}
  .subnav .home{margin-right:4px}
}

.crest{display:flex; align-items:center; flex-wrap:wrap; gap:13px; margin-bottom:40px}
.crest svg{width:28px;height:28px;flex:none}
.crest .wm{font-family:"JetBrains Mono",monospace; font-size:11.5px; letter-spacing:.3em;
  text-transform:uppercase; color:var(--dim)}
.crest .eco{margin-left:auto; font-family:"JetBrains Mono",monospace; font-size:10.5px;
  letter-spacing:.18em; color:var(--faint)}

/* Same box as `.wrap` including padding — without it this grid runs to the viewport edges on a
   phone and anything drawn at a negative offset gets clipped. Matching also aligns the rail
   with the header and both navs. */
.spread{display:grid; grid-template-columns:150px minmax(0,var(--measure)); gap:46px;
  max-width:1080px; margin:0 auto; padding:0 30px}
.rail{position:relative}
.rail .no{font-family:"JetBrains Mono",monospace; font-weight:700; font-size:28px; color:var(--ember);
  letter-spacing:-.02em; line-height:1}
.rail .piece{font-size:20px; color:var(--ember-dim); margin-bottom:10px; display:block}
.rail .ann{margin-top:15px; font-family:"JetBrains Mono",monospace; font-size:11px; line-height:1.6;
  letter-spacing:.02em; color:var(--dim); border-left:1px solid var(--line2); padding-left:12px}
.rail .ann .s{color:var(--signal)} .rail .ann .b{color:var(--alert)}
.body{min-width:0; font-family:"Fraunces",Georgia,serif; font-size:19px; line-height:1.66}
.body h2{font-family:"Bricolage Grotesque",sans-serif; font-size:clamp(26px,4.2vw,36px);
  font-weight:600; letter-spacing:-.028em; line-height:1.1; margin-bottom:20px; color:var(--bone)}
.body h2 .it{font-style:normal; color:var(--ember-hi)}
.body p{margin:0 0 20px; color:#c2bcb3}
.body p.lead{font-size:21px; color:var(--bone)}

ul{list-style:none; margin:4px 0 22px}
li{position:relative; padding-left:26px; margin-bottom:14px; color:#c2bcb3}
li::before{content:"▸"; position:absolute; left:2px; top:0; font-size:11px; color:var(--ember-dim)}
li b{color:#fff}

.code{font-family:"JetBrains Mono",monospace; font-size:13px; line-height:1.75; color:#cbd3cd;
  background:var(--surface); border:1px solid var(--line); border-radius:12px;
  padding:20px 22px; margin:24px 0; overflow-x:auto; white-space:pre; letter-spacing:0}
.code .k{color:var(--ember-hi)} .code .c{color:var(--faint)} .code .s{color:var(--signal)} .code .b{color:var(--alert)}

blockquote{margin:30px 0; padding:6px 0 6px 24px; border-left:2px solid var(--ember-dim);
  font-family:"Fraunces",Georgia,serif; font-style:italic; font-weight:300;
  font-size:clamp(20px,3vw,26px); line-height:1.34; color:#e2dbd0}

.duo{display:grid; grid-template-columns:1fr 1fr; gap:16px; margin:26px 0}
.chip{background:linear-gradient(180deg,rgba(236,231,221,.028),rgba(236,231,221,.006));
  border:1px solid var(--line); border-radius:13px; padding:22px}
.chip .t{font-family:"JetBrains Mono",monospace; font-size:10px; letter-spacing:.17em;
  text-transform:uppercase; color:var(--signal); display:block; margin-bottom:11px}
.chip h3{font-family:"Bricolage Grotesque",sans-serif; font-size:19px; font-weight:600;
  margin-bottom:8px; letter-spacing:-.015em; color:var(--bone)}
.chip p{font-family:"Bricolage Grotesque",sans-serif; font-size:15px; color:#a9a49c; margin:0; line-height:1.55}

.line-moves{margin:18px 0 4px}
.mv{display:grid; grid-template-columns:46px 1fr; gap:18px; padding:14px 0;
  border-bottom:1px solid var(--line); align-items:baseline}
.mv:last-child{border-bottom:0}
.mv .n{font-family:"JetBrains Mono",monospace; font-size:13px; font-weight:500; color:var(--ember)}
.mv .x{font-size:17px; color:#bfb9b0}
.mv .x .s{font-family:"JetBrains Mono",monospace; font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--signal); margin-left:8px}
.mv.done .n{color:var(--signal)}
.mv.future .n{color:var(--faint)} .mv.future .x{color:var(--dim)}

table.abi{width:100%; border-collapse:collapse; margin:26px 0; font-size:14.5px;
  font-family:"Bricolage Grotesque",sans-serif}
table.abi th{font-family:"JetBrains Mono",monospace; font-size:10px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--signal); text-align:left; font-weight:500;
  padding:0 14px 11px 0; border-bottom:1px solid var(--line2); vertical-align:bottom}
table.abi td{padding:13px 14px 13px 0; border-bottom:1px solid var(--line); color:#a9a49c;
  vertical-align:top; line-height:1.55}
table.abi td:first-child{white-space:nowrap}
table.abi code{font-family:"JetBrains Mono",monospace; font-size:12.5px; color:var(--bone)}
table.abi .req{color:var(--ember); font-family:"JetBrains Mono",monospace; font-size:10px;
  letter-spacing:.1em; text-transform:uppercase}
table.abi .opt{color:var(--faint); font-family:"JetBrains Mono",monospace; font-size:10px;
  letter-spacing:.1em; text-transform:uppercase}
/* A table of prose cannot be made narrow enough for a phone; scroll it in its own box rather
   than letting the page body scroll sideways. */
.scrollx{overflow-x:auto; -webkit-overflow-scrolling:touch}
.scrollx table.abi{min-width:520px}

.note{border-left:2px solid var(--ember-dim); padding:4px 0 4px 22px; margin:26px 0;
  font-family:"Bricolage Grotesque",sans-serif; color:#a9a49c; font-size:16px; line-height:1.6}
.note b{color:var(--ember-hi); font-weight:600}
.warn{border-left-color:var(--alert)}
.warn b{color:var(--alert)}

.steps{counter-reset:step; list-style:none; margin:26px 0}
/* Explicit rather than inheriting `li{position:relative}` above: the counter is absolutely
   positioned and a chapter should not break if that rule moves. */
.steps li{counter-increment:step; position:relative; padding-left:44px; margin-bottom:17px}
.steps li::before{content:counter(step); position:absolute; left:0; top:-1px;
  font-family:"JetBrains Mono",monospace; font-weight:700; font-size:13px; color:var(--ember);
  width:26px; height:26px; display:grid; place-items:center;
  border:1px solid var(--line2); border-radius:50%}

.trio{display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin:26px 0}
@media (max-width:760px){
  .trio,.duo{grid-template-columns:1fr}
  .spread{grid-template-columns:1fr; gap:22px}
  .rail{display:flex; align-items:baseline; gap:14px}
  .rail .piece{margin:0}
  .rail .ann{display:none}
}

/* ============================ FOOTER ============================ */
footer{border-top:1px solid var(--line); padding:56px 0 84px; background:rgba(15,17,24,.4)}
footer .cols{display:grid; grid-template-columns:1.6fr 1fr 1fr; gap:34px; max-width:1080px;
  margin:0 auto; padding:0 30px}
footer h5{font-family:"JetBrains Mono",monospace; font-size:10px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--faint); margin-bottom:14px; font-weight:500}
footer .cols a{display:block; margin-bottom:10px; color:var(--paper); border:0; font-size:15px}
footer .cols a:hover{color:var(--ember-hi)}
footer p{color:#a09b94; max-width:46ch; font-size:15px}
/* Chapter footers are a lighter single row rather than the landing page's column block —
   a reader who has reached the end of a chapter wants the other chapters, not the whole site. */
footer .wrap > p{color:#a09b94; max-width:48ch}
footer .links{display:flex; flex-wrap:wrap; gap:10px 26px; margin:22px 0 0;
  font-family:"JetBrains Mono",monospace; font-size:12.5px}
footer .links a{color:var(--paper); border:0}
footer .links a:hover{color:var(--ember-hi)}
footer .crest{margin-bottom:18px}
/* Two footer shapes, so `.fine` is scoped rather than shared: on the landing page it is a
   direct child and supplies its own gutter; inside a chapter it already sits in `.wrap`, which
   supplies one — an unscoped rule would double the padding and nest a max-width in a max-width. */
footer > .fine{margin:30px auto 0; max-width:1080px; padding:24px 30px 0;
  border-top:1px solid var(--line); font-size:13px; line-height:1.65; color:var(--faint)}
footer .wrap .fine{margin-top:26px; padding-top:22px; border-top:1px solid var(--line);
  font-size:13px; line-height:1.65; color:var(--faint); max-width:60ch}
footer .fine em{color:var(--dim)}
@media (max-width:760px){ footer .cols{grid-template-columns:1fr; gap:26px} }

/* ============================ MOTION ============================ */
.rise{opacity:0; transform:translateY(18px); transition:opacity .8s var(--ease), transform .8s var(--ease)}
.rise.in{opacity:1; transform:none}

@media (prefers-reduced-motion:reduce){
  /* `html{scroll-behavior:smooth}` is motion too — every in-page anchor animates the viewport.
     Suppressing reveals while leaving that on honours the preference only halfway, and smooth
     scrolling is a common vestibular trigger. The morph loop is continuous, so it stops outright
     rather than merely not replaying. */
  html{scroll-behavior:auto}
  .rise{opacity:1;transform:none;transition:none}
  .cells i,.cells i::after,.sweep::after{animation:none!important}
  /* The fracture animates continuously, so it stops outright rather than not replaying.
     It reads correctly frozen: the cracks, the light bleeding onto the dark and the scene
     behind are all static. Sparks are parked part-way up so the drift is still implied. */
  .b-aurora ellipse,.b-lights rect,.b-sparks .s{animation:none!important}
  .b-sparks .s{opacity:.9; offset-distance:44%}
  .cells i.c1,.cells i.c2,.cells i.c3{margin-right:1.55em; border-color:var(--ember); color:var(--ember-hi)}
  /* Show the blades statically so the motif still reads as 'cut' without moving. */
  .cells i.c1::after,.cells i.c2::after,.cells i.c3::after{opacity:1; transform:scaleY(1)}
  .card:hover,.btn:hover{transform:none}
}
