/* 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 ----
   A wall seen head-on, torn open, lit from behind. Inline SVG so it themes from the same palette
   as everything else and stays sharp at any size; the motion is CSS so it can be switched off
   wholesale for prefers-reduced-motion.

   Layer order is load-bearing and not obvious: the glow sits BEHIND the wall so it reads as light
   from the far side, and the rays sit IN FRONT so the light appears to spill toward the viewer.
   Swap those two and the whole thing flattens into a sticker. */
.breach{position:relative; width:100%}
/* Clipped, not visible: the rays are masked to fade before the frame, so nothing needs to
   paint outside — and painting outside made the element overflow the viewport on a phone. */
.breach-svg{display:block; width:100%; height:auto; overflow:hidden}

.b-glow{transform-origin:296px 282px; animation:b-breathe 7s ease-in-out infinite}
.b-core{transform-origin:296px 282px; animation:b-flare 7s ease-in-out infinite}
.b-rays{transform-origin:296px 282px; animation:b-turn 62s linear infinite, b-shimmer 7s ease-in-out infinite}
@keyframes b-breathe{0%,100%{transform:scale(1);opacity:.92}50%{transform:scale(1.055);opacity:1}}
@keyframes b-flare  {0%,100%{transform:scale(.97);opacity:.85}50%{transform:scale(1.05);opacity:1}}
@keyframes b-turn   {to{transform:rotate(360deg)}}
@keyframes b-shimmer{0%,100%{opacity:.72}50%{opacity:1}}

/* Two bricks knocked loose. Small amplitude on purpose — masonry that visibly wobbles reads as
   a cartoon. */
.b-debris .d0{animation:b-drift0 9s ease-in-out infinite}
.b-debris .d1{animation:b-drift1 11s ease-in-out infinite}
@keyframes b-drift0{0%,100%{transform:translate(0,0) rotate(0)}50%{transform:translate(4px,-3px) rotate(2.5deg)}}
@keyframes b-drift1{0%,100%{transform:translate(0,0) rotate(0)}50%{transform:translate(-3px,4px) rotate(-2deg)}}

/* Packets escaping through the gap: out of the light, past the wall, away. Three paths and
   staggered delays so the stream never pulses in lockstep. */
.b-stream .p{offset-path:path("M296 282 L560 214"); offset-rotate:0deg; opacity:0}
.b-stream .p3,.b-stream .p4{offset-path:path("M296 282 L566 306")}
.b-stream .p5,.b-stream .p6{offset-path:path("M296 282 L548 402")}
.b-stream .p0{animation:b-esc 3.4s linear infinite}
.b-stream .p1{animation:b-esc 3.4s linear .49s infinite}
.b-stream .p2{animation:b-esc 3.4s linear .97s infinite}
.b-stream .p3{animation:b-esc 4.1s linear .30s infinite}
.b-stream .p4{animation:b-esc 4.1s linear 1.4s infinite}
.b-stream .p5{animation:b-esc 3.8s linear .80s infinite}
.b-stream .p6{animation:b-esc 3.8s linear 2.1s infinite}
@keyframes b-esc{0%{offset-distance:0%;opacity:0}13%{opacity:1}83%{opacity:1}100%{offset-distance:100%;opacity:0}}

/* ---- 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 breach animates continuously — it has to stop outright, not merely not replay.
     The graphic still reads correctly frozen: the light, the ragged edge and the escaped
     packets are all static geometry. The packets are parked mid-flight so the stream is
     still legible rather than collapsing to a point at the origin. */
  .b-glow,.b-core,.b-rays,.b-debris rect,.b-stream .p{animation:none!important}
  .b-stream .p{opacity:1; offset-distance:58%}
  .b-stream .p1{offset-distance:34%} .b-stream .p2{offset-distance:82%}
  .b-stream .p4{offset-distance:40%} .b-stream .p6{offset-distance:70%}
  .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}
}
