/* ==========================================================================
   THE PORTAL — chapter compositions v2
   One dominant compositional idea per chapter. Cards are used only where an
   information object genuinely is a card; they are never the page primitive.

     CH01  monumental mark on a facade         (ink)
     CH02  a market twin, built in steps       (paper)
     CH03  media as stage, three realities     (ink)
     CH04  a circuit that closes               (paper)
     CH05  narrowing evidence, editorial page  (paper)
     CH06  an unlocking ladder + founder pair  (paper)
     CH07  a load path, closing on an ink plate
   ========================================================================== */

/* ======================================================= CH01 — THESIS ===
   Dominant idea: ONE OBJECT, standing in a field of unknown.

   The hero is THE PORTAL's Spatial State Object: a single commercial volume,
   authored for this page only, read as lit mass rather than as a drawing.
   Its wide face is a stacking of leasable units of unequal size — the same
   spatial grammar the product uses — and each unit carries a state. There are
   no floor labels, no dimension rails and no CAD apparatus anywhere in the
   scene, because none of that is how a real volume is read.

   Six layers, back to front:
     GL       WebGL night atmosphere (light, haze, slow drift)
     FIELD    the ground datum, the light the object throws down, its reflection
     HAZE     the unknown the base of the mass is still standing in
     MASS     the volume: three faces, the unit stacking, the survey sweep
     EVENT    the single line of light that crosses the object when it resolves
     MARKS    ? and ! at unit scale, as signage on a real face
   Each layer answers pointer and scroll at its own rate, so the scene has real
   depth rather than a parallax gimmick. Under reduced motion the scene is a
   still frame in its settled state and the copy is untouched.               */
.hero {
  min-height: 100dvh;
  display: grid; grid-template-rows: 1fr auto;
  padding-block: 62px 0;
  position: relative; overflow: hidden;
}

.scene {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  --mx: 0; --my: 0; --sc: 0;
  background:
    radial-gradient(58% 52% at 68% 44%, rgba(99, 214, 176, 0.15) 0%, rgba(99, 214, 176, 0) 66%),
    radial-gradient(130% 78% at 62% 104%, rgba(22, 30, 27, 0.94) 0%, rgba(8, 9, 8, 1) 68%),
    linear-gradient(180deg, #050706 0%, #0a0f0d 44%, #080908 100%);
}
.scene__gl { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0;
             transition: opacity 1.4s var(--e-soft) 0.15s; }
.scene[data-gl='on'] .scene__gl { opacity: 1; }
.scene__field { position: absolute; inset: 0; width: 100%; height: 100%; }
/* the veil is doing typographic work, not decoration: the copy column is held
   at full contrast, and the object is allowed to stand in its own light */
.scene__veil {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(92deg, rgba(8, 9, 8, 0.97) 0%, rgba(8, 9, 8, 0.94) 30%,
                    rgba(8, 9, 8, 0.62) 44%, rgba(8, 9, 8, 0.06) 60%,
                    rgba(8, 9, 8, 0) 100%),
    linear-gradient(180deg, rgba(8, 9, 8, 0.80) 0%, rgba(8, 9, 8, 0.06) 24%,
                    rgba(8, 9, 8, 0.10) 60%, rgba(8, 9, 8, 0.92) 97%);
}

/* --- THE FIELD ---------------------------------------------------------
   Ground before object: the datum the volume is measured from, the pool of
   light it stands in, the light it throws back down, and the haze its base
   is still standing in.                                                    */
.scene__field .so-datum  { stroke: rgba(99, 214, 176, 0.24); stroke-width: 1.1; }
.scene__field .so-glow   { fill: url(#so-glow); }
.scene__field .so-cast   { fill: url(#so-cast); }
.scene__field .so-pool   { fill: url(#so-pool); }
.scene__field .so-pool--tight { opacity: 0.85; }
.scene__field .so-refl   { fill: url(#so-refl); filter: url(#so-soft); }
.scene__field .so-haze__b { fill: url(#so-haze); }

/* the market around it: other volumes, unread, held back in the haze. They are
   why the object matters, so they are present — as far mass, never as skyline */
.scene__field .so-fv__l { fill: #1b2321; }
.scene__field .so-fv__r { fill: #101615; }
.scene__field .so-fv__t { fill: #242d2a; }
.scene__field .so-fv    { opacity: calc(0.80 - var(--i) * 0.10); }

/* --- THE MASS ----------------------------------------------------------
   Solid before line. The silhouette is the object's weight; the faces are its
   material; the units are what the market actually consists of.            */
.scene__field .so-sil    { fill: #060908; }
.scene__field .so-face   { stroke: none; }
.scene__field .so-face--l { fill: url(#so-l); }
.scene__field .so-face--r { fill: url(#so-r); }
/* the plinth is ground-lit and the cap takes the sky: the same object read at
   its two extremes, which is what gives a volume its weight */
.scene__field .so-pl--t  { fill: url(#so-plt); }
.scene__field .so-pl--l  { fill: url(#so-plb); }
.scene__field .so-pl--r  { fill: #060908; }
.scene__field .so-cap--t { fill: url(#so-t); }
.scene__field .so-cap--l { fill: #1a221f; }
.scene__field .so-cap--r { fill: #0a0e0d; }

/* a unit is a recess in the face until evidence puts light in it */
.scene__field .so-u__b   { fill: #080c0b; stroke: rgba(244, 242, 236, 0.085);
                           stroke-width: 0.9; }
.scene__field .so-u__lit { stroke: none; }
.scene__field .so-u[data-s='unknown'] .so-u__lit { fill: none; }
.scene__field .so-u[data-s='obs']   .so-u__lit { fill: url(#so-obs); }
.scene__field .so-u[data-s='ok']    .so-u__lit { fill: url(#so-ok); }
.scene__field .so-u[data-s='focus'] .so-u__lit { fill: url(#so-foc); }
/* the return face is in shadow: same stacking, a fraction of the light */
.scene__field .so-u__hd { fill: none; stroke: rgba(214, 245, 233, 0.30); stroke-width: 1.1; }
.scene__field .so-u[data-s='ok']    .so-u__hd { stroke: rgba(120, 226, 189, 0.50); }
.scene__field .so-u[data-s='focus'] .so-u__hd { stroke: rgba(232, 255, 247, 0.92); }
.scene__field .so-u--r .so-u__b   { stroke-opacity: 0.045; }
.scene__field .so-u--r .so-u__lit { opacity: 0.17; }
.scene__field .so-u--r .so-u__hd  { stroke-opacity: 0.10; }
.scene__field .so-u--focus        { filter: drop-shadow(0 0 15px rgba(99, 214, 176, 0.6)); }

.scene__field .so-band  { fill: url(#so-band); }
.scene__field .so-sheen { fill: url(#so-sheen); }
.scene__field .so-sheen--drift { fill: url(#so-drift); opacity: 0; }

.scene__field .so-e { fill: none; stroke: rgba(244, 242, 236, 0.26); stroke-width: 1;
                      stroke-linejoin: round; stroke-linecap: round; }
.scene__field .so-e--near  { stroke: rgba(244, 242, 236, 0.54); stroke-width: 1.5; }
.scene__field .so-e--top   { stroke: rgba(244, 242, 236, 0.42); stroke-width: 1.2; }
.scene__field .so-e--topn  { stroke: rgba(244, 242, 236, 0.20); }
.scene__field .so-e--base  { stroke: rgba(99, 214, 176, 0.44); stroke-width: 1.2; }
.scene__field .so-e--left  { stroke: rgba(244, 242, 236, 0.30); }
.scene__field .so-e--right { stroke: rgba(244, 242, 236, 0.16); }

/* --- THE EVENT ---------------------------------------------------------- */
.scene__field .so-ray   { fill: none; stroke: url(#so-ray); stroke-width: 10;
                          stroke-linecap: round; }
.scene__field .so-ray--hair { stroke: rgba(255, 255, 255, 0.8); stroke-width: 1.3; }
.scene__field .so-flare { fill: url(#so-flare); }

/* --- THE STATE MARKS ---------------------------------------------------- */
.scene__field .so-mk--q   { color: #63d6b0; }
.scene__field .so-mk--b   { color: #f4f2ec; }
.scene__field .so-mk--res { filter: drop-shadow(0 0 14px rgba(255, 255, 255, 0.45)); }
.scene__field .so-mk--swap { opacity: 0; }

/* depth: each band answers the pointer and the scroll at its own rate */
.scene .so-field  { --depth: 0.16; }
.scene .so-far    { --depth: 0.20; }
.scene .so-haze   { --depth: 0.24; }
.scene .so-mass   { --depth: 0.52; }
.scene .so-event  { --depth: 0.66; }
.scene .so-marks  { --depth: 0.86; }

.scene .so-field, .scene .so-far, .scene .so-haze, .scene .so-mass,
.scene .so-event, .scene .so-marks {
  translate: calc(var(--mx) * var(--depth) * -24px)
             calc(var(--my) * var(--depth) * -13px - var(--sc) * var(--depth) * 44px);
}

/* --- THE AUTHORED ENTRY -------------------------------------------------
   portal.js publishes the beat number as --b on the scene. Every layer below
   gates itself on --b with clamped arithmetic, so the beat is a step and the
   transition is what makes it a movement. Nothing here reads scroll.
     b1 the field        b2 the mass rises          b3 the units are set out
     b4 evidence is read b5 the volume is set true  b6 one state resolves
     b7 the composition settles                                             */
.js .scene .so-field {
  opacity: clamp(0, calc(var(--b, 0) * 1), 1);
  transition: opacity 1.7s var(--e-soft);
}
.js .scene .so-datum {
  transform: scaleX(clamp(0, calc(var(--b, 0) * 1), 1));
  transform-origin: 1168px 605px;
  transition: transform 1.5s var(--e-inout);
}
.js .scene .so-haze {
  opacity: clamp(0, calc((var(--b, 0) - 0.3) * 1.2), 1);
  transition: opacity 1.9s var(--e-soft) 0.15s;
}
.js .scene .so-refl {
  opacity: clamp(0, calc((var(--b, 0) - 1.6) * 0.9), 1);
  transition: opacity 1.9s var(--e-soft);
}

/* b2 the mass arrives as weight before it is anything else */
.js .scene .so-mass {
  opacity: clamp(0, calc((var(--b, 0) - 0.9) * 1.4), 1);
  transform: translateY(calc(26px * clamp(0, calc((2.4 - var(--b, 0)) * 1), 1)))
             scale(calc(0.982 + 0.018 * clamp(0, calc((var(--b, 0) - 0.9) * 1), 1)));
  transform-origin: 1168px 595px;
  transition: opacity 1.2s var(--e-out), transform 1.6s var(--e-out);
}
/* the volume gains material a beat after it gains weight */
.js .scene .so-face--l, .js .scene .so-face--r {
  opacity: clamp(0, calc((var(--b, 0) - 1.5) * 1.3), 1);
  transition: opacity 1.3s var(--e-soft);
}

/* b3 the stacking is set out: every unit present, none of them known */
.js .scene .so-u {
  opacity: clamp(0, calc((var(--b, 0) - 2.0) * 1.7), 1);
  transition: opacity 0.85s var(--e-out);
  transition-delay: calc(var(--e) * 30ms);
}
/* b4 evidence is read: a unit takes its state as the survey passes it */
.js .scene .so-u__lit {
  opacity: 0;
  transition: opacity 1s var(--e-out);
  transition-delay: calc(var(--e) * 56ms);
}
.js .scene .so-u[data-s='obs'] .so-u__lit {
  opacity: clamp(0, calc((var(--b, 0) - 3.15) * 1.2), 0.9);
}
.js .scene .so-u[data-s='ok'] .so-u__lit {
  opacity: clamp(0, calc((var(--b, 0) - 3.15) * 1.3), 1);
}
/* the resolving unit holds until the event actually reaches it */
.js .scene .so-u__hd {
  opacity: clamp(0, calc((var(--b, 0) - 3.15) * 1.3), 1);
  transition: opacity 1s var(--e-out);
  transition-delay: calc(var(--e) * 56ms);
}
.js .scene .so-u[data-s='focus'] .so-u__hd {
  opacity: clamp(0, calc((var(--b, 0) - 5.35) * 2.4), 1);
  transition-delay: 0.16s;
}
.js .scene .so-far {
  opacity: clamp(0, calc((var(--b, 0) - 0.5) * 0.85), 1);
  transition: opacity 2.1s var(--e-soft) 0.2s;
}
.js .scene .so-u[data-s='focus'] .so-u__lit {
  opacity: clamp(0, calc((var(--b, 0) - 5.35) * 2.4), 1);
  transition-delay: 0.16s;
}
.js .scene .so-u--focus { filter: none; }
.js .scene[data-beat='6'] .so-u--focus,
.js .scene[data-beat='7'] .so-u--focus {
  filter: drop-shadow(0 0 15px rgba(99, 214, 176, 0.6));
}

/* the survey bands rise across the market face while the reading happens */
.js .scene .so-band {
  opacity: clamp(0, min(calc((var(--b, 0) - 2.9) * 2.2), calc((5.7 - var(--b, 0)) * 1.1)), 1);
  transform: translateY(calc(70px - 660px * clamp(0, calc((var(--b, 0) - 2.9) * 0.55), 1)));
  transition: opacity 0.6s var(--e-out), transform 2s var(--e-inout);
  transition-delay: calc(var(--i) * 190ms);
}

/* b5 the volume is set true: the edges close and the top takes the sky */
.js .scene .so-e {
  stroke-dasharray: 760;
  stroke-dashoffset: calc(760 * (1 - clamp(0, calc((var(--b, 0) - 4.15) * 1.5), 1)));
  transition: stroke-dashoffset 1.15s var(--e-inout);
  transition-delay: calc(var(--i) * 72ms);
}
.js .scene .so-cap {
  opacity: clamp(0, calc((var(--b, 0) - 4.2) * 1.4), 1);
  transform: translateY(calc(-7px * clamp(0, calc((4.6 - var(--b, 0)) * 1), 1)));
  transition: opacity 1.2s var(--e-soft), transform 1.2s var(--e-out);
}
.js .scene .so-pl {
  opacity: clamp(0, calc((var(--b, 0) - 1.2) * 1.4), 1);
  transition: opacity 1.2s var(--e-soft);
}
.js .scene .so-sheen {
  opacity: clamp(0, calc((var(--b, 0) - 4.3) * 1.4), 1);
  transition: opacity 1.3s var(--e-soft);
}

/* b6 the resolution: one line of light crosses the whole object, once */
.js .scene .so-ray {
  opacity: clamp(0, min(calc((var(--b, 0) - 5.3) * 3.4), calc((6.85 - var(--b, 0)) * 1.6)), 1);
  transform: scaleX(clamp(0.06, calc((var(--b, 0) - 5.3) * 2), 1));
  transform-origin: 1094px 323px;
  transition: opacity 0.55s var(--e-out), transform 1.1s var(--e-out);
}
.js .scene .so-flare {
  opacity: clamp(0, min(calc((var(--b, 0) - 5.35) * 3), calc((6.9 - var(--b, 0)) * 1.5)), 1);
  transition: opacity 0.8s var(--e-soft);
}
.js .scene .so-flare--hold {
  opacity: clamp(0, calc((var(--b, 0) - 5.5) * 1.8), 0.62);
  transition: opacity 1.2s var(--e-soft) 0.25s;
}

/* b3 the unknown states are set; b6 the one the evidence converges on resolves */
.js .scene .so-mk {
  opacity: clamp(0, calc((var(--b, 0) - 2.5) * 2), 1);
  transform: translateY(calc(8px * clamp(0, calc((3.1 - var(--b, 0)) * 1), 1)));
  transform-box: fill-box; transform-origin: 50% 100%;
  transition: opacity 0.75s var(--e-out), transform 0.85s var(--e-out);
  transition-delay: calc(var(--i) * 105ms);
}
.js .scene .so-mk--swap {
  opacity: clamp(0, min(calc((var(--b, 0) - 2.5) * 2), calc((5.9 - var(--b, 0)) * 1.7)), 1);
}
.js .scene .so-mk--res {
  opacity: clamp(0, calc((var(--b, 0) - 5.5) * 2.2), 1);
  transition-delay: 0.2s;
}

/* the thesis sentence no longer carries the marks inline — it reads as Korean
   and the object carries the ? -> ! language. The lede's emphasis is still
   lit by the same moment, so headline and scene remain one statement. */
.js .hero .hero__title { transition: text-shadow 0.9s var(--e-out); }

/* --- AMBIENT LIFE: the object keeps operating after it has settled ------
   Every animation below runs only in a settled, on-screen scene (--amb). */
@keyframes poolBreathe  { 0%, 100% { opacity: 0.80; } 50% { opacity: 1; } }
@keyframes confidence   { 0%, 100% { opacity: 0.72; } 50% { opacity: 1; } }
@keyframes lightDrift   { 0% { opacity: 0; transform: translate(-330px, 96px); }
                          22% { opacity: 1; }
                          78% { opacity: 1; }
                          100% { opacity: 0; transform: translate(330px, -96px); } }
@keyframes hazeDrift    { 0%, 100% { transform: translateX(0); opacity: 0.72; }
                          50% { transform: translateX(26px); opacity: 1; } }
@keyframes evTick       { 0%, 86%, 100% { opacity: 0; } 91% { opacity: 0.5; } }
@keyframes markFloat    { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }

.js .scene[data-scene='settled'] .so-pool {
  animation: poolBreathe 10s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
/* light drifts across the market face, the way it moves over a real facade */
.js .scene[data-scene='settled'] .so-sheen--drift {
  animation: lightDrift 16s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
.js .scene[data-scene='settled'] .so-haze__b {
  animation: hazeDrift calc(15s + var(--i) * 3.5s) var(--e-soft) infinite;
  animation-delay: calc(var(--i) * -2600ms);
  animation-play-state: var(--amb, paused);
}
/* the resolved unit holds a slow confidence pulse */
.js .scene[data-scene='settled'] .so-u--focus .so-u__lit,
.js .scene[data-scene='settled'] .so-flare--hold {
  animation: confidence 6.8s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
/* a unit that is still unverified goes on receiving the occasional tick */
.js .scene[data-scene='settled'] .so-u[data-s='unknown'] .so-u__b {
  stroke: rgba(99, 214, 176, 0.085);
}
.js .scene[data-scene='settled'] .so-u[data-s='unknown'] .so-u__lit {
  fill: rgba(99, 214, 176, 0.16);
  animation: evTick calc(14s + var(--e) * 2.4s) linear infinite;
  animation-delay: calc(var(--e) * -3100ms);
  animation-play-state: var(--amb, paused);
}
.js .scene[data-scene='settled'] .so-mk {
  animation: markFloat 11s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
.js .scene[data-scene='settled'] .so-mk--res { animation-delay: -3.4s; }

/* The copy holds the left half at full contrast; the right half of the frame
   is left to the object, which is where the volume stands. */
.hero__in {
  display: grid; align-content: center;
  padding-block: clamp(1.6rem, 3.5vw, 3rem);
}
/* Korean measure in rem, never ch: ch sizes to the Latin digit and would cut
   the thesis line far too short */
.hero__lead { max-width: min(100%, 45rem); }

.hero__eyebrow { margin-bottom: clamp(1.2rem, 2.4vw, 1.9rem); }
.hero__title { margin-bottom: clamp(1.5rem, 2.8vw, 2.2rem); }
.hero__lede { margin-bottom: clamp(1.6rem, 3vw, 2.4rem); }

/* the drawing key, set along the bottom datum of the frame the way a key sits
   under an elevation: the two marks, the travel between them, the boundary */
.fieldkey {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; gap: clamp(1rem, 2.4vw, 2.4rem);
  padding-bottom: clamp(1.2rem, 2.4vw, 1.8rem);
}
.fieldkey__l { display: flex; flex-wrap: wrap; align-items: center;
               gap: clamp(0.9rem, 2.2vw, 2rem); }
.fieldkey__l > div { display: grid; gap: 0.12rem; }
.fieldkey__l dt { display: flex; align-items: center; gap: 0.62rem;
                  font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; color: var(--fg); }
.fieldkey__l dt .mark { height: clamp(26px, 2.6vw, 34px); }
.fieldkey__l dd { font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh);
                  padding-left: calc(clamp(26px, 2.6vw, 34px) * 0.6 + 0.62rem); }
.fieldkey__go {
  display: flex; align-items: center; gap: 0.62rem;
  font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--text-secondary);
}
.fieldkey__go svg { width: clamp(34px, 4vw, 52px); height: auto; overflow: visible; flex: none; }
.fieldkey__go svg path { fill: none; stroke: currentColor; stroke-width: 1.6;
                         stroke-linecap: round; stroke-linejoin: round; }
.fieldkey__n {
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary);
  max-width: 44em; padding-left: clamp(1rem, 2vw, 1.6rem);
  border-left: 1px solid var(--rule);
}

/* the closing plate reuses the drawn arrow at monumental scale */
.monument__arrow {
  display: grid; justify-items: center; gap: 0.55rem;
  color: var(--fg-2); font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em;
  text-align: center; white-space: nowrap;
}
.monument__arrow svg { width: clamp(38px, 5.4vw, 74px); height: auto; overflow: visible; }
.monument__arrow svg path { fill: none; stroke: currentColor; stroke-width: 1.6;
                            stroke-linecap: round; stroke-linejoin: round; }

/* fast facts — a dimension strip along the bottom datum, not cards */
.facts {
  border-top: 1px solid var(--rule-2);
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
}
.facts > div {
  padding: clamp(1.1rem, 2.2vw, 1.65rem) clamp(0.9rem, 1.8vw, 1.5rem)
           clamp(1.4rem, 2.8vw, 2.1rem) 0;
  display: grid; gap: 0.34rem; align-content: start;
  position: relative;
}
.facts > div + div { padding-left: clamp(0.9rem, 1.8vw, 1.5rem); }
.facts > div + div::before {
  content: ''; position: absolute; left: 0; top: 1.35rem; bottom: 1.35rem;
  width: 1px; background: var(--rule);
}
.facts dt { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.14em; color: var(--text-tertiary); }
.facts dd { font-size: var(--t4-fs); font-weight: 600; color: var(--fg); line-height: var(--t4-lh); }
.facts .note { font-size: var(--t5-fs); line-height: var(--t5-lh); }

/* Mobile is composed on purpose, not squeezed: the field reframes onto the
   monumental pair at the centre of the viewBox, the veil takes over the top
   and bottom so the copy and the fact strip stay first-class, and the drawing
   key becomes a full-width block under the actions. */
@media (max-width: 1023px) {
  .hero__lead { max-width: 100%; }
  .fieldkey { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .fieldkey__n { padding-left: 0; border-left: 0; padding-top: 0.9rem;
                 border-top: 1px solid var(--rule); }
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .facts > div:nth-child(3) { padding-left: 0; }
  .facts > div:nth-child(3)::before { display: none; }
  .facts > div:nth-child(n+3) { border-top: 1px solid var(--rule); }
  /* the object is given its own band above the thesis; portal.js reframes the
     drawing to match, so nothing is cropped (see FIELD / frameHero) */
  .hero { min-height: 0; }
  .scene { inset: 58px 0 auto 0; height: clamp(380px, 48vw, 500px); }
  .hero__in { padding-top: calc(58px + clamp(380px, 48vw, 500px) - 34px); }
  .scene__veil {
    background:
      linear-gradient(180deg, rgba(10, 11, 10, 0.80) 0%, rgba(10, 11, 10, 0.12) 24%,
                      rgba(10, 11, 10, 0.16) 62%, rgba(10, 11, 10, 0.96) 100%);
  }
}
/* A portrait viewport cannot hold the whole object and the copy at once, so the
   phone gets a different composition rather than a squeezed one: the frame moves
   onto the mass itself (portal.js frameHero). The volume, its unit stacking,
   the states and the resolution event are all still there — the object is just
   seen closer. The metaphor is unchanged; only the framing is. */
@media (max-width: 620px) {
  .scene { inset: 58px 0 auto 0; height: clamp(340px, 92vw, 400px); }
  .hero__in { padding-top: calc(58px + clamp(340px, 92vw, 400px) - 54px); }
  /* the object loses nothing on a phone: it is reframed, not stripped. Only
     the ground reflection is dropped, because at this height it lands under
     the copy rather than under the mass. */
  .scene .so-refl { display: none; }
  .scene__veil {
    background:
      linear-gradient(180deg, rgba(10, 11, 10, 0.74) 0%, rgba(10, 11, 10, 0.08) 26%,
                      rgba(10, 11, 10, 0.14) 64%, rgba(10, 11, 10, 0.97) 100%);
  }
  .fieldkey__l dd, .fieldkey__go { padding-left: 0; }
}

@media (max-width: 560px) {
  .facts { grid-template-columns: minmax(0, 1fr); }
  .facts > div + div { padding-left: 0; border-top: 1px solid var(--rule); }
  .facts > div + div::before { display: none; }
}

/* ================================================== CH02 — TWO ENGINES ===
   Dominant idea: two engines of equal weight filling one shared market model,
   uncovered as discrete scroll steps. */
.gapstate {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.6rem, 4vw, 3.5rem);
  padding-bottom: var(--gap-depth);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--gap-depth);
}
.gapstate h3 { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; margin-bottom: 0.55rem; }
.gapstate p { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
@media (max-width: 760px) { .gapstate { grid-template-columns: minmax(0, 1fr); gap: 1.8rem; } }

/* the two engines: equal weight, told apart by which side of the twin they
   work from. Their operational steps now uncover one at a time (see CH02
   MARKET TWIN below); the circuit that used to carry packets on wires and a
   captioned rectangle for the shared asset has been removed. */
.engine { display: grid; align-content: start; gap: 1.1rem; padding-top: 0.4rem; }
.engine__id {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.7rem;
}
.engine__id .tag { color: var(--mark); }
.engine__id .chip { font-size: var(--t6-fs); line-height: var(--t6-lh); }
.engine__name { font-family: var(--serif); font-weight: 500;
                font-size: var(--t2-fs); line-height: var(--t2-lh); }
.engine__lede { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }

/* ------------------------------------------------- DATA CONSTRUCTION (CH02)
   The same information twice: as it arrives, and as the two engines can use
   it. Scroll pulls the loose fragments into line and assembles the four
   objects. Both columns are readable at rest, so nothing depends on motion. */
.build__stage {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.05fr);
  gap: clamp(1rem, 2.4vw, 2rem); align-items: center;
  /* the loose fragments are offset outward before they are assembled; clip on
     the x axis so that offset can never become page overflow */
  overflow-x: clip;
}
.build__cap { display: block; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.14em;
              color: var(--text-tertiary); margin-bottom: 0.9rem; }
.build__raw ul { display: grid; gap: 0.5rem; }
.build__raw li {
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary);
  padding: 0.62rem 0.8rem; background: var(--bg-2);
  border: 1px dashed var(--rule-2);
  transition: transform 0.8s var(--e-out), border-color 0.6s var(--e-out),
              color 0.6s var(--e-out), opacity 0.6s var(--e-out);
  transition-delay: calc(var(--s) * 40ms);
}
/* the scattered start state exists only where JS can resolve it */
.js .build__raw li {
  transform: translate(calc(var(--x) * var(--u, 1)), calc(var(--y) * var(--u, 1)))
             rotate(calc(var(--r) * var(--u, 1)));
}
.build[data-assembled='true'] .build__raw li {
  --u: 0; border-style: solid; border-color: var(--rule); color: var(--fg-4); opacity: 0.62;
}
.build__arrow { display: grid; justify-items: center; gap: 0.5rem;
                font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.12em; color: var(--text-secondary); }
.build__arrow svg { width: clamp(34px, 5vw, 64px); height: auto; overflow: visible; }
.build__arrow svg path { fill: none; stroke: currentColor; stroke-width: 1.6;
                         stroke-linecap: round; stroke-linejoin: round; }
.build__out { border: 1px solid var(--rule-2); background: var(--bg-2); }
.build__out ul { display: grid; }
.build__out .build__cap { padding: 0.8rem 1rem 0; margin-bottom: 0; }
.build__out li {
  display: grid; grid-template-columns: 5.2em minmax(0, 1fr);
  gap: 0.2rem clamp(0.7rem, 1.6vw, 1.1rem); align-items: baseline;
  padding: 0.8rem 1rem; border-top: 1px solid var(--rule);
  transition: opacity 0.6s var(--e-out), transform 0.6s var(--e-out);
  transition-delay: calc(var(--s) * 110ms);
}
.js .build__out li { opacity: 0.28; transform: translateY(8px); }
.build__out li:first-of-type { border-top: 1px solid var(--rule-2); }
.build[data-assembled='true'] .build__out li { opacity: 1; transform: none; }
.build__out i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; color: var(--mark);
                letter-spacing: 0.02em; }
.build__out b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.build__out li > span { grid-column: 2; font-size: var(--t4-fs); color: var(--text-secondary); line-height: var(--t4-lh); }
@media (max-width: 900px) {
  .build__stage { grid-template-columns: minmax(0, 1fr); }
  .build__arrow { grid-auto-flow: column; justify-content: start; align-items: center; }
  .build__arrow svg { transform: rotate(90deg); width: 40px; }
  .build__out li { grid-template-columns: minmax(0, 1fr); }
  .build__out li > span { grid-column: 1; }
}
/* narrow viewports have no side room to scatter into: keep the gesture, halve
   the travel */
@media (max-width: 620px) { .js .build__raw li { --u: 0.4; } }

/* ================================================ CH03 — PRODUCT THEATRE ===
   Dominant idea: media as stage. Three product realities occupy one
   controlled stage; the maturity placard is pinned and always visible.     */

/* A. physical -> digital, drawn as an elevation */
.elevation {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: clamp(1.6rem, 3.6vw, 3.4rem);
  align-items: start;
}
.signpost {
  display: grid; gap: 1.15rem; align-content: start;
  padding: clamp(1.2rem, 2.6vw, 1.9rem);
  border: 1px solid var(--rule-2); background: var(--bg-2);
  position: relative;
}
.signpost::before {
  content: ''; position: absolute; inset: 6px; border: 1px solid var(--rule);
  pointer-events: none;
}
.signpost__qr {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 1.1rem; align-items: center;
}
/* 33 modules incl. the quiet zone: at 172px each module is ~5.2 CSS px,
   which is the size a phone camera reads reliably at arm's length */
.signpost__qr svg { width: clamp(132px, 15vw, 172px); height: auto;
                    background: #fff; padding: 7px; }
.signpost__url {
  display: inline-block; font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--mark);
  border: 1px solid var(--mark-line); background: var(--mark-wash);
  padding: 0.34rem 0.56rem; word-break: break-all;
}

/* the 5-step rail, drawn as floor datums */
.floors { display: grid; }
.floors li {
  display: grid; grid-template-columns: clamp(74px, 8vw, 96px) minmax(0, 1fr);
  gap: 0.3rem clamp(0.9rem, 2vw, 1.4rem);
  padding: clamp(0.9rem, 1.8vw, 1.15rem) 0;
  border-top: 1px solid var(--rule);
  position: relative;
}
.floors li:first-child { border-top: 1px solid var(--rule-2); }
.floors li:last-child { border-bottom: 1px solid var(--rule-2); }
.floors__k { display: grid; gap: 0.12rem; }
.floors__k b { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; color: var(--mark); }
.floors__k small { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.16em; color: var(--text-tertiary); }
.floors__v b { display: block; font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; margin-bottom: 0.2rem; }
.floors__v span { font-size: var(--t4-fs); color: var(--text-secondary); line-height: var(--t4-lh); }

@media (max-width: 900px) {
  .elevation { grid-template-columns: minmax(0, 1fr); }
  .floors li { grid-template-columns: minmax(0, 1fr); gap: 0.25rem; }
  .floors__k { display: flex; align-items: baseline; gap: 0.55rem; }
}

/* B. the two marks — a specimen pair, not tabbed cards */
.specimens {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.4rem, 3vw, 2.6rem);
}
.specimen { display: grid; gap: 1.1rem; align-content: start; }
.specimen__head { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1rem; align-items: center; }
.specimen__head .mark { height: clamp(44px, 5.4vw, 66px); }
.specimen__head b { display: block; font-family: var(--serif); font-weight: 500;
                    font-size: var(--t2-fs); line-height: var(--t2-lh); }
.specimen__head span { font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary); }
.specimen__screen {
  border: 1px solid var(--rule-2); background: var(--bg-2); padding: 0.8rem;
  display: grid; gap: 0.6rem;
}
.specimen__bar { display: flex; justify-content: space-between; gap: 0.5rem;
                 font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-secondary); }
.specimen__title { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.srow {
  display: flex; align-items: center; justify-content: space-between; gap: 0.7rem;
  padding: 0.5rem 0; border-top: 1px solid var(--rule); font-size: var(--t5-fs); line-height: var(--t5-lh);
}
.srow:first-of-type { border-top: 0; }
.srow > span:first-child { color: var(--text-secondary); }
.st { font-size: var(--t6-fs); line-height: var(--t6-lh); padding: 0.14rem 0.42rem; border: 1px solid var(--rule-2);
      color: var(--fg-2); white-space: nowrap; }
.st--ok { color: var(--mark); border-color: var(--mark-line); background: var(--mark-wash); }
.st--field { color: #c9a24a; border-color: rgba(201,162,74,0.36); }
[data-material='paper'] .st--field { color: #8a6410; border-color: rgba(138,100,16,0.34); }
.st--none { color: var(--text-tertiary); }
.deflist { display: grid; }
.deflist > div {
  display: grid; grid-template-columns: 6.2em minmax(0, 1fr); gap: 0.8rem;
  padding: 0.56rem 0; border-top: 1px solid var(--rule); font-size: var(--t4-fs); line-height: var(--t4-lh);
}
.deflist > div:first-child { border-top: 1px solid var(--rule-2); }
.deflist dt { color: var(--text-secondary); }
.deflist dd { color: var(--fg-2); }
@media (max-width: 860px) {
  .specimens { grid-template-columns: minmax(0, 1fr); }
  .deflist > div { grid-template-columns: minmax(0, 1fr); gap: 0.1rem; }
}

/* C. THE PRODUCT STAGE — one theatre, three realities
   The old composition read as a tabbed media component: three buttons over a
   video rectangle over a text column. It is now a stage.
     the TRACK  a drawn maturity axis with three stations and one carriage that
                travels to the act in play. Travel, not selection.
     the STAGE  a lit volume with a floor and side masking; the plate sits
                inside it instead of being the whole thing
     the TRUTH  a bar that belongs to the act, sticky inside the stage, so
                maturity and the evidence boundary cannot scroll away
   Acts exchange along the axis of travel with a depth change, so the investor
   feels which direction they moved between 현재 / 검증 / 목표.               */
.theatre { display: grid; gap: clamp(1.1rem, 2.2vw, 1.7rem); }

.track {
  --car: 0;
  position: relative; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  padding-top: 1.5rem;
}
.track__axis {
  position: absolute; top: 0.62rem; left: 0; right: 0; height: 1px;
  background: var(--rule-2);
}
/* the travelled part of the axis */
.track__axis::before {
  content: ''; position: absolute; inset: 0; background: var(--mark-line);
  transform: scaleX(calc((var(--car) + 1) / 3)); transform-origin: left;
  transition: transform 0.62s var(--e-inout);
}
/* the carriage: a marker that stops at the station in play */
.track__car {
  position: absolute; top: -3px; left: 0; width: 33.3333%; height: 7px;
  transform: translateX(calc(var(--car) * 100%));
  transition: transform 0.62s var(--e-inout);
}
.track__car::before {
  content: ''; position: absolute; left: 0; top: 0; width: 28px; height: 7px;
  background: var(--mark);
}
.track__st {
  text-align: left; cursor: pointer; position: relative;
  padding: 0 clamp(0.7rem, 1.4vw, 1.1rem) 0 0;
  display: grid; gap: 0.28rem; align-content: start;
  color: var(--text-secondary);
  transition: color var(--t-fast) var(--e-out);
}
.track__st::before {
  content: ''; position: absolute; left: 0; top: -1.5rem; width: 1px; height: 0.62rem;
  background: var(--rule-2);
  transition: background var(--t-fast) var(--e-out), height var(--t-fast) var(--e-out);
}
.track__st::after {
  content: ''; position: absolute; left: -3px; top: calc(-1.5rem + 0.62rem - 3px);
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--rule-3);
  transition: background var(--t-fast) var(--e-out), border-color var(--t-fast) var(--e-out);
}
.track__st[aria-selected='true'] { color: var(--fg); }
.track__st[aria-selected='true']::before { background: var(--mark); }
.track__st[aria-selected='true']::after { background: var(--mark); border-color: var(--mark); }
/* gated: a tap on a touch screen fires a hover that then sticks */
@media (hover: hover) and (pointer: fine) {
  .track__st:hover { color: var(--fg-2); }
}
.track__st i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.16em;
               color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.track__st[aria-selected='true'] i { color: var(--mark); }
.track__st b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.track__st span { font-size: var(--t4-fs); line-height: var(--t4-lh); }

.act__truth b { color: var(--fg); font-weight: 600; }
.act__dot { width: 7px; height: 7px; border-radius: 50%; flex: none;
            background: var(--placard, #63d6b0); }
.act__sep { width: 1px; height: 0.9rem; background: var(--rule-2); }
.act__ix { margin-left: auto; font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-tertiary);
           font-variant-numeric: tabular-nums; letter-spacing: 0.1em; }

.plate { position: relative; border: 1px solid var(--rule-2); background: #000;
         box-shadow: 0 26px 60px -30px rgba(0, 0, 0, 0.9); }
.plate video { width: 100%; height: auto; aspect-ratio: 16 / 9; display: block; }
.plate__foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem;
  padding: 0.72rem 0.9rem; border-top: 1px solid var(--rule); background: var(--bg-2);
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary);
}
.plate__foot b { color: var(--fg-2); font-weight: 600; }

.actbody { display: grid; gap: 1rem; align-content: start; }
.actbody__t { font-family: var(--serif); font-weight: 500;
              font-size: var(--t2-fs); line-height: var(--t2-lh); }
.actbody__seq { display: flex; flex-wrap: wrap; gap: 0.34rem; }


/* ========================================================= CH02 MARKET TWIN
   Discrete scroll-step storytelling. The step is a threshold, never a scrub:
   portal.js publishes --st (0..4) and data-step, and every layer below gates
   itself on --st with clamped arithmetic. Without JS --st stays 4, so every
   step is uncovered and the whole argument is readable.                    */
.twin { --st: 4; }
.js .twin { --st: 0; }

.twin__track { position: relative; }
.js .twin__track { height: 360vh; }
.js .twin__stick {
  position: sticky; top: 62px; min-height: calc(100dvh - 62px);
  display: grid; align-content: center; gap: clamp(1rem, 2.2vw, 1.8rem);
}
/* The model is the centre of the section, not the filler between two text
   groups: it owns the whole width of the stage, and the two engine ledgers
   stand over its outer ground. */
.twin__stage {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 0.76fr) minmax(0, 1.48fr) minmax(0, 0.76fr);
  gap: 0 clamp(0.8rem, 1.8vw, 1.8rem);
  position: relative;
  padding-bottom: clamp(1rem, 2.2vw, 1.8rem);
}
.twin__stage::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--rule-2) 14%,
              var(--rule-2) 86%, transparent 100%);
}
.twin__obj { grid-area: 1 / 1 / 2 / 4; position: relative; margin: 0; z-index: 0; }
.twin__svg { width: 100%; height: auto; display: block; overflow: hidden; }
.engine { position: relative; z-index: 2; }
/* the ledger is held off the ground it stands on, so the model can run behind
   it without the reading ever having to fight the drawing */
.engine::before {
  content: ''; position: absolute; inset: -1.1rem -1.3rem; z-index: -1;
  background: radial-gradient(128% 104% at 4% 50%, var(--bg) 0%, var(--bg) 56%,
              rgba(241, 238, 231, 0) 100%);
}
.engine[data-engine="b"]::before {
  background: radial-gradient(128% 104% at 96% 50%, var(--bg) 0%, var(--bg) 56%,
              rgba(241, 238, 231, 0) 100%);
}
.engine[data-engine="a"] { grid-area: 1 / 1 / 2 / 2; }
.engine[data-engine="b"] { grid-area: 1 / 3 / 2 / 4; }
.twin__cap {
  display: grid; margin-top: 0.2rem; text-align: center;
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary); min-height: 2.6em;
}
.twin__cap span {
  grid-area: 1 / 1; opacity: 0; transform: translateY(5px);
  transition: opacity 0.4s var(--e-out), transform 0.45s var(--e-out);
}
.twin[data-step="0"] .twin__cap span[data-for="0"],
.twin[data-step="1"] .twin__cap span[data-for="1"],
.twin[data-step="2"] .twin__cap span[data-for="2"],
.twin[data-step="3"] .twin__cap span[data-for="3"],
.twin[data-step="4"] .twin__cap span[data-for="4"] { opacity: 1; transform: none; }

.twin__legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem;
  padding-top: 0.9rem; border-top: 1px solid var(--rule);
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary);
}
.twin__legend-k { font-weight: 600; color: var(--fg); letter-spacing: 0.02em; }
.twin__prog { flex: 1 1 120px; height: 2px; background: var(--rule); position: relative; }
.twin__prog i {
  position: absolute; inset: 0 auto 0 0; background: var(--mark);
  width: calc(var(--st) / 4 * 100%);
  transition: width 0.55s var(--e-out);
}

/* --- the engines: one step uncovers at a time -------------------------- */
.engine__steps { display: grid; gap: 0; }
.engine__steps li {
  display: grid; grid-template-columns: 2.1rem minmax(0, 1fr);
  gap: 0.1rem 0.5rem; padding: 0.72rem 0 0.78rem;
  border-top: 1px solid var(--rule);
  opacity: clamp(0, calc((var(--st) - var(--s)) * 1), 1);
  transform: translateY(calc(10px * (1 - clamp(0, calc((var(--st) - var(--s)) * 1), 1))));
  transition: opacity 0.55s var(--e-out), transform 0.6s var(--e-out);
}
.engine__steps li i {
  grid-row: 1 / 3; font-style: normal; font-variant-numeric: tabular-nums;
  font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--mark); padding-top: 0.16rem;
}
.engine__steps li b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.engine__steps li span { font-size: var(--t4-fs); line-height: var(--t4-lh); color: var(--fg-2); }
/* the step that just landed is the one being read */
.twin[data-step="1"] .engine__steps li:nth-child(1),
.twin[data-step="2"] .engine__steps li:nth-child(2),
.twin[data-step="3"] .engine__steps li:nth-child(3),
.twin[data-step="4"] .engine__steps li:nth-child(4) { border-left: 2px solid var(--mark);
  padding-left: 0.7rem; margin-left: -0.7rem; }

.engine[data-engine="b"] { text-align: right; }
.engine[data-engine="b"] .engine__id { justify-content: flex-end; }
.engine[data-engine="b"] .engine__steps li {
  grid-template-columns: minmax(0, 1fr) 2.1rem;
}
.engine[data-engine="b"] .engine__steps li i { grid-column: 2; grid-row: 1 / 3;
  color: var(--fg); }
.engine[data-engine="b"] .engine__steps li b,
.engine[data-engine="b"] .engine__steps li span { grid-column: 1; }
.twin[data-step="1"] .engine[data-engine="b"] .engine__steps li:nth-child(1),
.twin[data-step="2"] .engine[data-engine="b"] .engine__steps li:nth-child(2),
.twin[data-step="3"] .engine[data-engine="b"] .engine__steps li:nth-child(3),
.twin[data-step="4"] .engine[data-engine="b"] .engine__steps li:nth-child(4) {
  border-left: 0; border-right: 2px solid var(--mark);
  padding-left: 0; padding-right: 0.7rem; margin-left: 0; margin-right: -0.7rem;
}

/* --- the drawn twin ----------------------------------------------------- */
.tw-base__p  { fill: rgba(11, 107, 83, 0.07); stroke: rgba(11, 107, 83, 0.44); stroke-width: 1.2; }
.tw-base__g  { fill: none; stroke: rgba(20, 23, 26, 0.14); stroke-width: 1; }
.tw-sup__top { fill: #d8d3c6; }
.tw-sup__fl  { fill: #bab5a8; }
.tw-sup__fr  { fill: #9d988d; }
.tw-sup__edge{ fill: none; stroke: rgba(20, 23, 26, 0.52); stroke-width: 1; }
.tw-pl       { fill: rgba(252, 251, 248, 0.58); }
.tw-pl__e    { fill: none; stroke: rgba(20, 23, 26, 0.34); stroke-width: 1.2; }
.tw-card     { fill: #ffffff; stroke: rgba(20, 23, 26, 0.34); stroke-width: 1; }
.tw-card__l  { fill: none; stroke: rgba(20, 23, 26, 0.30); stroke-width: 1.4; }
.tw-ev       { fill: none; stroke: var(--mark); stroke-width: 1.6; }
.tw-st--q    { color: var(--mark-paper); }
.tw-st--b    { color: #14171a; }
.tw-rel      { fill: none; stroke: rgba(11, 107, 83, 0.46); stroke-width: 1.2; }
.tw-node     { fill: #14171a; }
.tw-node__o  { fill: none; stroke: var(--mark); stroke-width: 1.4; }
.tw-feed__a, .tw-feed__b { fill: none; stroke-width: 1.8; stroke-linecap: round; }
.tw-feed__t { stroke-width: 2.6; }
.tw-feed__a { stroke: var(--mark); }
.tw-feed__b { stroke: rgba(20, 23, 26, 0.55); }

/* every layer waits for its own step, and the supply units land one by one */
.tw-l { opacity: 0; transition: opacity 0.6s var(--e-out); }
.tw-l--1 { --n: 1; } .tw-l--2 { --n: 2; } .tw-l--3 { --n: 3; } .tw-l--4 { --n: 4; }
.tw-l { opacity: clamp(0, calc((var(--st) - var(--n) + 1) * 1), 1); }
.tw-sup, .tw-it {
  opacity: 0; transform: translateY(-16px);
  transition: opacity 0.5s var(--e-out), transform 0.62s var(--e-out);
}
.tw-l--1 .tw-sup { transform: translateY(14px); }
.twin[data-step="1"] .tw-l--1 .tw-sup, .twin[data-step="2"] .tw-l--1 .tw-sup,
.twin[data-step="3"] .tw-l--1 .tw-sup, .twin[data-step="4"] .tw-l--1 .tw-sup,
.twin[data-step="2"] .tw-l--2 .tw-it, .twin[data-step="3"] .tw-l--2 .tw-it,
.twin[data-step="4"] .tw-l--2 .tw-it,
.twin[data-step="3"] .tw-l--3 .tw-it, .twin[data-step="4"] .tw-l--3 .tw-it,
.twin[data-step="4"] .tw-l--4 .tw-it {
  opacity: 1; transform: none; transition-delay: calc(0.1s + var(--i) * 70ms);
}
.tw-rel {
  stroke-dasharray: 200; stroke-dashoffset: 200;
  transition: stroke-dashoffset 0.7s var(--e-inout);
}
.twin[data-step="4"] .tw-rel { stroke-dashoffset: 0; transition-delay: calc(0.2s + var(--i) * 80ms); }
/* the engines put the layer in; the mark is the delivery, not a pill on a wire */
.tw-feed__a, .tw-feed__b {
  stroke-dasharray: 46; stroke-dashoffset: 46;
  transition: stroke-dashoffset 0.5s var(--e-out);
}
.twin[data-step="1"] .tw-feed__a[style*="--i:0"], .twin[data-step="1"] .tw-feed__b[style*="--i:0"],
.twin[data-step="2"] .tw-feed__a[style*="--i:1"], .twin[data-step="2"] .tw-feed__b[style*="--i:1"],
.twin[data-step="3"] .tw-feed__a[style*="--i:2"], .twin[data-step="3"] .tw-feed__b[style*="--i:2"],
.twin[data-step="4"] .tw-feed__a[style*="--i:3"], .twin[data-step="4"] .tw-feed__b[style*="--i:3"] {
  stroke-dashoffset: 0;
}

/* --- narrow: the twin holds the top of the frame and the steps run under it */
@media (max-width: 1023px) {
  .js .twin__track { height: auto; }
  .js .twin__stick { position: static; min-height: 0; }
  .twin__stage { grid-template-columns: minmax(0, 1fr); gap: 1.4rem; padding-bottom: 0; }
  .twin__stage::after { display: none; }
  .twin__obj { grid-area: auto; order: -1; position: sticky; top: 70px; z-index: 1;
               padding-bottom: 0.6rem; background: var(--bg); }
  .engine[data-engine="a"], .engine[data-engine="b"] { grid-area: auto; }
  /* the object is the sticky frame here, so the ledgers must run UNDER it */
  .engine { position: static; z-index: auto; }
  .engine::before, .engine[data-engine="b"]::before { display: none; }
  .twin__svg { max-height: 50dvh; }
  .engine[data-engine="b"] { text-align: left; }
  .engine[data-engine="b"] .engine__id { justify-content: flex-start; }
  .engine[data-engine="b"] .engine__steps li { grid-template-columns: 2.1rem minmax(0, 1fr); }
  .engine[data-engine="b"] .engine__steps li i { grid-column: 1; }
  .engine[data-engine="b"] .engine__steps li b,
  .engine[data-engine="b"] .engine__steps li span { grid-column: 2; }
  .twin[data-step="1"] .engine[data-engine="b"] .engine__steps li:nth-child(1),
  .twin[data-step="2"] .engine[data-engine="b"] .engine__steps li:nth-child(2),
  .twin[data-step="3"] .engine[data-engine="b"] .engine__steps li:nth-child(3),
  .twin[data-step="4"] .engine[data-engine="b"] .engine__steps li:nth-child(4) {
    border-right: 0; border-left: 2px solid var(--mark);
    padding-right: 0; padding-left: 0.7rem; margin-right: 0; margin-left: -0.7rem;
  }
}

/* ===================================== CH03 · THE THEATRE SHELL =========
   V2.4R1. The act rail, the product world and the act evidence used to be
   three stacked modules spanning 1200-1280px of document. The control and its
   result could never be seen together, so the coupling had to be inferred.

   They are now ONE object: a single bordered stage, one act rail across its
   top edge, and a body split into a dominant world column and a narrow
   evidence dock. Choosing an act changes the rail, the world and the dock
   inside one view. The dock is deliberately NOT a card: it is separated by a
   single hairline and a tonal shift of the same ground, so it reads as an
   inset in the same machined object rather than a panel sitting on top.     */
.theatre {
  --dock: clamp(276px, 25vw, 392px);
  display: grid; gap: 0;
  /* rail | cue | body. The body takes whatever the rail leaves, so the whole
     shell is guaranteed to fit one viewport instead of being budgeted by hand */
  grid-template-rows: auto auto minmax(0, 1fr);
  /* capped so the world column keeps a stable aspect from 1280 to 1920 and the
     camera never has to crop the subject to cover a very wide frame */
  max-width: 1420px; margin-inline: auto;
  position: relative;
  background:
    radial-gradient(112% 68% at 34% 0%, rgba(99, 214, 176, 0.09) 0%, rgba(99, 214, 176, 0) 62%),
    linear-gradient(180deg, #141a19 0%, #0a0e0d 46%, #060807 100%);
  border: 1px solid var(--rule-2);
  box-shadow: inset 0 1px 0 rgba(244, 242, 236, 0.09),
              0 44px 90px -56px rgba(0, 0, 0, 0.92);
}

/* --- A. THE ACT RAIL: on the shell's own top edge ----------------------- */
.theatre .track {
  margin: 0; padding: clamp(0.85rem, 1.7vw, 1.25rem) clamp(1rem, 2.2vw, 1.7rem)
                      clamp(0.7rem, 1.4vw, 1rem);
  border-bottom: 1px solid var(--rule);
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  align-items: start;
  column-gap: clamp(0.9rem, 2vw, 2rem);
}
.theatre .track__axis { grid-column: 1 / 4; }
/* the position readout: which of the three acts is live, on the rail itself */
.track__pos {
  grid-column: 4; grid-row: 2; display: grid; align-self: center;
  font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; white-space: nowrap; padding-left: 0.4rem;
}
.track__pos > span {
  grid-area: 1 / 1; opacity: 0;
  transition: opacity 0.32s var(--e-out);
}
.track__pos i { font-style: normal; font-weight: 600; color: var(--mark); font-size: var(--t6-fs); line-height: var(--t6-lh); }
.theatre[data-act="1"] .track__pos span[data-for="1"],
.theatre[data-act="2"] .track__pos span[data-for="2"],
.theatre[data-act="3"] .track__pos span[data-for="3"] { opacity: 1; }

/* the cue sits with the rail, not as a floating line above the world */
.theatre__cue {
  margin: 0; padding: 0 clamp(1rem, 2.2vw, 1.7rem) clamp(0.6rem, 1.2vw, 0.9rem);
  border-bottom: 1px solid var(--rule);
}

/* --- THE BODY: world | dock --------------------------------------------
   Without JS the three acts cannot be collapsed into one cell, so the body is
   a single column, the shell has no fixed height, and all three realities are
   readable in sequence. That is what the truth boundary requires. The
   single-viewport theatre is layered on top of that, not underneath it. */
.thbody {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-height: 0;
}
.js .theatre { height: min(860px, calc(100dvh - 104px)); }
.js .thbody { grid-template-columns: minmax(0, 1fr) var(--dock); }

/* --- B. THE HERO PRODUCT WORLD ----------------------------------------- */
.pworld {
  grid-column: 1; grid-row: 1;
  position: relative; overflow: hidden;
  border: 0; background: none; box-shadow: none;
}
.pworld__svg { width: 100%; height: 100%; display: block; aspect-ratio: auto; }
.pworld__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: grid; padding: 0.7rem clamp(0.9rem, 2vw, 1.4rem);
  background: linear-gradient(180deg, rgba(6, 8, 7, 0) 0%, rgba(6, 8, 7, 0.88) 46%);
  font-size: var(--t5-fs); line-height: var(--t5-lh); letter-spacing: 0.02em; color: var(--fg-2);
}
.pworld__cap span {
  grid-area: 1 / 1; opacity: 0; transform: translateY(6px);
  transition: opacity 0.4s var(--e-out), transform 0.5s var(--e-out);
}
.theatre[data-act="1"] .pworld__cap span[data-for="1"],
.theatre[data-act="2"] .pworld__cap span[data-for="2"],
.theatre[data-act="3"] .pworld__cap span[data-for="3"] { opacity: 1; transform: none; }

/* --- C. THE EVIDENCE DOCK ----------------------------------------------
   Not a card. One hairline against the world, one tonal step of the same
   ground, and the act's own placard colour running down its inner edge so the
   dock is unmistakably the selected act's, not a neutral sidebar.          */
.stage {
  grid-column: 1; grid-row: 2;
  /* static on purpose: the act's evidence media is positioned against the
     BODY, so it can sit in the world column while staying inside the tabpanel
     it belongs to. An absolutely positioned box whose containing block is an
     ancestor of this one is not clipped by this one's overflow. */
  position: static; overflow: visible;
  padding: 0; border: 0; box-shadow: none;
  border-top: 1px solid var(--rule-2);
  background: linear-gradient(180deg, rgba(10, 14, 13, 0.72), rgba(6, 8, 7, 0.9));
}
.js .stage {
  grid-column: 2; grid-row: 1;
  overflow: hidden;
  border-top: 0; border-left: 1px solid var(--rule-2);
}
.stage::before, .stage::after { content: none; }
.stage__ghost { display: none; }
/* static, like the dock: the containing block for the act's evidence media
   must resolve all the way up to .thbody so the media can sit in the world */
.stage__frame {
  display: grid; position: static; perspective: none;
  gap: clamp(1.4rem, 2.8vw, 2.4rem);
  height: 100%;
}
.js .stage__frame { gap: 0; }
.stage__act {
  display: flex; flex-direction: column; align-items: stretch;
  gap: clamp(0.7rem, 1.5vw, 1rem);
  padding: clamp(0.95rem, 2vw, 1.5rem) clamp(0.95rem, 2vw, 1.4rem)
           clamp(1.1rem, 2.2vw, 1.6rem);
}
.js .stage__act {
  grid-area: 1 / 1;
  overflow-y: auto; overscroll-behavior: auto;
  scrollbar-width: thin;
}
/* No blur on the act swap. A filter (even blur(0)) makes this element a
   containing block for absolutely positioned descendants, which would trap the
   act's evidence media inside the dock instead of letting it sit in the world.
   The blur was decorative; opacity and travel already carry the REVEAL. */
.js .stage__act {
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(10px);
  transition: opacity 0.22s var(--e-out), transform 0.24s var(--e-out),
              visibility 0.24s;
}
.js .stage__act[data-active='true'] {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
  transition: opacity 0.42s var(--e-out), transform 0.5s var(--e-inout),
              visibility 0.5s;
}

/* the truth bar heads the dock: the maturity boundary is the first thing
   read about the act, and it carries the act's own placard colour */
.act__truth {
  position: static; top: auto; grid-column: auto; z-index: 1;
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.34rem 0.6rem;
  margin: 0; padding: 0 0 0.7rem;
  background: none; border: 0; border-bottom: 1px solid var(--rule);
  border-left: 0;
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary); flex: none;
}
.act__ix { margin-left: auto; }

.stage__act .actbody__t {
  font-family: var(--serif); font-weight: 500; letter-spacing: -0.01em;
  font-size: var(--t2-fs); line-height: var(--t2-lh); color: var(--fg);
  text-wrap: pretty;
}
.stage__act .prose { font-size: var(--t4-fs); line-height: var(--t4-lh); }
.stage__act .qual  { font-size: var(--t5-fs); line-height: var(--t5-lh); }
.stage__act .actbody { display: grid; gap: clamp(0.6rem, 1.3vw, 0.9rem); }

/* --- the evidence media: inset INTO the world, not a second hero --------
   It sits in the quiet bottom corner of the world column, so the media is
   visibly attached to the scene it is evidence for. On play it grows into
   the world rather than opening somewhere else.                           */
.stage__act .plate {
  position: relative; margin: 0; width: 100%;
  border: 1px solid var(--rule-2);
  background: rgba(6, 8, 7, 0.86);
}
.js .stage__act .plate {
  position: absolute; z-index: 2;
  right: calc(var(--dock) + clamp(0.7rem, 1.4vw, 1.1rem));
  bottom: clamp(2.4rem, 3.4vw, 3rem);
  width: clamp(186px, 18vw, 252px);
  margin: 0;
  border: 1px solid var(--rule-2);
  background: rgba(6, 8, 7, 0.86);
  box-shadow: 0 24px 50px -30px rgba(0, 0, 0, 0.95);
  /* No eased width here. Animating width lays out a <video> on every frame,
     and this size change answers a direct press on the play control, where an
     immediate response is the correct feel. The room is given at once; only
     the settle is eased. */
  transform: translateY(0);
  transition: transform 0.24s var(--e-out);
}
.theatre[data-playing='true'] .stage__act[data-active='true'] .plate {
  transform: translateY(-2px);
}
.stage__act .plate video { width: 100%; height: auto; display: block; background: #050706; }
.stage__act .plate__foot {
  display: grid; gap: 0.14rem; padding: 0.5rem 0.6rem 0.55rem;
  border-top: 1px solid var(--rule);
  font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary);
}
.stage__act .plate__foot b { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; color: var(--fg-2); }
/* while it is actually playing, the evidence is allowed to take more room */
.theatre[data-playing='true'] .stage__act[data-active='true'] .plate {
  width: clamp(268px, 31vw, 420px);
}

/* --- D. the synchronised state signal ----------------------------------
   One act change is ONE event: the rail confirms, the world transforms and
   the dock reveals on the same curve, so causality is legible.            */
.theatre .track__st[aria-selected='true'] b { color: var(--fg); }
.theatre .track__st[aria-selected='true'] { color: var(--fg); }

/* --- the camera ---------------------------------------------------------
   The acts are not three pictures: they are three positions in one space,
   and the move between them has to be big enough to feel like a move. */
.pw-cam {
  transform-origin: 470px 320px;
  transition: transform 1.15s var(--e-inout);
}
.theatre[data-act="2"] .pw-cam { transform: translate(30px, -4px) scale(1.15); }
.theatre[data-act="3"] .pw-cam { transform: translate(-6px, -74px) scale(0.6); }

/* --- the air ------------------------------------------------------------ */
.pw-glow { fill: url(#pw-glow); transition: opacity 1.1s var(--e-soft), transform 1.1s var(--e-inout); }
.pw-fog  { fill: url(#pw-fog); }
.pw-atm  { transform-origin: 470px 320px; }
.theatre[data-act="2"] .pw-glow { opacity: 1.4; transform: translate(-70px, 40px) scale(0.72); }
.theatre[data-act="3"] .pw-glow { transform: scale(1.55); }

/* --- the site ----------------------------------------------------------- */
.pw-site__p { fill: rgba(99, 214, 176, 0.05); stroke: rgba(99, 214, 176, 0.26); stroke-width: 1.1; }
.pw-site__g { fill: none; stroke: rgba(244, 242, 236, 0.11); stroke-width: 1; }
.pw-site { transition: opacity 0.8s var(--e-out); }
.theatre[data-act="3"] .pw-site { opacity: 0; }

/* --- the subject: the dominant object in the frame ---------------------- */
.pw-vol__sh   { fill: url(#pw-cast); }
.pw-vol__top  { fill: url(#pw-volT); }
.pw-vol__fl   { fill: url(#pw-volL); }
.pw-vol__fr   { fill: url(#pw-volR); }
.pw-vol__edge { fill: none; stroke: rgba(244, 242, 236, 0.5); stroke-width: 1.3; }
.pw-vol__band { fill: none; stroke: rgba(244, 242, 236, 0.13); stroke-width: 1; }

/* the unit the story is about: unresolved until the field answers it */
.pw-vol__unit {
  fill: rgba(99, 214, 176, 0.05); stroke: rgba(99, 214, 176, 0.34); stroke-width: 1.3;
  transition: fill 0.7s var(--e-out), stroke 0.7s var(--e-out);
}
.theatre[data-act="2"] .pw-vol__unit,
.theatre[data-act="3"] .pw-vol__unit {
  fill: rgba(99, 214, 176, 0.28); stroke: rgba(150, 240, 208, 0.94);
}

/* --- the sign ----------------------------------------------------------- */
.pw-sign__plate { fill: #0b0f0e; stroke: rgba(244, 242, 236, 0.5); stroke-width: 1.1; }
.pw-sign__mk, .pw-sign__mb { transition: opacity 0.5s var(--e-out); }
.pw-sign__mk    { color: var(--mark-ink); }
.pw-sign__mb    { color: #f4f2ec; opacity: 0; }
.theatre[data-act="2"] .pw-sign__mk,
.theatre[data-act="3"] .pw-sign__mk { opacity: 0; }
.theatre[data-act="2"] .pw-sign__mb,
.theatre[data-act="3"] .pw-sign__mb { opacity: 1; }
.pw-sign__qr rect { fill: #f4f2ec; }
.pw-sign__qr .pw-sign__m { fill: #0a0b0a; }

/* --- the opening -------------------------------------------------------- */
.pw-open__f { fill: url(#pw-beam); }
.pw-open__e { fill: none; stroke: rgba(99, 214, 176, 0.55); stroke-width: 1.2; }
.pw-open {
  opacity: 0; transform-box: fill-box; transform-origin: 0% 50%;
  transform: scaleX(0.04);
  transition: opacity 0.5s var(--e-out), transform 1s var(--e-out);
}
.theatre[data-act] .pw-open { opacity: 1; transform: none; }
.theatre[data-act="3"] .pw-open { opacity: 0.34; }

/* --- THE PRODUCT SURFACE, UNFOLDED --------------------------------------
   Annotation on the product, not five equal information panels. They open
   out of the sign, and in the act where the field actually answers two of
   them, those two come forward and the rest hold back.                    */
.pw-pt__f  { fill: url(#pw-plate); }
.pw-pt__e  { fill: none; stroke: rgba(244, 242, 236, 0.2); stroke-width: 1; }
.pw-pt__hd { fill: none; stroke: rgba(244, 242, 236, 0.28); stroke-width: 1.1; }
.pw-pt__k  { fill: var(--fg-2); font-family: var(--sans); font-size: var(--svg-t4, 15px); font-weight: 500; }
.pw-pt__v, .pw-pt__v2 {
  font-family: var(--sans); font-size: var(--svg-t5, 13.5px); font-weight: 600; text-anchor: end;
  transition: opacity 0.5s var(--e-out);
}
.pw-pt__v  { fill: var(--fg-3); }
.pw-pt__v2 { fill: #9ff0d3; opacity: 0; }

/* They unfold out of the sign, edge on, one after another. Only the PLATE is
   scaled: a <text> scaled about its own fill-box would slide out of the plate
   it belongs to, so the type is faded in once its plate has opened. */
.pw-pt {
  opacity: 0;
  transition: opacity 0.5s var(--e-out) calc(var(--i) * 90ms);
}
.pw-pt__f, .pw-pt__hd, .pw-pt__e {
  transform-box: fill-box; transform-origin: 0% 100%;
  transform: scaleX(0.02);
  transition: fill 0.6s var(--e-out), stroke 0.6s var(--e-out),
              transform 0.9s var(--e-out) calc(var(--i) * 90ms);
}
.pw-pt__k, .pw-pt__v {
  opacity: 0;
  transition: opacity 0.45s var(--e-out) calc(0.42s + var(--i) * 90ms);
}
.theatre[data-act] .pw-pt { opacity: 1; }
.theatre[data-act] .pw-pt__f,
.theatre[data-act] .pw-pt__hd,
.theatre[data-act] .pw-pt__e { transform: none; }
.theatre[data-act] .pw-pt__k,
.theatre[data-act] .pw-pt__v { opacity: 1; }

/* act 1 is the state as the code found it; act 2 is the state after the field
   was actually checked. Only the plates the field can answer change, and they
   change one after another, not all at once. */
.theatre[data-act="2"] .pw-pt--field .pw-pt__v,
.theatre[data-act="3"] .pw-pt--field .pw-pt__v,
.theatre[data-act="2"] .pw-pt--none .pw-pt__v,
.theatre[data-act="3"] .pw-pt--none .pw-pt__v { opacity: 0; }
.theatre[data-act="2"] .pw-pt--field .pw-pt__v2,
.theatre[data-act="3"] .pw-pt--field .pw-pt__v2,
.theatre[data-act="2"] .pw-pt--none .pw-pt__v2,
.theatre[data-act="3"] .pw-pt--none .pw-pt__v2 {
  opacity: 1; transition-delay: calc(var(--i) * 150ms);
}
.theatre[data-act="2"] .pw-pt--field .pw-pt__f,
.theatre[data-act="3"] .pw-pt--field .pw-pt__f,
.theatre[data-act="2"] .pw-pt--none .pw-pt__f,
.theatre[data-act="3"] .pw-pt--none .pw-pt__f {
  fill: url(#pw-plate-on); transition-delay: calc(var(--i) * 150ms);
}
.theatre[data-act="2"] .pw-pt--field .pw-pt__hd,
.theatre[data-act="3"] .pw-pt--field .pw-pt__hd,
.theatre[data-act="2"] .pw-pt--none .pw-pt__hd,
.theatre[data-act="3"] .pw-pt--none .pw-pt__hd {
  stroke: rgba(170, 245, 218, 0.9); transition-delay: calc(var(--i) * 150ms);
}
.theatre[data-act="2"] .pw-pt--field .pw-pt__e,
.theatre[data-act="3"] .pw-pt--field .pw-pt__e,
.theatre[data-act="2"] .pw-pt--none .pw-pt__e,
.theatre[data-act="3"] .pw-pt--none .pw-pt__e {
  stroke: rgba(99, 214, 176, 0.62); transition-delay: calc(var(--i) * 150ms);
}
/* the plates the field cannot answer hold back, so the two that changed are
   not competing with three equals */
.pw-pt { transition: opacity 0.6s var(--e-out) calc(var(--i) * 90ms); }
.theatre[data-act="2"] .pw-pt--hold { opacity: 0.44; }

/* what the plates are the state OF, written on the world, not on a title bar */
.pw-idn__t, .pw-idn__t2 { fill: var(--fg); font-family: var(--sans); font-size: var(--svg-t3, 17px);
                          font-weight: 600; transition: opacity 0.5s var(--e-out); }
.pw-idn__t2 { opacity: 0; }
.pw-idn__n  { fill: var(--fg-4); font-family: var(--sans); font-size: var(--svg-t6, 12px);
              letter-spacing: 0.1em; }
.theatre[data-act="3"] .pw-idn__t  { opacity: 0; }
.theatre[data-act="3"] .pw-idn__t2 { opacity: 1; }
.pw-idn__r { fill: none; stroke: rgba(99, 214, 176, 0.5); stroke-width: 1.2; }
.pw-idn { opacity: 0; transition: opacity 0.6s var(--e-out) 0.5s; }
.theatre[data-act] .pw-idn { opacity: 1; }
.pw-plates, .pw-idn { transition: opacity 0.7s var(--e-out); }
.theatre[data-act="3"] .pw-idn--n { opacity: 0; }

/* --- ACT 2: the field evidence ------------------------------------------ */
.pw-field { opacity: 0; transition: opacity 0.5s var(--e-out); }
.theatre[data-act="2"] .pw-field, .theatre[data-act="3"] .pw-field { opacity: 1; }
.pw-ev__c    { fill: #0d1312; stroke: rgba(99, 214, 176, 0.62); stroke-width: 1.1; }
.pw-ev__t    { fill: none; stroke: rgba(99, 214, 176, 0.85); stroke-width: 2; }
.pw-ev__l    { fill: none; stroke: rgba(244, 242, 236, 0.42); stroke-width: 1; }
.pw-ev__lead { fill: none; stroke: rgba(99, 214, 176, 0.46); stroke-width: 1;
               stroke-dasharray: 4 5; opacity: 0; transition: opacity 0.45s var(--e-out); }
.theatre[data-act="2"] .pw-ev__lead {
  opacity: 1; transition-delay: calc(0.42s + var(--i) * 0.16s);
}
.pw-ev { opacity: 0; transform: translate(-26px, 12px);
         transition: opacity 0.5s var(--e-out), transform 0.7s var(--e-out); }
.theatre[data-act="2"] .pw-ev {
  opacity: 1; transform: none; transition-delay: calc(var(--i) * 0.16s);
}
.theatre[data-act="3"] .pw-ev, .theatre[data-act="3"] .pw-ev__lead { opacity: 0; }
.pw-field__mk { color: #f4f2ec; filter: drop-shadow(0 0 10px rgba(244, 242, 236, 0.4)); }
.pw-field__fl { fill: url(#pw-flare); opacity: 0; transition: opacity 0.9s var(--e-soft) 0.5s; }
.theatre[data-act="2"] .pw-field__fl { opacity: 1; }

/* --- ACT 3: the market, and the same plate logic multiplied over it ------ */
.pw-mkt { opacity: 0; transition: opacity 0.7s var(--e-out); }
.theatre[data-act="3"] .pw-mkt { opacity: 1; }
.pw-mkt__p { fill: rgba(99, 214, 176, 0.025); stroke: rgba(99, 214, 176, 0.22);
             stroke-width: 1; stroke-dasharray: 6 6; }
.pw-mkt__g { fill: none; stroke: rgba(99, 214, 176, 0.10); stroke-width: 1; }
.pw-nb__top  { fill: rgba(34, 43, 40, 0.62); }
.pw-nb__fl   { fill: rgba(22, 29, 27, 0.62); }
.pw-nb__fr   { fill: rgba(11, 15, 14, 0.62); }
.pw-nb__edge { fill: none; stroke: rgba(244, 242, 236, 0.3); stroke-width: 1;
               stroke-dasharray: 5 5; }
.pw-nb__mk   { color: rgba(99, 214, 176, 0.72); }
.pw-nb--b .pw-nb__top  { fill: rgba(32, 68, 58, 0.7); }
.pw-nb--b .pw-nb__edge { stroke: rgba(99, 214, 176, 0.5); }
.pw-nb--b .pw-nb__mk   { color: rgba(226, 248, 240, 0.8); }
.pw-nb { opacity: 0; transform: translateY(16px); }
.theatre[data-act="3"] .pw-nb {
  opacity: 1; transform: none;
  transition: opacity 0.6s var(--e-out) calc(0.2s + var(--i) * 0.07s),
              transform 0.8s var(--e-out) calc(0.2s + var(--i) * 0.07s);
}
.pw-lat__f  { fill: rgba(19, 26, 24, 0.94); }
.pw-lat__hd { fill: none; stroke: rgba(99, 214, 176, 0.55); stroke-width: 1.6; }
.pw-lat__l  { fill: none; stroke: rgba(244, 242, 236, 0.3); stroke-width: 2.2; }
.pw-lat__l--b { stroke: rgba(120, 226, 189, 0.75); }
.pw-lat__p  { opacity: 0; transform: translateY(9px); }
.theatre[data-act="3"] .pw-lat__p {
  opacity: 1; transform: none;
  transition: opacity 0.6s var(--e-out) calc(0.5s + var(--i) * 0.07s),
              transform 0.7s var(--e-out) calc(0.5s + var(--i) * 0.07s);
}

/* --- ambient: the world is not dead between acts ------------------------ */
@keyframes pwOpen { 0%, 100% { stroke-opacity: 0.28; } 50% { stroke-opacity: 0.68; } }
@keyframes pwUnit { 0%, 100% { stroke-opacity: 0.5; } 50% { stroke-opacity: 1; } }
@keyframes pwFog  { 0%, 100% { transform: translateX(0); opacity: 0.7; }
                    50% { transform: translateX(30px); opacity: 1; } }
.theatre[data-scene="settled"] .pw-open__e {
  animation: pwOpen 5.5s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
.theatre[data-scene="settled"] .pw-vol__unit {
  animation: pwUnit 6.5s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
.theatre[data-scene="settled"] .pw-fog {
  animation: pwFog calc(15s + var(--i) * 4s) var(--e-soft) infinite;
  animation-delay: calc(var(--i) * -3000ms);
  animation-play-state: var(--amb, paused);
}

/* --- narrow: the shell stacks, the coupling does not break --------------
   The act rail stays at the top of the theatre and sticks while the world
   and the dock scroll under it, so the control that produced the state is
   never off screen while its result is being read.                        */
@media (max-width: 1023px) {
  .theatre, .js .theatre { height: auto; max-width: none; grid-template-rows: auto auto auto; }
  .thbody, .js .thbody { grid-template-columns: minmax(0, 1fr); }
  .theatre .track {
    position: sticky; top: 58px; z-index: 6;
    background: rgba(8, 11, 10, 0.96); grid-template-columns: minmax(0, 1fr) auto;
  }
  @supports (backdrop-filter: blur(6px)) {
    .theatre .track { background: rgba(8, 11, 10, 0.82); backdrop-filter: blur(8px); }
  }
  .theatre .track__axis { grid-column: 1 / 3; }
  .track__pos { grid-column: 2; grid-row: 2; }
  .pworld { grid-column: 1; grid-row: 1; aspect-ratio: 5 / 4; }
  .pworld__svg { height: 100%; }
  .stage, .js .stage {
    grid-column: 1; grid-row: 2;
    border-left: 0; border-top: 1px solid var(--rule-2);
  }
  .stage__act, .js .stage__act { overflow: visible; }
  /* the evidence stays attached to the world it belongs to: it sits on the
     seam between the scene and the dock rather than floating in either */
  .stage__act .plate, .js .stage__act .plate {
    position: relative; right: auto; bottom: auto; width: 100%;
    margin-top: 0.2rem;
  }
  .theatre[data-playing='true'] .stage__act[data-active='true'] .plate { width: 100%; }
  .pw-idn__t, .pw-idn__t2 { font-size: var(--svg-t3, 21px); }
  .pw-idn__n { font-size: var(--svg-t6, 15px); }
  .pw-pt__k  { font-size: var(--svg-t4, 19px); }
  .pw-pt__v, .pw-pt__v2 { font-size: var(--svg-t5, 17px); }
  .theatre[data-act="2"] .pw-cam { transform: translate(52px, -22px) scale(1.16); }
  .theatre[data-act="3"] .pw-cam { transform: translate(-4px, -48px) scale(0.66); }
}
@media (max-width: 620px) {
  .pworld { aspect-ratio: 4 / 3; }
  .pworld__cap { font-size: var(--t5-fs); line-height: var(--t5-lh); }
  .theatre .track { top: 54px; }
  /* the sticky rail must not eat the viewport it is meant to keep company
     with: the maturity label stays, the secondary line goes */
  .theatre .track__st span { display: none; }
  .theatre .track__st b { font-size: var(--t4-fs); line-height: var(--t4-lh); }
  .theatre .track { padding-bottom: 0.6rem; }
  .theatre__cue { padding-bottom: 0.5rem; font-size: var(--t6-fs); line-height: var(--t6-lh); }
}
@media (max-width: 620px) {
  .track { grid-template-columns: minmax(0, 1fr); gap: 0.9rem; padding-top: 0; }
  .track__axis { display: none; }
  .track__st { padding: 0.75rem 0 0 0.9rem; border-top: 1px solid var(--rule); }
  .track__st::before { left: 0; top: 0.9rem; width: 0.5rem; height: 1px; }
  .track__st::after { left: -3px; top: calc(0.9rem - 3px); }
  .track__st[aria-selected='true'] { border-top-color: var(--mark-line); }
  .act__ix { margin-left: 0; }
}

/* D. state ladder — ? to ! as a drawn progression */
.ladder { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ladder li {
  padding: clamp(0.9rem, 1.8vw, 1.25rem) clamp(0.8rem, 1.6vw, 1.2rem)
           clamp(0.9rem, 1.8vw, 1.25rem) 0;
  border-top: 2px solid var(--rule-2); display: grid; gap: 0.28rem;
  position: relative;
}
.ladder li + li { padding-left: clamp(0.8rem, 1.6vw, 1.2rem); }
.ladder li::before {
  content: ''; position: absolute; top: -2px; left: 0; width: 26px; height: 2px;
  background: var(--solid);
}
.ladder b { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; }
.ladder span { font-size: var(--t4-fs); color: var(--text-secondary); line-height: var(--t4-lh); }
@media (max-width: 800px) { .ladder { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .ladder { grid-template-columns: minmax(0, 1fr); } }

/* live embeds — framed as instruments, with their own disclaimers */
.instruments { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
               gap: clamp(1rem, 2.2vw, 1.6rem); }
.instrument { border: 1px solid var(--rule-2); background: var(--bg-2); display: grid; }
.instrument__h { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.8rem;
                 padding: 0.8rem 1rem; border-bottom: 1px solid var(--rule); }
.instrument__h b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.instrument iframe { width: 100%; height: 400px; border: 0; background: #000; }
.instrument .note { padding: 0.78rem 1rem; border-top: 1px solid var(--rule); }
@media (max-width: 950px) { .instruments { grid-template-columns: minmax(0, 1fr); }
                            .instrument iframe { height: 330px; } }

/* ==================================================== CH04 — FIELD/LOOP ===
   Dominant idea: a circuit that closes. Opposed domains, an execution path
   whose knowledge grows, and a loop that visibly returns to its start.     */

.zones { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.zones > div { padding: clamp(1.1rem, 2.2vw, 1.6rem) clamp(1rem, 2vw, 1.5rem)
                        clamp(1.1rem, 2.2vw, 1.6rem) 0;
               display: grid; gap: 0.35rem; border-top: 1px solid var(--rule-2); position: relative; }
.zones > div + div { padding-left: clamp(1rem, 2vw, 1.5rem); }
.zones > div + div::before { content: ''; position: absolute; left: 0; top: 1.2rem; bottom: 1.2rem;
                             width: 1px; background: var(--rule); }
.zones b { font-family: var(--serif); font-weight: 500; font-size: var(--t2-fs); line-height: var(--t2-lh); }
.zones span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
@media (max-width: 640px) {
  .zones { grid-template-columns: minmax(0, 1fr); }
  .zones > div + div { padding-left: 0; }
  .zones > div + div::before { display: none; }
}

/* five accumulating elements resolving to one target state */
.accum { display: grid; }
.accum li {
  display: grid; grid-template-columns: 1.5rem auto minmax(0, 1fr);
  gap: 0.25rem 1rem; align-items: baseline;
  padding: 0.82rem 0; border-top: 1px solid var(--rule);
}
.accum li:first-child { border-top: 1px solid var(--rule-2); }
.accum i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--mark);
           font-variant-numeric: tabular-nums; font-weight: 600; }
.accum b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; white-space: nowrap; }
.accum span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.accum__sum {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.3rem 1rem;
  align-items: baseline; padding: 1.05rem 0 1.05rem 1.1rem;
  border-top: 2px solid var(--mark-line); border-bottom: 1px solid var(--mark-line);
  background: var(--mark-wash);
}
.accum__sum b { font-size: var(--t4-fs); line-height: var(--t4-lh); white-space: nowrap; }
.accum__sum span { font-size: var(--t4-fs); line-height: var(--t4-lh); color: var(--fg-2); }
@media (max-width: 680px) {
  .accum li { grid-template-columns: 1.4rem minmax(0, 1fr); }
  .accum li span { grid-column: 2; }
  .accum__sum { grid-template-columns: minmax(0, 1fr); }
}

/* brokerage execution: knowledge grows step by step */
/* --- CH04-B · brokerage execution, uncovered one step at a time ---------
   Each step visibly produces a row in the field record beside it, so the
   claim that execution creates evidence is shown, not asserted. */
.exec { --st: 5; }
.js .exec { --st: 0; }
.js .exec__track { min-height: 240vh; }
.exec__grid {
  position: sticky; top: 84px;
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.85fr);
  gap: clamp(1.4rem, 3.4vw, 3rem); align-items: start;
}
/* a grid item defaults to min-width:auto, so it can refuse to be narrower than
   its own content and push the page sideways however the track is sized */
.exec__grid > *, .twin__stage > *, .worlds__grid > *, .obs__stick > * { min-width: 0; }
.execpath li {
  opacity: clamp(0.16, calc((var(--st) - var(--s)) * 1), 1);
  transform: translateY(calc(9px * (1 - clamp(0, calc((var(--st) - var(--s)) * 1), 1))));
  transition: opacity 0.5s var(--e-out), transform 0.55s var(--e-out),
              border-color 0.5s var(--e-out);
}
.exec[data-step="1"] .execpath li:nth-child(1),
.exec[data-step="2"] .execpath li:nth-child(2),
.exec[data-step="3"] .execpath li:nth-child(3),
.exec[data-step="4"] .execpath li:nth-child(4),
.exec[data-step="5"] .execpath li:nth-child(5) { border-left: 2px solid var(--mark);
  padding-left: 0.8rem; margin-left: -0.8rem; }

.evrec { margin: 0; display: grid; gap: 0.75rem;
          padding: clamp(0.9rem, 2vw, 1.25rem);
          border: 1px solid var(--rule-2); background: rgba(20, 23, 26, 0.02); }
.evrec__k { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--mark); font-weight: 600; }
.evrec__stack { display: grid; }
.evrec__r {
  display: grid; gap: 0.1rem; padding: 0.6rem 0.7rem;
  border-top: 1px solid var(--rule);
  opacity: clamp(0, calc((var(--st) - var(--i)) * 1), 1);
  transform: translateX(calc(-8px * (1 - clamp(0, calc((var(--st) - var(--i)) * 1), 1))));
  transition: opacity 0.5s var(--e-out), transform 0.55s var(--e-out);
}
.evrec__r b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.evrec__r span { font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh); }
.evrec__sum { display: flex; align-items: baseline; gap: 0.55rem;
               font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh); }
.evrec__n { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--mark);
             font-size: var(--t4-fs); line-height: var(--t4-lh); }
.evrec__n::before { content: counter(led) ' / 5'; counter-reset: led var(--st, 0); }
.evrec { counter-reset: led var(--st, 0); }

@media (max-width: 860px) {
  /* without a pinned track there is nothing to step against, so the record and
     its steps are simply read in full rather than held back by a stepper that
     can no longer advance */
  .exec { --st: 5 !important; }
  .js .exec__track { min-height: 0; }
  .exec__grid { position: static; grid-template-columns: minmax(0, 1fr); }
  /* the outcome moves under the step rather than competing with it for width */
  .execpath li { grid-template-columns: 2.4rem minmax(0, 1fr); }
  .execpath__o { grid-column: 2; justify-self: start; text-align: left;
                 max-width: none; padding-top: 0.35rem; }
}

.execpath { display: grid; counter-reset: s; }
.execpath li {
  display: grid; grid-template-columns: 2.4rem minmax(0, 1fr) minmax(0, auto);
  gap: 0.3rem clamp(0.9rem, 2vw, 1.5rem); align-items: baseline;
  padding: clamp(0.95rem, 1.9vw, 1.2rem) 0; border-top: 1px solid var(--rule);
  position: relative;
}
.execpath li:first-child { border-top: 1px solid var(--rule-2); }
.execpath li:last-child { border-bottom: 1px solid var(--rule-2); }
.execpath__n { counter-increment: s; font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--mark);
               font-variant-numeric: tabular-nums; font-weight: 600; }
.execpath__n::before { content: '0' counter(s); }
.execpath b { display: block; font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; margin-bottom: 0.18rem; }
.execpath__b span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.execpath__o { font-size: var(--t5-fs); color: var(--text-tertiary); text-align: right;
               /* it may wrap: a nowrap outcome label was forcing the whole
                  row to min-content and pushing the page sideways */
               max-width: 13ch; justify-self: end; line-height: var(--t5-lh); }
@media (max-width: 800px) {
  .execpath li { grid-template-columns: 2rem minmax(0, 1fr); }
  .execpath__o { grid-column: 2; white-space: normal; }
}

/* THE LOOP — a circuit that closes, then runs again on what it learned.
   Scroll drives the carrier twice around. Pass 1 draws the thin live path and
   lights each station as it is reached. Pass 2 runs on the drawn track with a
   heavier stroke: same seven steps, less unknown. The counter says which pass
   the reader is watching, and the qualifier under the diagram keeps the second
   pass a hypothesis rather than a result.                                    */
/* THE MEMORY THEATRE — V2.4R2.
   R1 stacked three modules: a pass meter, a wide world, and a four-column
   card row beneath it. Measured at 1440x900, the meter to the bottom of the
   cards was 968px against 837px of usable height, so the world and the
   reading that interprets it could not be seen at once, and the four equal
   cards under the scene were the most generic possible container for it.

   They are now ONE bordered object. Its body is two columns: the world keeps
   about seventy per cent of the width and the whole height, and the eight
   readings stand beside it as a dock rather than below it as a card row. The
   height is bounded by the viewport rather than budgeted by hand, so it
   cannot drift as copy changes, and the dock's rows are fractions of that
   height, so they cannot push the world off the screen.                    */
.loop { position: relative; display: grid; gap: 0; }
.mm__lede { margin-bottom: 1.45rem; }

.mm__shell {
  border: 1px solid var(--rule-2);
  background: var(--bg);
  display: grid; grid-template-rows: auto minmax(0, 1fr);
  max-width: 1420px; overflow: hidden;
}
/* the shell is a JS enhancement over a flowing base. With no script the world
   and the readings simply stack at their natural height: nothing is capped,
   nothing is clipped, and nothing paints on top of anything else. */
.js .mm__shell { height: min(748px, calc(100dvh - 128px)); }

.mm__rail {
  border-bottom: 1px solid var(--rule);
  padding: 0.72rem clamp(0.9rem, 1.7vw, 1.5rem);
}
.mm__body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 30%); }
.js .mm__body { min-height: 0; }

.mm__world { position: relative; min-width: 0; min-height: 0; overflow: hidden; display: grid; }
.mm__world .loop__svg { width: 100%; height: auto; display: block; grid-area: 1 / 1; }
.js .mm__world .loop__svg { height: 100%; }

/* the rendered world and the drawn fallback occupy the same cell; exactly one
   of them is ever displayed. data-render is written by portal.js only after a
   2D context has been obtained and the first frame has been painted, so a
   failed renderer leaves the SVG standing rather than an empty flagship. */
.mm__cv { display: none; grid-area: 1 / 1; width: 100%; height: 100%; }
.mm__world[data-render='canvas'] .mm__cv { display: block; }
.mm__world[data-render='canvas'] .loop__svg { display: none; }

/* the reading dock: hairline-divided rows, not eight cards. Each row is a
   fraction of the shell, so the set always ends where the world ends. */
.mm__dock {
  border-left: 1px solid var(--rule); background: var(--bg-2);
  display: grid; grid-auto-rows: minmax(0, 1fr); min-height: 0; min-width: 0;
}
.mm__dock li {
  display: grid; grid-template-columns: 1.55rem minmax(0, 1fr);
  column-gap: 0.28rem; row-gap: 0.14rem; align-content: center;
  padding: 0.32rem clamp(0.75rem, 1.2vw, 1.05rem);
  border-top: 1px solid var(--rule); border-left: 2px solid transparent;
  transition: border-color var(--t-base) var(--e-out),
              background-color var(--t-base) var(--e-out);
}
.mm__dock li:first-child { border-top: 0; }
.mm__dock i {
  font-style: normal; font-size: var(--t6-fs); color: var(--text-tertiary); line-height: var(--t6-lh);
  font-variant-numeric: tabular-nums; letter-spacing: 0.05em;
  grid-row: 1; grid-column: 1;
  transition: color var(--t-base) var(--e-out);
}
.mm__dock b { grid-row: 1; grid-column: 2; font-size: var(--t4-fs); font-weight: 600; line-height: var(--t4-lh); }
.mm__dock span { grid-column: 2; font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh);
                 text-wrap: pretty; }
.mm__dock li[data-pole='q'] b { color: var(--mark); }
.mm__dock li[data-pole='b'] b { color: var(--solid); }
.mm__dock li[data-lit='true'] { border-left-color: var(--mark); background: var(--bg); }
.mm__dock li[data-lit='true'] i { color: var(--mark); }

/* the pass rail sits in the shell head, so which pass is running is read in
   the same object as the world it is running on */
.loop__meter {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: clamp(0.7rem, 1.8vw, 1.4rem);
}
.loop__cyc { display: grid; gap: 0.1rem; opacity: 0.4;
             transition: opacity var(--t-base) var(--e-out); }
.loop__cyc i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.14em;
               color: var(--mark); font-variant-numeric: tabular-nums; }
.loop__cyc b { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; color: var(--fg-2); }
.loop__cyc:last-child { text-align: right; }
.loop[data-pass='1'] .loop__cyc[data-cycle='1'],
.loop[data-pass='2'] .loop__cyc[data-cycle='2'] { opacity: 1; }
.loop[data-pass='2'] .loop__cyc[data-cycle='2'] b { color: var(--fg); }
.loop__bar { height: 2px; background: var(--rule); position: relative; overflow: hidden; }
.loop__bar i {
  position: absolute; inset: 0; display: block; background: var(--mark);
  transform: scaleX(1); transform-origin: left;
}
/* with no script both turns are shown complete, matching the drawn diagram */
.js .loop__bar i { transform: scaleX(var(--turn, 0)); }

.loop__svg { overflow: hidden; }
.loop__svg .carrier { fill: var(--mark); stroke: var(--bg); stroke-width: 2;
                      opacity: var(--carrier-o, 0); transition: opacity 0.3s var(--e-out); }
.loop__svg text { font-family: var(--sans); font-size: var(--svg-t6, 12px); fill: var(--fg-3);
                  letter-spacing: 0.01em; }
.loop__svg .st text { opacity: 0.58; transition: opacity 0.45s var(--e-out); }
.loop__svg .st[data-lit='true'] text { opacity: 1; }

/* a short viewport takes the height out of the dock rows before it takes it
   out of the world: the world is the thing that must stay dominant */
@media (max-height: 820px) {
  .mm__dock li { padding-block: 0.24rem; }
  .mm__dock b { font-size: var(--t4-fs); line-height: var(--t4-lh); }
  .mm__dock span { font-size: var(--t5-fs); line-height: var(--t5-lh); }
}
@media (max-width: 1366px) {
  .mm__body { grid-template-columns: minmax(0, 1fr) minmax(0, 32%); }
}

/* --- narrow: the shell unstacks rather than capping ---------------------
   Below a tablet the two columns cannot both hold their job, so the object
   becomes world-over-dock and the height cap is released. The world is kept
   and reframed (portal.js frameHero), never replaced by a list.           */
@media (max-width: 980px) {
  .js .mm__shell { height: auto; }
  .mm__body { grid-template-columns: minmax(0, 1fr); }
  .js .mm__world .loop__svg { height: auto; }
  /* released from the shell's height cap, the world needs its own ratio: a
     canvas has no intrinsic aspect the way the SVG's viewBox does. The camera
     lifts and pulls back at this ratio (portal.js resize), so the same world
     still reads rather than flattening into a plan. */
  .mm__world[data-render='canvas'] { aspect-ratio: 4 / 3; }
  .mm__dock { border-left: 0; border-top: 1px solid var(--rule);
              grid-auto-rows: auto; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mm__dock li { border-top: 1px solid var(--rule); padding-block: 0.7rem; }
  .mm__dock li:first-child { border-top: 0; }
  .mm__dock li:nth-child(2) { border-top: 0; }
  .mm__dock li:nth-child(odd) { border-right: 1px solid var(--rule); }
}
@media (max-width: 560px) {
  .mm__world[data-render='canvas'] { aspect-ratio: 1 / 1; }
  .mm__dock { grid-template-columns: minmax(0, 1fr); }
  .mm__dock li:nth-child(2) { border-top: 1px solid var(--rule); }
  .mm__dock li:nth-child(odd) { border-right: 0; }
  .loop__meter { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
                 grid-template-areas: 'a b' 'bar bar'; row-gap: 0.7rem; }
  .loop__cyc[data-cycle='1'] { grid-area: a; }
  .loop__cyc[data-cycle='2'] { grid-area: b; }
  .loop__bar { grid-area: bar; }
}

/* ------------------------------------------------- R2 · HOLD-OWNED TAIL --
   A block that closes an interactive Screen's argument used to be authored
   as a sibling AFTER the scroll runway. Structurally it was inside the
   Screen; semantically it was nowhere, because the runway is exhausted by
   the time it arrives and it swept through the viewport with no readable
   stable state to belong to.

   `holdnote` moves such a block INTO the Screen's pinned composition, so it
   is inside the visual frame at every HOLD the Screen has -- FINAL HOLD
   included -- and is read together with the thing it qualifies. It spans the
   full width of whichever grid composition it lands in, and it keeps S3
   COMPONENT rhythm (evidence <-> qualifier), which is exactly the relation
   it expresses.                                                             */
.holdnote { grid-column: 1 / -1; margin-top: var(--s3); }
/* the composition grids already carry their own row gap, and that gap IS the
   S3 separation -- adding the token on top of it would spend frame height
   twice and push the note out of the very frame it was moved into */
.obs__stick > .holdnote,
.worlds__grid > .holdnote,
.exec__grid > .holdnote { margin-top: 0; }
.obs__stick > .holdnote + .holdnote { margin-top: calc(var(--s3) - 0.55rem); }
/* the pinned steptrack is a plain block, so the note keeps its own rhythm
   and the hairline that separates a boundary from the model above it */
.steptrack__pin > .holdnote {
  padding-top: var(--s3); border-top: 1px solid var(--rule);
}
/* a boundary paragraph inside a scene composition is instrument-scale
   marginalia, not body copy: T5 already, but held to a narrower measure so a
   long boundary cannot become five lines and push the model out of frame */
.holdnote.qual { max-inline-size: 92ch; }
.holdnote--tight { --s3: 1rem; }

/* ------------------------------------------------------------ disclosure --
   R3. ONE treatment, applied wherever a disclosure exists, so every instance
   in the deck reads the same. The control used to be a bare `+` set in the
   DECORATIVE role token (--t7-fs, 10.88px rendered) with no box, no ring and
   no state of its own: at the right edge of a wide row it was easy to read as
   punctuation rather than as something you can open.

   The glyph now sits in its own 40x40 box -- the preferred hit target -- on a
   restrained hairline ring, at 18px optical size. The ring is the quiet part;
   the glyph is the signal, and it carries --mark, the deck's own accent, so
   the affordance is obvious at a glance without becoming a CTA pill. The
   summary row remains the full-width click target it always was, so the
   effective hit area is the row, not the box.

   Closed -> open is the authored rotation this deck already used: + turns 45
   degrees into x. Hover and keyboard focus both light the ring and tint the
   well, so the control answers a pointer and a Tab key the same way.        */
.more { border-top: 1px solid var(--rule-2); margin-top: var(--gap-idea); overflow-x: clip; }
.more > summary {
  cursor: pointer; list-style: none; padding: 0.72rem 0;
  display: flex; align-items: baseline; gap: 0.8rem;
  min-height: 40px;
  font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600;
  transition: color var(--t-fast) var(--e-out);
}
.more > summary::-webkit-details-marker { display: none; }
.more > summary::after {
  content: '+';
  margin-left: auto; align-self: center; flex: 0 0 auto;
  inline-size: 40px; block-size: 40px;
  display: grid; place-items: center;
  font-family: var(--sans); font-size: 1.125rem; font-weight: 500; line-height: 1;
  color: var(--mark);
  border: 1px solid var(--rule-2); border-radius: var(--r-xs);
  background: transparent;
  transition: transform var(--t-base) var(--e-inout),
              border-color var(--t-fast) var(--e-out),
              background-color var(--t-fast) var(--e-out);
}
.more[open] > summary::after { transform: rotate(45deg); }
.more > summary:hover { color: var(--mark); }
.more > summary:hover::after,
.more > summary:focus-visible::after {
  border-color: var(--mark);
  background: color-mix(in srgb, var(--mark) 10%, transparent);
}
/* the row already owns the focus outline (system.css); the well lights with
   it so the eye is sent to the control and not only to the row edge */
.more > summary:focus-visible { color: var(--mark); }
/* reduced motion: the closed/open DISTINCTION is information, so the glyph
   still turns -- it just does not travel to get there. */
@media (prefers-reduced-motion: reduce) {
  .more > summary::after {
    transition: border-color var(--t-fast) linear, background-color var(--t-fast) linear;
  }
}
.more__tag { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 400; color: var(--text-tertiary); }
.more__in { padding-bottom: 1.6rem; display: grid; gap: 1.1rem; }

/* ==================================================== CH05 — VALIDATION ===
   Dominant idea: evidence narrowing. Three measures at decreasing scale on
   a converging rule, each with its boundary set as marginalia.             */
@media (max-width: 880px) { .narrow { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; } }

/* validation questions — an editorial numbered list with hanging indent */
.questions { display: grid; }
.questions li {
  display: grid; grid-template-columns: 2.6rem minmax(0, 1fr);
  gap: 0.6rem clamp(0.8rem, 1.8vw, 1.3rem);
  padding: clamp(1.1rem, 2.2vw, 1.5rem) 0; border-top: 1px solid var(--rule);
}
.questions li:first-child { border-top: 1px solid var(--rule-2); }
.questions li:last-child { border-bottom: 1px solid var(--rule-2); }
.questions__n { font-family: var(--serif); font-size: var(--t2-fs); color: var(--mark);
                font-variant-numeric: tabular-nums; line-height: var(--t2-lh); }
.questions__q { font-family: var(--serif); font-weight: 500;
                font-size: var(--t2-fs); line-height: var(--t2-lh); }
.questions__pair { grid-column: 2; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
                   gap: 0.8rem clamp(1rem, 2.2vw, 2rem); }
.questions__pair > div { display: grid; gap: 0.22rem; align-content: start; }
.questions__pair dt { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.12em; color: var(--text-tertiary); }
.questions__pair dd { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
@media (max-width: 760px) {
  .questions li { grid-template-columns: 1.9rem minmax(0, 1fr); }
  .questions__pair { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================ CH06 — MODEL & FOUNDERS ===
   Dominant idea: unlocking. What opens, and what must be true first.       */
.rails { display: grid; gap: 1px; background: var(--rule); border-block: 1px solid var(--rule-2); }
.rail { background: var(--bg); display: grid;
        grid-template-columns: clamp(120px, 15vw, 190px) repeat(3, minmax(0, 1fr));
        gap: 1px; }
.rail > * { background: var(--bg); padding: clamp(0.9rem, 1.8vw, 1.3rem); }
.rail--head > * { padding-block: 0.7rem; }
.rail--head span { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.12em; color: var(--text-tertiary); }
.rail__name { font-size: var(--t2-fs); line-height: var(--t2-lh); font-weight: 600; }
.rail__cell { display: grid; gap: 0.35rem; align-content: start; }
.rail__cell b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.rail__cell span { font-size: var(--t4-fs); color: var(--text-secondary); line-height: var(--t4-lh); }
.mt { justify-self: start; font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-secondary);
      border: 1px solid var(--rule-2); padding: 0.12rem 0.4rem; margin-top: 0.15rem; }
.mt--now { color: var(--mark); border-color: var(--mark-line); }
@media (max-width: 900px) {
  .rail { grid-template-columns: minmax(0, 1fr); }
  .rail--head { display: none; }
  .rail__name { border-bottom: 1px solid var(--rule-2); font-family: var(--serif);
                font-size: var(--t2-fs); line-height: var(--t2-lh); }
}

/* ======================== CH06 · ONE MONETISATION DEPENDENCY MODEL ======
   V2.4R2. The chapter carried two systems that were semantically joined and
   visually divorced: a five-gate chain, then a qualifier, then a five-row
   revenue list, 857px apart end to end at 1440x900 against 837px of usable
   height. The gates animated on their own clock and the families never moved,
   so the relationship between them had to be inferred.

   They are now one object with a drawn spine between them:

     01    02    03    04                05
      \\     \\     \\    /                  |  conditional
       `-----`-----`--'                   |
                |                         |
        [ 05 lies across A's stem ]-------'
                |                          \\
                A                            B  C  D  E

   The truth the drawing has to hold, and does:
     01-04  progressively establish A's operational, evidence, need and
            legal-contract readiness. Solid, because those are real
            conditions being met.
     05     remains the conditional monetisation gate for A as well. It is
            drawn as a bar lying ACROSS A's stem, and everything below that
            bar -- A's own landing included -- is dashed. Nothing in this
            model is ever drawn as opened.
     05     also reveals B-E as a possibility field. Same dash, lower
            contrast, and every family keeps its own maturity badge.

   Everything below is authored twice: the base rules are the SETTLED state,
   so with no script the whole model is drawn complete and legible, and the
   `.js` rules are the ones that hold it back and let the sequence build it. */
.um { display: grid; max-width: 1420px; }
.um__g { margin-bottom: min(0.8rem, 1.4dvh); }
.um__g--b { margin-top: 0; margin-bottom: 0.75rem; }

/* the spine. Percentage geometry throughout -- the risers land on x=2.6% and
   x=5.4% of the model, and the rails in the rows are placed at those same
   percentages, so the linkage stays aligned at every viewport width. */
.um__spine--w { display: block; width: 100%; height: min(84px, 8.2dvh); }
.um__spine--n { display: none; width: 100%; height: 66px; }
.um__spine path { fill: none; vector-effect: non-scaling-stroke;
                  stroke-linecap: round; stroke-linejoin: round; }
.um__spine .um__r, .um__spine .um__stem {
  stroke: var(--mark); stroke-width: 1.3;
  stroke-dasharray: 100; stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.62s var(--e-inout), opacity 0.5s var(--e-out);
}
/* R4: non-scaling-stroke makes the dash pattern screen-space, so a fully
   offset riser still leaves a tail on a non-uniformly scaled spine. R3 only
   ever showed that for the frame before the sequence started; the orientation
   state holds indefinitely, so nothing may be drawn while nothing is claimed. */
.js .um[data-a='0'] .um__spine .um__r,
.js .um[data-a='0'] .um__spine .um__stem { opacity: 0; }
.js .um__spine .um__r {
  stroke-dashoffset: calc(100 * (1 - clamp(0, calc(var(--a, 0) - var(--i)), 1)));
}
.js .um__spine .um__stem {
  stroke-dashoffset: calc(100 * (1 - clamp(0, calc(var(--a, 0) - 3), 1)));
}
/* 05 and everything it implies: dashed, because none of it is established */
.um__spine .um__c5, .um__spine .um__a2, .um__spine .um__fan {
  stroke: rgba(11, 107, 83, 0.34); stroke-width: 1.2; stroke-dasharray: 4 5;
  opacity: 1; transition: opacity 0.6s var(--e-out);
}
.js .um__spine .um__c5, .js .um__spine .um__a2, .js .um__spine .um__fan { opacity: 0; }
.js .um[data-cond='on'] .um__spine .um__c5,
.js .um[data-cond='on'] .um__spine .um__a2 { opacity: 1; }
.um__spine .um__c5 { stroke: rgba(11, 107, 83, 0.52); stroke-width: 1.4;
                     stroke-dasharray: 5 4; }
.js .um__spine .um__fan { opacity: clamp(0, var(--fan, 0), 1); }
/* the bar 05 lays across A's stem. It never opens on this page, so it is
   never drawn as a passed gate: it stays a closed bar. */
.um__spine .um__bar {
  fill: var(--bg); stroke: var(--fg-2); stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
  opacity: 1; transform-box: fill-box; transform-origin: 50% 50%;
  transition: opacity 0.5s var(--e-out), transform 0.55s var(--e-out);
}
.js .um__spine .um__bar { opacity: 0; transform: scaleX(0.4); }
.js .um[data-cond='on'] .um__spine .um__bar { opacity: 1; transform: none; }
.um__spine .um__bart {
  fill: none; stroke: var(--fg-2); stroke-width: 1.3; vector-effect: non-scaling-stroke;
  opacity: 0.75; transition: opacity 0.5s var(--e-out) 0.1s;
}
.js .um__spine .um__bart { opacity: 0; }
.js .um[data-cond='on'] .um__spine .um__bart { opacity: 0.75; }

/* five revenue families as the field the model lands in */
.unlock { display: grid; }
.unlock li {
  position: relative;
  display: grid;
  grid-template-columns: 8.4% 2.2rem minmax(0, 1.02fr) minmax(0, 1.32fr) minmax(0, 0.94fr);
  gap: 0.28rem clamp(0.7rem, 1.6vw, 1.3rem); align-items: baseline;
  padding: min(0.9rem, 1.36dvh) 0; border-top: 1px solid var(--rule);
}
.unlock li:first-child { border-top: 1px solid var(--rule-2); }
.unlock li:last-child { border-bottom: 1px solid var(--rule-2); }
.unlock li[data-lead='true'] .unlock__l { color: var(--mark); font-size: var(--t2-fs); line-height: var(--t2-lh); }
.unlock__l { font-family: var(--serif); font-size: var(--t2-fs); line-height: var(--t2-lh); font-weight: 500; color: var(--text-secondary); }
.unlock__t b { display: block; font-size: var(--t4-fs); font-weight: 600; line-height: var(--t4-lh); }
.unlock__t span { font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary); }
.unlock__w { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.unlock__g { display: grid; gap: 0.25rem; justify-items: start; }
.unlock__g span { font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh); }

/* --- the linkage, drawn in the gutter column ----------------------------
   ::before is the dashed rail 05 runs down past A into the B-E field.
   ::after is either A's own landing (a corner off the 2.6% stem) or one
   family's tick off the 5.4% rail.                                        */
.unlock__k { display: block; }
.unlock__k::before, .unlock__k::after {
  content: ''; position: absolute; opacity: 1;
  transition: opacity 0.5s var(--e-out);
}
.unlock__k::before {
  left: 6.4%; top: 0; bottom: 0; width: 0;
  border-left: 1px dashed rgba(11, 107, 83, 0.34);
}
.unlock li:last-child .unlock__k::before { bottom: 50%; }
.unlock li:not([data-lead='true']) .unlock__k::after {
  left: 6.4%; top: 0; width: 1.9%; height: 50%;
  border-bottom: 1px dashed rgba(11, 107, 83, 0.34);
}
/* A is reached by 01-04, so its landing builds with them -- but it arrives
   through 05's bar, so the landing itself is dashed like everything below
   the bar. A is the nearest executable hypothesis, never an open product. */
.unlock li[data-lead='true'] .unlock__k::after {
  left: 3.4%; top: 0; width: 4.6%; height: 50%;
  border-left: 1px dashed var(--mark); border-bottom: 1px dashed var(--mark);
}
.js .unlock__k::before,
.js .unlock li:not([data-lead='true']) .unlock__k::after {
  opacity: clamp(0, calc(var(--fan, 0) - var(--k, 1) + 1), 1);
}
.js .unlock li[data-lead='true'] .unlock__k::after {
  opacity: clamp(0, calc(var(--a, 0) * 0.34), 1);
}

/* what 01-04 put into A, named with the gates' own words. Decoration for the
   screen reader (the gate text is already in the DOM above), state for the
   eye: each chip confirms as its gate confirms. */
.unlock__x { grid-column: 4; display: flex; flex-wrap: wrap; gap: 0.3rem;
             margin-top: min(0.5rem, 0.9dvh); }
.unlock__x i {
  font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--mark);
  border: 1px solid var(--mark-line); padding: 0.1rem 0.38rem; opacity: 1;
  transition: opacity 0.5s var(--e-out), color 0.5s var(--e-out),
              border-color 0.5s var(--e-out);
}
.js .unlock__x i { color: var(--fg-4); border-color: var(--rule); opacity: 0.5; }
.js .um[data-a='1'] .unlock__x i:nth-child(-n+1),
.js .um[data-a='2'] .unlock__x i:nth-child(-n+2),
.js .um[data-a='3'] .unlock__x i:nth-child(-n+3),
.js .um[data-a='4'] .unlock__x i:nth-child(-n+4) {
  opacity: 1; color: var(--mark); border-color: var(--mark-line);
}

/* the families themselves respond: A gains presence as its conditions are
   established, B-E only become legible once 05 has revealed the field */
.js .um[data-phase] .unlock li[data-lead='true'] {
  opacity: clamp(0.5, calc(0.5 + var(--a, 0) * 0.13), 1);
  transition: opacity 0.55s var(--e-out);
}
.js .um[data-phase] .unlock li:not([data-lead='true']) {
  opacity: clamp(0.4, calc(var(--fan, 0) - var(--k, 1) + 1), 1);
  transition: opacity 0.55s var(--e-out);
}

@media (max-width: 980px) {
  .um__spine--w { display: none; }
  .um__spine--n { display: block; }
  .unlock li { grid-template-columns: 9% 2rem minmax(0, 1fr); }
  .unlock__w, .unlock__g, .unlock__x { grid-column: 3; }
  .unlock__k::before, .unlock li:not([data-lead='true']) .unlock__k::after { left: 6.4%; }
  .unlock li[data-lead='true'] .unlock__k::after { left: 3%; width: 5%; }
}
@media (max-width: 520px) {
  .um__spine--n { height: 54px; }
  .unlock__x i { font-size: var(--t6-fs); line-height: var(--t6-lh); }
}

/* founders — two capabilities meeting on one loop */
.pair { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
        gap: clamp(1.2rem, 3vw, 2.6rem); align-items: center; }
.person { display: grid; gap: 0.55rem; align-content: start; }
.person__e { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.16em; color: var(--mark); }
.person__n { font-family: var(--serif); font-weight: 400;
             font-size: var(--t1-fs); line-height: var(--t1-lh); letter-spacing: -0.02em; }
.person__r { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.person__d { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
@media (max-width: 1000px) { .pair { grid-template-columns: minmax(0, 1fr); } }

/* planned entity chain */
.entity { display: grid; gap: 0; }
.entity__b { padding: clamp(1.1rem, 2.2vw, 1.6rem) 0; border-top: 1px solid var(--rule-2);
             display: grid; gap: 0.4rem; }
.entity__b:last-of-type { border-bottom: 1px solid var(--rule-2); }
.entity__b b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.entity__b span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.entity__j { display: flex; align-items: center; gap: 0.7rem; padding: 0.7rem 0;
             font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-secondary); }
.entity__j i { font-style: normal; color: var(--mark); }

/* ======================================================= CH07 — CAPITAL ===
   Dominant idea: a load path. Capital carries capability, which produces
   evidence, which alone opens conditional capital.                         */
.loadpath { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: clamp(1.1rem, 2.4vw, 2.2rem); }
.load { display: grid; gap: 0.8rem; align-content: start; padding-top: 1.2rem;
        border-top: 2px solid var(--rule-2); position: relative; }
.load::before { content: ''; position: absolute; top: -2px; left: 0; width: 34px; height: 2px;
                background: var(--mark); }
.load__e { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.16em; color: var(--mark); }
.load__t { font-family: var(--serif); font-weight: 500; font-size: var(--t2-fs); line-height: var(--t2-lh); }
.figure { display: grid; gap: 0.24rem; padding: 0.75rem 0; border-top: 1px solid var(--rule); }
.figure:first-of-type { border-top: 0; padding-top: 0.2rem; }
.figure b { font-family: var(--serif); font-weight: 400; line-height: var(--t1-lh);
            font-size: var(--t1-fs); letter-spacing: -0.025em;
            font-variant-numeric: tabular-nums; }
.figure b small { font-family: var(--sans); font-size: 0.36em; font-weight: 400;
                  color: var(--text-secondary); margin-left: 0.4em; letter-spacing: 0; }
.figure span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.linelist { display: grid; }
.linelist li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.4rem 0.9rem;
               align-items: baseline; padding: 0.6rem 0; border-top: 1px solid var(--rule);
               font-size: var(--t4-fs); line-height: var(--t4-lh); }
.linelist li:first-child { border-top: 0; }
.linelist b { font-weight: 600; }
.linelist em { font-style: normal; display: block; font-size: var(--t5-fs); color: var(--text-secondary);
               line-height: var(--t5-lh); margin-top: 0.12rem; }
.linelist li > span + span,
.linelist .amt { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--fg); }
@media (max-width: 1000px) { .loadpath { grid-template-columns: minmax(0, 1fr); } }

/* round terms — a settlement table, not a pricing grid */
.terms { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.terms > div { padding: clamp(1.2rem, 2.4vw, 1.8rem) clamp(1rem, 2vw, 1.5rem)
                        clamp(1.2rem, 2.4vw, 1.8rem) 0;
               border-top: 1px solid var(--rule-2); display: grid; gap: 0.3rem;
               align-content: start; position: relative; }
.terms > div + div { padding-left: clamp(1rem, 2vw, 1.5rem); }
.terms > div + div::before { content: ''; position: absolute; left: 0; top: 1.4rem; bottom: 1.4rem;
                             width: 1px; background: var(--rule); }
.terms dt { font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-secondary); }
.terms dd { font-family: var(--serif); font-weight: 400; line-height: var(--t2-lh);
            font-size: var(--t2-fs); letter-spacing: -0.02em; }
.terms .note { font-size: var(--t5-fs); line-height: var(--t5-lh); }
.terms > div:first-child { border-top-color: var(--mark); }
.terms > div:first-child dd { color: var(--mark); }
@media (max-width: 900px) { .terms { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) {
  .terms { grid-template-columns: minmax(0, 1fr); }
  .terms > div + div { padding-left: 0; }
  .terms > div + div::before { display: none; }
}

/* budget + sequence inside disclosures */
.ledger { width: 100%; font-size: var(--t4-fs); line-height: var(--t4-lh); min-width: 520px; }
.ledger th, .ledger td { text-align: left; padding: 0.55rem 0.7rem;
                         border-bottom: 1px solid var(--rule); vertical-align: top; }
.ledger thead th { font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-tertiary); font-weight: 500;
                   border-bottom-color: var(--rule-2); }
.ledger td:nth-child(2) { text-align: right; font-variant-numeric: tabular-nums;
                          white-space: nowrap; color: var(--fg); }
.ledger td:nth-child(3) { color: var(--text-secondary); font-size: var(--t5-fs); line-height: var(--t5-lh); }
.ledger tfoot td { font-weight: 600; color: var(--fg); border-top: 1px solid var(--rule-2); }
.scroller { overflow-x: auto; }

.sequence { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.sequence li { flex: 1 1 180px; padding: 0.8rem 0.95rem; border-top: 2px solid var(--rule-2);
               display: grid; gap: 0.22rem; }
.sequence b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.sequence span { font-size: var(--t4-fs); color: var(--text-secondary); line-height: var(--t4-lh); }

.layers { display: grid; gap: 1.2rem; }
.layer { display: grid; gap: 0.85rem; }
.layer__h { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 0.9rem;
            padding-bottom: 0.6rem; border-bottom: 1px solid var(--rule-2); }
.layer__h b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }

/* ---------------------------------------------------- the closing plate */
.close {
  background: #0a0b0a; color: #f4f2ec;
  padding-block: clamp(5rem, 12vw, 11rem);
  position: relative; overflow: hidden;
}
.close__in { display: grid; justify-items: center; gap: clamp(1.4rem, 3vw, 2.4rem);
             text-align: center; }
.close__marks { display: grid; grid-template-columns: auto auto auto; align-items: center;
                gap: clamp(0.9rem, 2.4vw, 2rem); }
.close__marks .mark { height: clamp(76px, 11vw, 148px); }
.close__marks .monument__arrow svg { width: clamp(40px, 6vw, 84px); }
.close__t { font-family: var(--serif); font-weight: 300;
            font-size: var(--t0-fs); line-height: var(--t0-lh);
            letter-spacing: -0.025em; max-width: 20em; }
.close .lede { text-align: center; margin-inline: auto; }
.close__chain { font-size: var(--t4-fs); color: #a6aca5; line-height: var(--t4-lh); max-width: 44em; }
.close__chain b { color: #f4f2ec; font-weight: 600; }

/* downloads */
.dl-groups { display: grid; gap: clamp(2rem, 4vw, 3.4rem); }
.dl-group { display: grid; gap: 0.65rem; }
.dl-group h3 { font-size: var(--t5-fs); line-height: var(--t5-lh); font-weight: 600; color: var(--text-secondary); }
.dl-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.dl-card { padding: clamp(1.2rem, 2.4vw, 1.7rem) clamp(1rem, 2vw, 1.5rem) clamp(1.2rem, 2.4vw, 1.7rem) 0; border-top: 1px solid var(--rule-2); display: grid; gap: 0.42rem; align-content: start; }
.dl-card + .dl-card { padding-left: clamp(1rem, 2vw, 1.5rem); border-left: 1px solid var(--rule); }
.dl-card b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.dl-card > span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.dl-card em { font-style: normal; font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-tertiary); }
.dl-card p { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; margin-top: 0.3rem; }
.dl-card p a { color: var(--fg); font-size: var(--t5-fs); line-height: var(--t5-lh); font-weight: 600; text-decoration: underline; text-underline-offset: 0.18em; }
.dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.dl a, .dl > div {
  padding: clamp(1.2rem, 2.4vw, 1.7rem) clamp(1rem, 2vw, 1.5rem)
           clamp(1.2rem, 2.4vw, 1.7rem) 0;
  border-top: 1px solid var(--rule-2); display: grid; gap: 0.42rem;
  align-content: start; position: relative;
  transition: border-color var(--t-fast) var(--e-out);
}
.dl a + a { padding-left: clamp(1rem, 2vw, 1.5rem); }
.dl a + a::before { content: ''; position: absolute; left: 0; top: 1.4rem; bottom: 1.4rem;
                    width: 1px; background: var(--rule); }
.dl a:hover { border-top-color: var(--mark); }
.dl b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; display: flex; align-items: center; gap: 0.5rem; }
.dl span { font-size: var(--t4-fs); color: var(--fg-2); line-height: var(--t4-lh); }
.dl em { font-style: normal; font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-tertiary); }
@media (max-width: 860px) {
  .dl-cards { grid-template-columns: minmax(0, 1fr); }
  .dl-card, .dl-card + .dl-card { padding-left: 0; border-left: 0; }
  .dl { grid-template-columns: minmax(0, 1fr); }
  .dl a + a { padding-left: 0; }
  .dl a + a::before { display: none; }
}

/* ---------------------------------------------------------------- footer */
.foot { background: #070807; color: #a6aca5; padding-block: clamp(2.6rem, 5vw, 4rem);
        border-top: 1px solid rgba(244,242,236,0.12); }
.foot__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
              gap: clamp(1.6rem, 3.5vw, 3.5rem); }
.foot__brand { font-weight: 700; letter-spacing: 0.16em; font-size: var(--t4-fs); line-height: var(--t4-lh); color: #f4f2ec; }
.foot p { font-size: var(--t5-fs); line-height: var(--t5-lh); }
.foot p + p { margin-top: 0.7rem; }
.foot b { color: #d6d9d3; font-weight: 600; }
.foot__meta { font-size: var(--t6-fs); line-height: var(--t6-lh); color: #7f857d; margin-top: 1.3rem; }
@media (max-width: 800px) { .foot__grid { grid-template-columns: minmax(0, 1fr); } }

/* ==========================================================================
   V2.2 LIVING SCENES
   One block per authored scene. Every hidden start state is gated on .js, so
   with no script every scene renders in its settled composition. Every ambient
   animation declares animation-play-state: var(--amb, paused), so it runs only
   in a settled scene that is on screen.
   ========================================================================== */


/* ------------------------------------------- CH02 · data construction --- */
.js .build[data-stage] .build__out li {
  opacity: clamp(0.28, calc(var(--rows, 4) - var(--s)), 1);
  transform: translateY(calc(8px * (1 - clamp(0, calc(var(--rows, 4) - var(--s)), 1))));
  transition: opacity 0.55s var(--e-out), transform 0.6s var(--e-out);
  transition-delay: 0ms;
}
.build__out { position: relative; overflow: hidden; }
@keyframes scanDown {
  0%   { transform: translateY(-4%); opacity: 0; }
  14%  { opacity: 0.55; }
  86%  { opacity: 0.55; }
  100% { transform: translateY(104%); opacity: 0; }
}
.js .build[data-scene='settled'] .build__out::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: var(--mark-line); pointer-events: none;
  animation: scanDown 11s linear infinite;
  animation-play-state: var(--amb, paused);
}

/* ============================================ CH03 · the product theatre
   The stage takes the chapter over: the plate stands in a lit volume, the act
   in play is named by a ghost numeral, and the light moves with the act.     */
@property --spot { syntax: '<percentage>'; inherits: true; initial-value: 50%; }
@property --ap { syntax: '<number>'; inherits: true; initial-value: 1; }

.theatre__cue { display: none; }
.js .theatre__cue {
  display: block; position: relative; height: 1.15rem; margin-top: -0.2rem;
  font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--text-tertiary);
}
.js .theatre__cue span { position: absolute; inset: 0; transition: opacity 0.5s var(--e-out); }
.js .theatre__cue-b { opacity: 0; }
.js .theatre[data-auto='done'] .theatre__cue-a { opacity: 0; }
.js .theatre[data-auto='done'] .theatre__cue-b { opacity: 1; }

.stage {
  --spot: 50%;
  background:
    radial-gradient(120% 66% at var(--spot) 0%, rgba(99, 214, 176, 0.11) 0%, rgba(99, 214, 176, 0) 62%),
    linear-gradient(180deg, #151b19 0%, #0b0e0d 46%, #060807 100%);
  transition: --spot 0.9s var(--e-inout);
}
.theatre[data-act='1'] .stage { --spot: 20%; }
.theatre[data-act='2'] .stage { --spot: 50%; }
.theatre[data-act='3'] .stage { --spot: 80%; }

.stage__ghost {
  position: absolute; right: clamp(0.5rem, 1.6vw, 1.3rem); bottom: clamp(0.1rem, 0.8vw, 0.5rem);
  z-index: 0; pointer-events: none;
  font-family: var(--serif); font-weight: 300; line-height: 0.74;
  font-size: var(--t7-fs); letter-spacing: -0.04em;
  color: #f4f2ec; opacity: 0.08;
}
.theatre[data-act='1'] .stage__ghost::before { content: '1'; }
.theatre[data-act='2'] .stage__ghost::before { content: '2'; }
.theatre[data-act='3'] .stage__ghost::before { content: '3'; }
@media (max-width: 620px) { .stage__ghost { display: none; } }

/* while an act plays, its own sequence arrives in order */
.js [data-seq] { --n: 4; }
.js #ac3 [data-seq] { --n: 8; }
.js [data-seq] .chip {
  opacity: clamp(0.3, calc(var(--ap, 1) * (var(--n) + 1.2) - var(--i)), 1);
  transition: opacity 0.42s var(--e-out);
}
.statebar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem 0.5rem; }
.statebar__go { position: relative; display: block; width: 20px; height: 1px; background: var(--rule-2); }
.statebar__go::after {
  content: ''; position: absolute; right: 0; top: -2.5px;
  border-left: 5px solid var(--rule-3); border-top: 2.5px solid transparent;
  border-bottom: 2.5px solid transparent;
}
.js .statebar > * {
  opacity: clamp(0.24, calc(var(--ap, 1) * 4.6 - var(--i) * 1.6), 1);
  transition: opacity 0.42s var(--e-out);
}
/* the plate is what the reader came for: give it the room on wide screens */
@media (min-width: 1200px) {
  .stage__act { grid-template-columns: minmax(0, 1.62fr) minmax(0, 1fr); }
  .stage { padding: clamp(1.1rem, 2.2vw, 1.9rem) clamp(1.1rem, 2.2vw, 1.9rem) clamp(1.9rem, 3.6vw, 3rem); }
}
/* the station not in play steps back while the performance runs */
.js .theatre[data-auto='play'] .track__st:not([aria-selected='true']) { opacity: 0.52; }
.js .track__st { transition: opacity 0.5s var(--e-out), color var(--t-fast) var(--e-out); }

/* ================================== CH04-A · two signal worlds, one field
   The two lanes do not look alike, and that is the argument: the machine lane
   is evenly spaced and comparable, the human lane arrives in bursts.         */
/* ================================== CH04-A · TWO INFORMATION WORLDS ====
   Two things that are not alike must not be drawn alike. The machine world is
   a register: uniform cells, ruled rows, everything in the same order every
   time. The human world is not a register: fragments at angles, different
   sizes, arriving in bursts, each tied back to the person it came from.
   Both write into one operating field, which gains a band for every item
   either world contributes. Steps uncover one item from each at a time.   */
.worlds { --st: 3; }
.js .worlds { --st: 0; }
.js .worlds__track { min-height: 210vh; }
.worlds__grid {
  position: sticky; top: 86px;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.4rem, 3vw, 3rem) clamp(1.6rem, 4vw, 4rem);
  align-items: start;
}
.world h3 {
  font-family: var(--serif); font-weight: 500;
  font-size: var(--t2-fs); line-height: var(--t2-lh); margin-bottom: 0.3rem;
}
.world__note { font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--text-tertiary); margin-bottom: 1rem; }
.world__list { display: grid; gap: 0; margin-top: 1.1rem; }
.world__list li {
  display: grid; grid-template-columns: 2rem minmax(0, 1fr); gap: 0.5rem;
  padding: 0.68rem 0; border-top: 1px solid var(--rule);
  font-size: var(--t4-fs); line-height: var(--t4-lh); color: var(--fg-2);
  opacity: clamp(0, calc((var(--st) - var(--s)) * 1), 1);
  transform: translateY(calc(9px * (1 - clamp(0, calc((var(--st) - var(--s)) * 1), 1))));
  transition: opacity 0.5s var(--e-out), transform 0.55s var(--e-out);
}
.world__n { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--mark);
            font-variant-numeric: tabular-nums; padding-top: 0.24rem; }

/* the register: order is the whole point, so it is drawn as order */
.reg { display: grid; gap: 6px; }
.reg__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 0.7rem; padding: 0.42rem 0.6rem;
  border: 1px solid var(--rule); background: rgba(20, 23, 26, 0.025);
  opacity: clamp(0, calc((var(--st) - var(--i)) * 1), 1);
  transform: translateX(calc(-10px * (1 - clamp(0, calc((var(--st) - var(--i)) * 1), 1))));
  transition: opacity 0.5s var(--e-out), transform 0.55s var(--e-out);
}
.reg__k { font-size: var(--t6-fs); line-height: var(--t6-lh); color: var(--text-secondary); letter-spacing: 0.01em; }
.reg__cells { display: flex; gap: 3px; }
.reg__cells i { width: 9px; height: 9px; background: var(--mark-wash);
                border: 1px solid var(--mark-line); display: block; }

/* the fragments: irregular by design, and each one carries who it came from */
.frag { display: grid; gap: 9px; padding-left: 6px; }
.frag__c {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.55rem;
  align-items: center; justify-self: start; max-width: 92%;
  padding: 0.5rem 0.8rem 0.56rem;
  background: var(--bg-2); border: 1px solid var(--rule-2);
  box-shadow: 2px 3px 0 rgba(20, 23, 26, 0.05);
  transform: rotate(var(--r)) translateX(var(--x));
  opacity: clamp(0, calc((var(--st) - var(--i)) * 1), 1);
  transition: opacity 0.5s var(--e-out) calc(var(--i) * 60ms);
}
.frag__c:nth-child(2) { max-width: 100%; }
.frag__who {
  width: 15px; height: 15px; border-radius: 50%; flex: none;
  border: 1px solid var(--mark); background: var(--mark-wash); position: relative;
}
.frag__who::after {
  content: ''; position: absolute; left: 50%; top: 3px; width: 4px; height: 4px;
  margin-left: -2px; border-radius: 50%; background: var(--mark);
}
.frag__t { font-size: var(--t6-fs); color: var(--fg-2); line-height: var(--t6-lh); }

/* the one field both worlds write into: it gains a band per contribution */
.opfield {
  grid-column: 1 / -1; margin: 0;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: clamp(0.9rem, 2.4vw, 2rem);
  padding: clamp(0.9rem, 2vw, 1.3rem) clamp(1rem, 2.2vw, 1.5rem);
  border: 1px solid var(--rule-2); background: rgba(20, 23, 26, 0.02);
}
.opfield__k { font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.1em; color: var(--mark);
              font-weight: 600; white-space: nowrap; }
.opfield__stack { display: grid; gap: 5px; }
/* one band per contribution: the field is not a box with a label on it, it is
   the thing that visibly gets thicker as the two worlds write into it */
.opfield__b {
  height: 9px; background: var(--mark-wash); border-left: 2px solid var(--mark);
  width: calc(46% + var(--i) * 9%); transform-origin: 0 50%;
  transform: scaleX(clamp(0.03, calc((var(--st) * 2 - var(--i)) * 1), 1));
  opacity: clamp(0.14, calc((var(--st) * 2 - var(--i)) * 1), 1);
  transition: transform 0.7s var(--e-out), opacity 0.5s var(--e-out);
  transition-delay: calc(var(--i) * 55ms);
}
.opfield__out {
  font-size: var(--t5-fs); line-height: var(--t5-lh); font-weight: 600; color: var(--fg); white-space: nowrap;
  opacity: clamp(0, calc((var(--st) - 2) * 1), 1);
  transform: translateX(calc(-8px * (1 - clamp(0, calc((var(--st) - 2) * 1), 1))));
  transition: opacity 0.6s var(--e-out), transform 0.6s var(--e-out);
}

@media (max-width: 860px) {
  .worlds { --st: 3 !important; }
  .js .worlds__track { min-height: 0; }
  .worlds__grid { position: static; grid-template-columns: minmax(0, 1fr); }
  .opfield { grid-template-columns: minmax(0, 1fr); gap: 0.7rem; }
  .opfield__out { justify-self: end; }
}


/* ======================================= CH04-C · LIVING MARKET MEMORY
   The hero was a circle with stations on it. It is now the market: a field of
   candidate spaces the loop actually moves through. The improvement is not a
   second coloured line or a faster dot -- the FIELD behaves differently on the
   second pass, because what the first pass verified is written into it.
   Route lengths are measured from the drawn geometry by portal.js and
   published on the element, so "shorter" is a fact of the drawing.        */
/* --- CH04 · the operating world, V2.4R2 ---------------------------------
   R1 made memory the subject and demoted the route, which was right. What it
   did not fix is that the frame still read as a technical drawing: one flat
   axonometric, twenty interchangeable blocks in three flat tones, hairline
   creases everywhere and a translucent quad standing in for memory.

   The material system below is the repair. There is ONE key light, high and
   to the upper left, and every surface in the world is graded against it. No
   plane in this scene is flat-filled: the air has a lit upper region, the
   ground is lit rather than filled, each mass carries a top / lit face /
   shadow face gradient, distance lifts mass toward the air colour, contact
   shadows are soft and warm rather than grey lozenges, and a static grain and
   vignette sit over the frame so it stops reading as flat vector.

   The test the brief sets is unchanged: hide the route, and PASS 2 must still
   be obviously the smarter world. It is, because memory changes the OBJECTS
   and the GROUND: verified spaces rise, light and pool light beneath them,
   answered unknowns gain a solid head, ruled-out candidates collapse to the
   footprint they leave behind, and the ground the first pass surveyed stops
   being unknown.                                                           */
.loop {
  /* R2b. The first render came back washed: a low tonal range across the
     whole frame and a memory stratum opaque enough to green-tint everything
     under it, which is the "flat translucent plane" the repair exists to
     remove. The range below is widened at both ends -- a lighter sky, a
     deeper far ground, genuinely dark shadow faces -- and the stratum is
     dropped to a wash so that what reads on it is the RECORDS, not the
     sheet. */
  --mm-air-hi:      #fcfaf6;
  --mm-air-lo:      #cdc4b0;
  --mm-ground-hi:   #f0e9d9;
  --mm-ground-mid:  #d3c9b3;
  --mm-ground-lo:   #9b9179;
  --mm-top-hi:      #f2ede1;
  --mm-top-lo:      #d2cab6;
  --mm-lit-hi:      #f6fbf4;
  --mm-lit-lo:      #cfe3cd;
  --mm-ll-hi:       #c2baa8;
  --mm-ll-lo:       #a09781;
  --mm-lr-hi:       #837b6b;
  --mm-lr-lo:       #625b4d;
  --mm-strat-dense: rgba(11, 107, 83, 0.026);
  --mm-strat-mid:   rgba(11, 107, 83, 0.012);
  --mm-strat-thin:  rgba(11, 107, 83, 0.003);
  --mm-halo-hi:     rgba(11, 107, 83, 0.30);
  --mm-halo-lo:     rgba(11, 107, 83, 0);
  --mm-vig-in:      rgba(20, 23, 26, 0);
  --mm-vig-out:     rgba(20, 23, 26, 0.13);
  --mm-haze:        #e8e2d5;
  --mm-shadow:      rgba(52, 42, 26, 0.36);
  --mm-glaze:       rgba(44, 54, 58, 0.38);
}

/* --- the air the world stands in --------------------------------------- */
.loop__svg .mm-air__p { fill: url(#mmAir); }

/* --- the ground: lit, not filled --------------------------------------- */
.loop__svg .mm-field__p { fill: url(#mmGround); }
.loop__svg .mm-field__e { fill: none; stroke: rgba(11, 107, 83, 0.46); stroke-width: 1.4; }
/* the survey grid recedes instead of ruling the whole plane at one weight */
.loop__svg .mm-field__g { fill: none; stroke: rgba(20, 23, 26, 0.16); stroke-width: 1;
                          opacity: calc(0.18 + var(--z, 1) * 0.82); }
/* the two commercial areas of the round, as zones rather than as labels */
.loop__svg .mm-zone { fill: rgba(20, 23, 26, 0.026); stroke: none; }
.loop__svg .mm-zone--b { fill: rgba(11, 107, 83, 0.030); }

/* --- the ground the first pass surveyed --------------------------------- */
.loop__svg .mm-known__p { fill: rgba(11, 107, 83, 0.10); }
.loop__svg .mm-known__e { fill: none; stroke: rgba(11, 107, 83, 0.42);
                          stroke-width: 1.2; stroke-dasharray: 8 7; }
.loop__svg .mm-known { opacity: 0; transition: opacity 1s var(--e-out); }
.loop[data-mem="on"] .mm-known { opacity: calc(0.3 + 0.7 * var(--mp, 0)); }

/* the light a verified space pools on the ground it stands on */
.loop__svg .mm-halo { fill: url(#mmHalo); opacity: 0; }
.loop[data-mem="on"] .mm-halo {
  opacity: clamp(0, calc(var(--mp, 0) * 2.2 - var(--i) * 0.3), 0.9);
  transition: opacity 0.8s var(--e-out) calc(var(--i) * 80ms);
}

/* --- a candidate space: three visible faces under one light -------------
   R1 drew the top, the visible lower-left face and, by mistake, the HIDDEN
   upper-left one, while never drawing the visible lower-right face, so every
   mass in the field had a hole on its right side and a false crease down its
   front. The generator now emits top + lower-left + lower-right only.      */
.loop__svg .mc__top { fill: url(#mmTop); }
.loop__svg .mc__ll  { fill: url(#mmLL); }
.loop__svg .mc__lr  { fill: url(#mmLR); }
.loop__svg .mm-c__edge { fill: none; stroke: rgba(20, 23, 26, 0.50); stroke-width: 1;
                         opacity: calc(1 - var(--z, 0) * 0.42); }
/* the floors of a stacked space, and the glazed frontage of a strip */
.loop__svg .mm-c__flr { fill: none; stroke: rgba(20, 23, 26, 0.17); stroke-width: 1; }
.loop__svg .mm-c__front { fill: var(--mm-glaze); }
/* distance lifts mass toward the colour of the air it is seen through */
.loop__svg .mm-c__haze { fill: var(--mm-haze); opacity: calc(var(--z, 0) * 0.30); }
/* a contact shadow with weight and warmth, thrown along the key light */
.loop__svg .mm-c__sh { fill: var(--mm-shadow); filter: url(#mmSoft);
                       opacity: calc(1 - var(--z, 0) * 0.42);
                       transition: opacity 0.8s var(--e-out); }
/* the footprint a ruled-out candidate leaves behind */
.loop__svg .mm-c__flat { fill: none; stroke: rgba(20, 23, 26, 0.32); stroke-width: 1;
                         stroke-dasharray: 5 5; opacity: 0; }
.loop__svg .mm-c__q    { color: var(--mark-paper); }
.loop__svg .mm-c__b    { color: var(--solid); opacity: 0; }
.loop__svg .mm-c__conf { fill: none; stroke: var(--mark); stroke-width: 1.8;
                         opacity: 0; transform-box: fill-box; transform-origin: 50% 50%;
                         transform: scale(0.55); }
.loop__svg .mm-c {
  transform-box: fill-box; transform-origin: 50% 100%;
  transition: opacity 0.75s var(--e-out), transform 0.9s var(--e-out);
}
.loop__svg .mm-c__conf, .loop__svg .mm-c__b, .loop__svg .mm-c__q,
.loop__svg .mm-c__flat, .loop__svg .mc__top {
  transition: opacity 0.6s var(--e-out), transform 0.7s var(--e-out),
              fill 0.7s var(--e-out), stroke 0.7s var(--e-out);
}

/* --- WHAT MEMORY CHANGES ABOUT THE WORLD --------------------------------
   Pass 2 is not the same line drawn shorter. The world behaves differently:
     1. a ruled-out candidate collapses to the footprint it leaves behind,
        BEFORE the loop would have reached it
     2. a verified space rises, takes a lit head and an emerald edge, and
        pools light on the ground
     3. an answered unknown gains a solid head and stops being unknown
   The route is short because of all three, not instead of them.           */
.loop[data-mem="on"] .mm-c--g .mm-c__conf { opacity: 0.9; transform: scale(1); }
.loop[data-mem="on"] .mm-c--g .mm-c__b    { opacity: 1; }
.loop[data-known="true"] .mm-c--g .mc__top  { fill: url(#mmTopLit); }
.loop[data-known="true"] .mm-c--g .mm-c__edge { stroke: var(--mark-paper); stroke-width: 1.3;
                                                opacity: 0.85; }
.loop[data-known="true"] .mm-c--u .mm-c__q    { opacity: 0; }
.loop[data-known="true"] .mm-c--u .mc__top  { fill: url(#mmTop); }
.loop[data-known="true"] .mm-c--w {
  opacity: 0.42; transform: translateY(13px) scaleY(0.055);
}
.loop[data-known="true"] .mm-c--w .mm-c__flat { opacity: 1; }
.loop[data-known="true"] .mm-c--w .mm-c__sh   { opacity: 0.1; }
.loop[data-known="true"] .mm-c--u  { opacity: 0.82; }
.loop[data-known="true"] .mm-c--g  { transform: translateY(-11px); }

/* --- THE MEMORY STRATUM -------------------------------------------------
   Held above the field it came from, wired down to the spaces that produced
   it, casting a shadow onto the world and shedding light back onto the
   ground it surveyed. Its surface is graded: dense over what the first pass
   actually covered, dissolving toward what it did not, so the shape of the
   layer is itself a statement about what is known.                        */
.loop__svg .mm-cast__p { fill: rgba(20, 23, 26, 0.055); filter: url(#mmCast); }
.loop__svg .mm-cast { opacity: 0; transition: opacity 1s var(--e-out); }
.loop[data-mem="on"] .mm-cast { opacity: var(--mp, 0); }
.loop__svg .mm-strat__p   { fill: url(#mmStrat); }
.loop__svg .mm-strat__e   { fill: none; stroke: rgba(11, 107, 83, 0.55); stroke-width: 1.3;
                            stroke-dasharray: 10 7; }
.loop__svg .mm-strat__rim { fill: rgba(11, 107, 83, 0.14); }
.loop__svg .mm-strat__rim--b { fill: rgba(11, 107, 83, 0.22); }
.loop__svg .mm-strat__g   { fill: none; stroke: rgba(11, 107, 83, 0.09); stroke-width: 1; }
.loop__svg .mm-strat__t   { fill: rgba(11, 107, 83, 0.55); stroke: none; opacity: 0.75; }
.loop__svg .mm-strat__t--f { fill: rgba(11, 107, 83, 0.26); opacity: 0.5; }
.loop__svg .mm-strat {
  opacity: 0; transform: translateY(30px);
  transition: opacity 0.9s var(--e-out), transform 1.1s var(--e-out);
}
.loop[data-mem="on"] .mm-strat { opacity: calc(0.25 + 0.75 * var(--mp, 0)); transform: none; }

/* a record is a plate with thickness, catching the same key light */
.loop__svg .mm-rec__w { fill: none; stroke: rgba(11, 107, 83, 0.48); stroke-width: 1.1;
                        stroke-dasharray: 3 4; }
.loop__svg .mm-rec__p { fill: #f6f3ec; stroke: var(--mark); stroke-width: 1.3; }
.loop__svg .mm-rec__d { fill: rgba(11, 107, 83, 0.42); }
.loop__svg .mm-rec__d--b { fill: rgba(11, 107, 83, 0.60); }
.loop__svg .mm-rec__l { fill: none; stroke: var(--mark); stroke-width: 1.6; }
.loop__svg .mm-rec--s .mm-rec__p { fill: #f3efe6;
                                   stroke: rgba(11, 107, 83, 0.55); stroke-width: 1; }
.loop__svg .mm-rec--s .mm-rec__d { fill: rgba(11, 107, 83, 0.26); }
.loop__svg .mm-rec--s .mm-rec__d--b { fill: rgba(11, 107, 83, 0.38); }
.loop__svg .mm-rec--s .mm-rec__l { stroke: rgba(11, 107, 83, 0.6); stroke-width: 1.3; }
.loop__svg .mm-rec {
  opacity: 0; transform: translateY(18px);
  transition: opacity 0.6s var(--e-out), transform 0.7s var(--e-out);
}
.loop[data-mem="on"] .mm-rec {
  opacity: clamp(0, calc(var(--mp, 0) * 2.4 - var(--i) * 0.2), 1);
  transform: none; transition-delay: calc(var(--i) * 70ms);
}

/* the relationships the first pass found, written back */
.loop__svg .mem__c {
  fill: none; stroke: var(--mark-line); stroke-width: 1.4; stroke-linecap: round;
  stroke-dasharray: var(--len, 400);
  stroke-dashoffset: calc(var(--len, 400) *
    (1 - clamp(0, calc(var(--mp, 0) * 1.7 - var(--i) * 0.18), 1)));
}

/* --- the two routes: consequence, not subject ---------------------------
   Pass 1 is a search and is drawn as one. Pass 2 is a decision, but it is
   still only the OUTPUT of the learned field, so it is thin and low-chroma
   and it never out-contrasts the memory it came from.                     */
.loop__svg .mm-route { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.loop__svg .mm-route--1 {
  stroke: rgba(20, 23, 26, 0.30); stroke-width: 1.3; stroke-dasharray: var(--len, 3000);
  stroke-dashoffset: calc(var(--len, 3000) * (1 - var(--p, 0)));
  transition: stroke 0.8s var(--e-out), opacity 0.8s var(--e-out);
}
.loop__svg .mm-route--2 {
  stroke: rgba(11, 107, 83, 0.55); stroke-width: 1.9; opacity: 0;
  stroke-dasharray: var(--len, 1200);
  stroke-dashoffset: calc(var(--len, 1200) * (1 - var(--p, 0)));
  transition: opacity 0.5s var(--e-out);
}
.loop[data-pass="2"] .mm-route--2 { opacity: 1; }
.loop[data-pass="2"] .mm-route--1 { stroke: rgba(20, 23, 26, 0.09); opacity: 0.5; }
.loop__svg .mm-next, .loop__svg .mm-next__h {
  fill: none; stroke: rgba(11, 107, 83, 0.5); stroke-width: 1.5; stroke-linecap: round;
  stroke-linejoin: round; stroke-dasharray: 7 6;
  opacity: 0; transition: opacity 0.7s var(--e-out);
}
.loop__svg .mm-next__h { stroke-dasharray: none; }
.loop[data-next="on"] .mm-next, .loop[data-next="on"] .mm-next__h { opacity: 0.8; }

/* --- the frame furniture ------------------------------------------------
   A vignette and a grain plate: the two cheapest things that stop an SVG
   frame reading as flat vector. Both are static, rasterised once, never
   animated, and neither takes pointer events.                             */
.loop__svg .mm-vig { fill: url(#mmVig); pointer-events: none; }
.loop__svg .mm-grain { filter: url(#mmGrain); opacity: 0.065;
                       mix-blend-mode: multiply; pointer-events: none; }

/* --- the stages: numbered pins, never labels in the field ---------------
   Hairline rings, not filled dots: a stage is a place in the world, not a
   bead threaded on the route.                                             */
.loop__svg circle.pt { fill: var(--bg); stroke: var(--rule-3); stroke-width: 1.4; }
/* the hairline that ties a stage pin to the space it stands for */
.loop__svg .stl { fill: none; stroke: rgba(20, 23, 26, 0.26); stroke-width: 1;
                  stroke-dasharray: 2.5 3; }
.loop__svg circle.pt--q { stroke: var(--mark); }
.loop__svg circle.pt--b { stroke: var(--rule-3); }
.loop__svg .st[data-lit='true'] .pt { fill: rgba(11, 107, 83, 0.14); stroke: var(--mark); }
.loop__svg .st[data-lit='true'] .pt--b { fill: rgba(20, 23, 26, 0.5); stroke: var(--solid); }
.loop__svg .nnum { font-family: var(--sans); font-size: var(--svg-t6, 12px); font-weight: 600;
                   fill: var(--fg-4); font-variant-numeric: tabular-nums;
                   letter-spacing: 0.04em; }
.loop__svg .st[data-lit='true'] .nnum { fill: var(--fg-2); }
.st--mem .pt { transform-box: fill-box; transform-origin: 50% 50%;
               transition: transform 0.6s var(--e-out); }
.loop[data-mem="on"] .st--mem .pt { transform: scale(1.35); }
@keyframes memPulse { 0%, 100% { transform: scale(1.2); } 50% { transform: scale(1.55); } }
.js .loop[data-scene="settled"] .st--mem .pt {
  animation: memPulse 4.6s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}
@keyframes stratDrift { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.8; } }
/* the memory layer keeps taking records after the sequence has settled */
.js .loop[data-scene="settled"] .mm-strat__t {
  animation: stratDrift calc(11s + var(--i) * 2.4s) var(--e-soft) infinite;
  animation-delay: calc(var(--i) * -2600ms);
  animation-play-state: var(--amb, paused);
}
/* the same resolution on the reading dock: the readings that carried
   unknowns get the drawn evidence mark once the field has answered them */
.mm__dock li { position: relative; }
.mm__dock li:nth-child(n+2):nth-child(-n+4)::after {
  content: ''; position: absolute; right: 0.8rem; top: 50%;
  width: 14px; height: 5px; opacity: 0; transform: translateY(-50%);
  background: linear-gradient(180deg, var(--mark) 0 2px, transparent 2px 3px,
                              var(--mark) 3px 5px);
  transition: opacity 0.6s var(--e-out);
}
.loop[data-known="true"] .mm__dock li:nth-child(n+2):nth-child(-n+4)::after { opacity: 0.9; }

/* after both passes the short route keeps carrying one packet: the loop is
   still running, quietly, on a field it now knows */
.loop__svg .mm-sig { fill: none; stroke: var(--mark); stroke-width: 3.6;
                     stroke-linecap: round; opacity: 0; }
.js .loop[data-scene="settled"] .mm-sig {
  opacity: 0.7;
  stroke-dasharray: 11 calc(var(--len, 1100) - 11);
  animation: wireRun 9s linear infinite;
  animation-play-state: var(--amb, paused);
}

/* --- reduced motion: all three states must still be legible -------------
   Nothing moves, so the settled frame has to carry PASS 1, the write-back
   and PASS 2 at once. The first route therefore stays readable instead of
   fading almost out, which is what makes "the long route came first" a
   thing the reader can see rather than a thing they have to be told.      */
@media (prefers-reduced-motion: reduce) {
  .loop[data-pass="2"] .mm-route--1 { stroke: rgba(20, 23, 26, 0.26); opacity: 1; }
}

/* --- narrow: the world is kept, not replaced by a list ------------------
   The phone keeps the spatial memory field, framed close on the surveyed
   ground and the stratum above it (portal.js frameHero), with the reading
   dock continuing underneath as the supporting content it always was.     */
@media (max-width: 760px) {
  .loop__svg { display: block; }
  .loop__svg .nnum { font-size: var(--svg-t6, 23px); }
  .loop__svg .mm-strat__e { stroke-width: 2.2; }
  .loop__svg .mm-rec__p { stroke-width: 1.7; }
  .loop__svg .mm-route--2 { stroke-width: 2.6; }
  .loop__svg .mm-c__edge { stroke-width: 1.4; }
  .loop__svg .stl { stroke-width: 1.4; }
  .loop__svg .mm-c__conf { stroke-width: 2.4; }
}

/* ============================== CH05 · MARKET TRIAGE OBSERVATORY =======
   The section is about operational judgement, so judgement is what is drawn:
   a field of candidate dossiers being cleaned, classified, re-ordered and
   finally reduced to a queue of the ones worth a site visit. The three
   accepted measures stay, as annotation under the analysis they came from,
   which is the opposite of the count-up they used to be.                  */
.obs { --st: 5; display: grid; }
.js .obs { --st: 0; }
.js .obs__track { min-height: 300vh; }
.obs__stick { position: sticky; top: 78px; display: grid; gap: clamp(0.9rem, 2vw, 1.5rem); }

.obs__cap { display: grid; min-height: 2.2em; font-size: var(--t5-fs); line-height: var(--t5-lh); color: var(--fg-2); }
.obs__cap span {
  grid-area: 1 / 1; display: flex; align-items: baseline; gap: 0.6rem;
  opacity: 0; transform: translateY(5px);
  transition: opacity 0.4s var(--e-out), transform 0.45s var(--e-out);
}
.obs__cap i {
  font-style: normal; font-variant-numeric: tabular-nums; font-weight: 600;
  font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.12em; color: var(--mark);
  border: 1px solid var(--mark-line); padding: 0.1rem 0.4rem;
}
.obs[data-step="0"] .obs__cap span[data-for="1"],
.obs[data-step="1"] .obs__cap span[data-for="1"],
.obs[data-step="2"] .obs__cap span[data-for="2"],
.obs[data-step="3"] .obs__cap span[data-for="3"],
.obs[data-step="4"] .obs__cap span[data-for="4"],
.obs[data-step="5"] .obs__cap span[data-for="5"] { opacity: 1; transform: none; }

.obs__scene { margin: 0; display: grid; gap: 0.8rem; }
.obs__svg { width: 100%; height: auto; overflow: hidden; }

/* --- a candidate dossier ------------------------------------------------
   Not a card: a dossier of one space. It carries that space's plan, how well
   it fits, and how much of it has actually been evidenced — and no two of
   them are the same size, because no two of them are the same thing. */
.tri__p  { fill: rgba(20, 23, 26, 0.030); stroke: var(--rule-2); stroke-width: 1; }
.tri__f  { fill: rgba(20, 23, 26, 0.10); stroke: rgba(20, 23, 26, 0.46);
           stroke-width: 1.1; stroke-linejoin: round; }
.tri__fit { fill: var(--mark); }
.tri__fit--off { fill: rgba(20, 23, 26, 0.13); }
.tri__ev  { fill: var(--fg-3); }
.tri__ev--off { fill: rgba(20, 23, 26, 0.11); }
.tri__l  { fill: none; stroke: rgba(20, 23, 26, 0.16); stroke-width: 2; }
.tri__l--2 { stroke: rgba(20, 23, 26, 0.10); }
.tri__x  { fill: none; stroke: #a2402f; stroke-width: 1.4; opacity: 0; }

.tri {
  transform-box: fill-box; transform-origin: 50% 50%;
  transition: opacity 0.55s var(--e-out), transform 0.75s var(--e-inout);
  transition-delay: calc(var(--i) * 5ms);
}
/* 1 the raw field arrives */
.tri { opacity: clamp(0, calc(var(--st) * 3), 1); }

/* 2 what cannot be relied on is struck out, then dropped */
.obs[data-step="2"] .tri--dup .tri__x,
.obs[data-step="2"] .tri--stale .tri__x,
.obs[data-step="2"] .tri--unver .tri__x { opacity: 0.9; }
.tri__x { transition: opacity 0.4s var(--e-out); transition-delay: calc(var(--i) * 7ms); }
.obs[data-step="3"] .tri--dup, .obs[data-step="4"] .tri--dup, .obs[data-step="5"] .tri--dup,
.obs[data-step="3"] .tri--stale, .obs[data-step="4"] .tri--stale, .obs[data-step="5"] .tri--stale,
.obs[data-step="3"] .tri--unver, .obs[data-step="4"] .tri--unver, .obs[data-step="5"] .tri--unver {
  opacity: 0; transform: scale(0.82);
}

/* 3 what survives is classified: fit is read on one axis, evidence on the
   other, and the tiles that need a person are held forward */
.obs[data-step="3"] .tri--weak { opacity: 0.34; }
.obs[data-step="3"] .tri--ok .tri__p { fill: var(--mark-wash); stroke: var(--mark-line); }
.tri__p { transition: fill 0.5s var(--e-out), stroke 0.5s var(--e-out); }

/* 4 the order visibly changes: the shortlist pulls itself into rank order
   while everything else falls back */
.obs[data-step="4"] .tri--weak, .obs[data-step="5"] .tri--weak { opacity: 0.12; }
.obs[data-step="4"] .tri--ok:not(.tri--short),
.obs[data-step="5"] .tri--ok:not(.tri--short) { opacity: 0.14; }
.obs[data-step="4"] .tri--short .tri__p { fill: var(--mark-wash); stroke: var(--mark); }
.obs[data-step="4"] .tri--short .tri__f { stroke: var(--mark-paper); fill: rgba(11,107,83,0.12); }
/* the order visibly changes: every shortlisted dossier leaves the place it
   was found in and takes the place its rank has earned it */
.obs[data-step="4"] .tri--short {
  transform: translate(calc((var(--r) - 7.5) * 4px), calc((var(--r) - 7.5) * -13px))
             scale(1.12);
  transition-delay: calc(var(--r) * 34ms);
}

/* 5 the shortlist is lifted out of the field into the queue */
.tri-queue { opacity: 0; transition: opacity 0.6s var(--e-out); }
.tri-queue--m { display: none; }
.obs[data-step="5"] .tri-queue { opacity: 1; }
.obs[data-step="5"] .tri-field { opacity: 0.16; transition: opacity 0.7s var(--e-out); }
.tri-field { transition: opacity 0.7s var(--e-out); }
/* --- the queue: an authored field programme -----------------------------
   One datum, two runs at the accepted split, and every entry standing at the
   height its priority earns it. Nothing here is a card. */
.tri-queue__r { fill: none; stroke: var(--mark); stroke-width: 1.4; }
.tri-q__p { fill: var(--mark-wash); stroke: var(--mark); stroke-width: 1.2;
            stroke-linejoin: round; }
.tri-q__f { fill: rgba(11, 107, 83, 0.14); stroke: var(--mark-paper);
            stroke-width: 1.1; stroke-linejoin: round; }
.tri-q__fit { fill: var(--mark); }
.tri-q__n { font-family: var(--sans); font-size: var(--svg-t5, 12.5px); font-weight: 600;
            fill: var(--mark-paper); font-variant-numeric: tabular-nums; }
.tri-q__t { fill: none; stroke: var(--mark); stroke-width: 1.4; }
.tri-run__b { fill: none; stroke: var(--rule-3); stroke-width: 1.1; }
.tri-run__t { font-family: var(--sans); font-size: var(--svg-t5b, 12px); font-weight: 600;
              fill: var(--fg-2); letter-spacing: 0.06em; }
.tri-run { opacity: 0; transition: opacity 0.6s var(--e-out) 0.75s; }
.obs[data-step="5"] .tri-run { opacity: 1; }
.tri-q {
  opacity: 0; transform: translateY(-22px);
  transform-box: fill-box; transform-origin: 50% 50%;
  transition: opacity 0.45s var(--e-out), transform 0.6s var(--e-out);
}
.obs[data-step="5"] .tri-q {
  opacity: 1; transform: none; transition-delay: calc(0.1s + var(--n) * 45ms);
}

/* --- the three accepted measures, as annotation ------------------------- */
.narrow {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.6vw, 2.4rem);
  padding-top: clamp(0.9rem, 2vw, 1.4rem); border-top: 1px solid var(--rule);
}
.narrow > div { display: grid; gap: 0.16rem; align-content: start; }
/* four parallel evidence layers, not three funnel stages: the row is equal
   width by design so that no measure reads as a conversion of another. */
.narrow--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1100px) { .narrow--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.narrow__n {
  font-family: var(--serif); font-weight: 500; letter-spacing: -0.01em;
  font-size: var(--t2-fs); line-height: var(--t2-lh);
  font-variant-numeric: tabular-nums;
}
.narrow__n small { font-size: 0.62em; color: var(--text-secondary); margin-left: 0.24em;
                   font-family: var(--sans); letter-spacing: 0; }
.narrow__k { font-size: var(--t6-fs); line-height: var(--t6-lh); font-weight: 600; }
.narrow__d { font-size: var(--t5-fs); color: var(--fg-2); line-height: var(--t5-lh); }
.narrow__x { font-size: var(--t6-fs); color: var(--text-tertiary); line-height: var(--t6-lh);
             padding-top: 0.24rem; border-top: 1px dashed var(--rule-2); margin-top: 0.24rem; }

@media (max-width: 1023px) {
  /* portal.js reframes the drawing to a legible slice of the field, and the
     shortlist is re-laid two-up so the queue can still be read */
  .tri-queue:not(.tri-queue--m) { display: none; }
  .tri-queue--m { display: block; }
}
@media (max-width: 900px) {
  /* the triage still needs somewhere to happen: the scene stays pinned while
     the five stages run, on a shorter track than the desktop one */
  .js .obs__track { min-height: 190vh; }
  .obs__stick { top: 66px; }
  .narrow { grid-template-columns: minmax(0, 1fr); gap: 1.2rem; }
  .narrow > div + div { padding-top: 1rem; border-top: 1px solid var(--rule); }
}

/* ==================================== CH06 · the gates that must be true */
.gatechain { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.gate {
  position: relative; display: grid; gap: 0.26rem; align-content: start;
  padding: min(0.95rem, 1.45dvh) clamp(0.7rem, 1.4vw, 1.1rem) min(0.95rem, 1.45dvh) 0;
  border-top: 2px solid var(--rule-2);
}
.gate + .gate { padding-left: clamp(0.7rem, 1.4vw, 1.1rem); }
.gate::before {
  content: ''; position: absolute; top: -2px; left: 0; height: 2px; background: var(--mark);
  width: 100%; transform-origin: left;
  transform: scaleX(clamp(0, calc(var(--lit, 9) - var(--i)), 1));
  transition: transform 0.65s var(--e-inout);
}
.gate i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.14em; color: var(--text-tertiary);
          font-variant-numeric: tabular-nums; }
.gate b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.gate span { font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh); }
.gate--cond { border-top-style: dashed; }
.gate--cond::before { background: repeating-linear-gradient(90deg,
                        var(--mark) 0 5px, transparent 5px 10px); }
.gate--cond b { color: var(--fg-2); }
.js .gatechain[data-lit] .gate {
  opacity: clamp(0.42, calc(var(--lit, 9) - var(--i)), 1);
  transition: opacity 0.55s var(--e-out);
}
@media (max-width: 900px) {
  .gatechain { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gate:nth-child(odd) { padding-left: 0; }
  .gate:nth-child(n+3) { border-top-width: 1px; }
  .gate--cond { border-top-width: 2px; }
}
@media (max-width: 520px) {
  .gatechain { grid-template-columns: minmax(0, 1fr); }
  .gate + .gate { padding-left: 0; }
}

/* CH06 · the founders as the two engines feeding one loop */
.person { position: relative; }
.person__feed { display: block; height: 2px; background: var(--rule); margin-top: 0.9rem;
                position: relative; overflow: hidden; }
.person__feed i { position: absolute; top: 0; bottom: 0; left: 0; width: 28%;
                  background: var(--mark); transform: translateX(-40%); opacity: 0; }
.js .pair[data-stage] .person__feed i { transition: opacity 0.5s var(--e-out); }
.js .pair[data-stage='1'] [data-engine='a'] .person__feed i,
.js .pair[data-stage='2'] .person__feed i,
.js .pair[data-stage='3'] .person__feed i { opacity: 1; }
.js .pair[data-scene='settled'] .person__feed i {
  opacity: 1;
  animation: railRun 5.6s linear infinite;
  animation-play-state: var(--amb, paused);
}
.js .pair[data-scene='settled'] [data-engine='b'] .person__feed i { animation-delay: -2.8s; }
.converge--engines { transition: box-shadow 0.7s var(--e-out); }
.js .pair[data-stage='3'] .converge--engines,
.js .pair[data-scene='settled'] .converge--engines {
  box-shadow: inset 0 0 0 1px var(--mark-line);
}

/* ============================================ CH07 · capital to evidence */
.chain { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
         margin-bottom: var(--gap-idea); }
.chain li {
  position: relative; display: grid; gap: 0.24rem; align-content: start;
  padding: clamp(0.85rem, 1.7vw, 1.1rem) clamp(0.7rem, 1.4vw, 1.1rem)
           clamp(0.9rem, 1.8vw, 1.15rem) 0;
  border-top: 2px solid var(--rule-2);
}
.chain li + li { padding-left: clamp(0.7rem, 1.4vw, 1.1rem); }
.chain li::before {
  content: ''; position: absolute; top: -2px; left: 0; height: 2px; background: var(--mark);
  width: 100%; transform-origin: left;
  transform: scaleX(clamp(0, calc(var(--lit, 9) - var(--i)), 1));
  transition: transform 0.7s var(--e-inout);
}
.chain i { font-style: normal; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.16em; color: var(--mark); }
.chain b { font-size: var(--t4-fs); line-height: var(--t4-lh); font-weight: 600; }
.chain span { font-size: var(--t5-fs); color: var(--text-secondary); line-height: var(--t5-lh); }
.js .chain[data-lit] li {
  opacity: clamp(0.44, calc(var(--lit, 9) - var(--i)), 1);
  transition: opacity 0.55s var(--e-out);
}
@media (max-width: 820px) {
  .chain { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .chain li:nth-child(odd) { padding-left: 0; }
  .chain li:nth-child(n+3) { border-top-width: 1px; }
}
@media (max-width: 460px) {
  .chain { grid-template-columns: minmax(0, 1fr); }
  .chain li + li { padding-left: 0; }
}

/* what the capital actually pays for, lit in order with its amount */
.figure__flow { display: block; height: 2px; background: var(--rule); margin-top: 0.7rem;
                position: relative; overflow: hidden; }
.figure__flow i { position: absolute; inset: 0; background: var(--mark);
                  transform-origin: left; transform: scaleX(var(--flow, 1)); }
@keyframes shimmer { from { transform: translateX(-120%); } to { transform: translateX(420%); } }
.js .loadpath[data-scene='settled'] .figure__flow::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 22%;
  background: linear-gradient(90deg, transparent, rgba(244, 242, 236, 0.5), transparent);
  animation: shimmer 7s linear infinite;
  animation-play-state: var(--amb, paused);
}
.js .linelist[data-alloc] li {
  opacity: clamp(0.34, calc(var(--lit, 9) - var(--i)), 1);
  transition: opacity 0.55s var(--e-out);
}
.js .linelist[data-alloc] .amt {
  clip-path: inset(0 calc(100% - 100% * clamp(0, calc(var(--lit, 9) - var(--i)), 1)) 0 0);
  transition: clip-path 0.6s var(--e-inout);
}
/* the conditional tranche: drawn shut, acknowledged by evidence, never opened */
.figure--gated { position: relative; padding-left: 0.95rem; border-top-style: dashed; }
.figure--gated::before {
  content: ''; position: absolute; left: 0; top: 0.85rem; bottom: 0.7rem; width: 2px;
  background: repeating-linear-gradient(180deg, var(--rule-3) 0 4px, transparent 4px 9px);
}
.figure--gated b { color: var(--fg-2); }
.figure__gate { display: flex; gap: 4px; align-items: center; margin-top: 0.35rem; }
.figure__gate i { display: block; width: clamp(18px, 2.4vw, 28px); height: 2px;
                  background: var(--rule-3); transition: background 0.6s var(--e-out); }
.figure__lock {
  justify-self: start; margin-top: 0.35rem; font-size: var(--t6-fs); line-height: var(--t6-lh); letter-spacing: 0.06em;
  color: var(--text-secondary); border: 1px dashed var(--rule-2); padding: 0.16rem 0.46rem;
  transition: color 0.6s var(--e-out), border-color 0.6s var(--e-out);
}
.loadpath[data-gatelit='true'] .figure__gate i { background: var(--mark-line); }
.loadpath[data-gatelit='true'] .figure__lock { color: var(--fg-2); border-color: var(--mark-line); }

/* the 18 months as proof accumulating, not as a timeline */
.sequence--proof li { position: relative; }
.proof__b { display: block; height: 5px; margin-bottom: 0.55rem; position: relative;
            overflow: hidden;
            background: repeating-linear-gradient(90deg, var(--rule) 0 11px, transparent 11px 15px); }
.proof__b::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0;
  width: calc((var(--i) + 1) * 15px);
  background: repeating-linear-gradient(90deg, var(--mark) 0 11px, transparent 11px 15px);
  transform-origin: left; transform: scaleX(clamp(0, calc(var(--lit, 9) - var(--i)), 1));
  transition: transform 0.65s var(--e-inout);
}
.js .sequence--proof[data-lit] li {
  opacity: clamp(0.42, calc(var(--lit, 9) - var(--i)), 1);
  transition: opacity 0.55s var(--e-out);
}

/* ------------------------------------------------- the quiet closing plate */
.close__field { position: absolute; inset: 0; pointer-events: none; }
.close__field i {
  position: absolute; width: 3px; height: 3px; border-radius: 50%;
  background: rgba(99, 214, 176, 0.55); opacity: 0;
  transition: opacity 1.4s var(--e-soft);
}
.close__field i:nth-child(1) { left: 9%;  top: 26%; }
.close__field i:nth-child(2) { left: 18%; top: 62%; }
.close__field i:nth-child(3) { left: 31%; top: 18%; }
.close__field i:nth-child(4) { left: 42%; top: 78%; }
.close__field i:nth-child(5) { left: 62%; top: 22%; }
.close__field i:nth-child(6) { left: 74%; top: 68%; }
.close__field i:nth-child(7) { left: 86%; top: 34%; }
.close__field i:nth-child(8) { left: 93%; top: 74%; }
.js .close[data-stage='1'] .close__field i,
.js .close[data-stage='2'] .close__field i,
.js .close[data-stage='3'] .close__field i,
.js .close[data-stage='4'] .close__field i,
.js .close[data-stage='5'] .close__field i { opacity: 0.42; }
@keyframes closeDrift {
  0%, 100% { transform: translateY(0); opacity: 0.22; }
  50%      { transform: translateY(-8px); opacity: 0.6; }
}
.js .close[data-scene='settled'] .close__field i {
  animation: closeDrift calc(8s + var(--i) * 850ms) var(--e-soft) infinite;
  animation-delay: calc(var(--i) * -1200ms);
  animation-play-state: var(--amb, paused);
}
.close__travel { position: relative; }
.close__ticks { display: flex; gap: 5px; justify-content: center; }
.close__ticks i {
  width: 5px; height: 5px; border-radius: 50%; background: var(--mark-ink);
  opacity: 0; transform: translateX(-10px);
  transition: opacity 0.5s var(--e-out), transform 0.7s var(--e-out);
  transition-delay: calc(var(--i) * 170ms);
}
.js .close[data-stage] .close__marks .mark--q {
  opacity: 0; transform: translateY(12px);
  transition: opacity 0.9s var(--e-out), transform 1.1s var(--e-out);
}
.js .close[data-stage] .close__marks .mark--b {
  opacity: 0; transform: translateY(16px) scale(0.96);
  transition: opacity 0.9s var(--e-out), transform 1.1s var(--e-out);
}
.js .close[data-stage] .close__travel svg,
.js .close[data-stage] .close__travel > span { opacity: 0; transition: opacity 0.8s var(--e-out); }
.js .close[data-stage='2'] .close__marks .mark--q,
.js .close[data-stage='3'] .close__marks .mark--q,
.js .close[data-stage='4'] .close__marks .mark--q,
.js .close[data-stage='5'] .close__marks .mark--q { opacity: 1; transform: none; }
.js .close[data-stage='3'] .close__ticks i,
.js .close[data-stage='4'] .close__ticks i,
.js .close[data-stage='5'] .close__ticks i { opacity: 1; transform: none; }
.js .close[data-stage='3'] .close__travel svg,
.js .close[data-stage='4'] .close__travel svg,
.js .close[data-stage='5'] .close__travel svg,
.js .close[data-stage='4'] .close__travel > span,
.js .close[data-stage='5'] .close__travel > span { opacity: 1; }
.js .close[data-stage='4'] .close__marks .mark--b,
.js .close[data-stage='5'] .close__marks .mark--b { opacity: 1; transform: none; }
@keyframes closeMarkBreathe { 0%, 100% { opacity: 0.92; } 50% { opacity: 1; } }
.js .close[data-scene='settled'] .close__marks .mark--q {
  animation: closeMarkBreathe 10s var(--e-soft) infinite;
  animation-play-state: var(--amb, paused);
}

/* ------------------------------------------------------- REDUCED MOTION
   The refinement adds no meaning that only motion can deliver. Under reduced
   motion every scene, circuit, assembly and turn is shown resolved: the field
   is a still drawing, the wires are drawn, the asset is filled, the fragments
   are assembled and both loop passes are complete.                          */
@media (prefers-reduced-motion: reduce) {
  /* every ambient animation is off, not merely fast */
  .loopsig, .scene .so-pool, .scene .so-haze__b, .scene .so-sheen--drift,
  .scene .so-u--focus .so-u__lit, .scene .so-flare--hold,
  .scene .so-u[data-s='unknown'] .so-u__lit, .scene .so-mk,
  .st--mem .pt, .person__feed i, .srail__t i, .close__field i,
  .close__marks .mark--q,
  .build__out::after, .figure__flow::after { animation: none !important; }
  /* motion-only layers simply do not appear */
  .scene__gl, .loopsig, .loop__svg .carrier, .scene .so-band,
  .scene .so-sheen--drift, .scene .so-ray,
  .build__out::after, .figure__flow::after { display: none !important; }

  /* CH01 the object is a still frame in its resolved composition: the field
     is lit, the mass is set true, every unit that evidence reached carries its
     state, and the one resolved unit reads ! with no leftover ? */
  .scene .so-field, .scene .so-haze, .scene .so-mass,
  .scene .so-event, .scene .so-marks {
    translate: none !important; opacity: 1 !important;
    transform: none !important; transition: none !important;
  }
  .scene .so-face--l, .scene .so-face--r, .scene .so-cap, .scene .so-pl,
  .scene .so-sheen, .scene .so-refl, .scene .so-u, .scene .so-e {
    opacity: 1 !important; transform: none !important; transition: none !important;
    stroke-dashoffset: 0 !important;
  }
  .scene .so-u[data-s='obs'] .so-u__lit   { opacity: 0.9 !important; transition: none !important; }
  .scene .so-u[data-s='ok'] .so-u__lit,
  .scene .so-u[data-s='focus'] .so-u__lit { opacity: 1 !important; transition: none !important; }
  .scene .so-u--focus { filter: drop-shadow(0 0 15px rgba(99, 214, 176, 0.6)) !important; }
  .scene .so-flare      { opacity: 0 !important; }
  .scene .so-flare--hold { opacity: 0.62 !important; transition: none !important; }
  .scene .so-datum { transform: scaleX(1) !important; }
  /* the resolved state is the settled state: one ! , no leftover ? */
  .scene .so-mk--res  { opacity: 1 !important; }
  .scene .so-mk--swap { opacity: 0 !important; }
  .scene .so-mk--q:not(.so-mk--swap) { opacity: 1 !important; transform: none !important; }

  /* CH02 / CH04 steppers: every step is shown landed.
     Each stepper has its OWN step count (.twin 4, .worlds 3, .exec 5, .obs 5;
     see data-steps and the no-JS defaults above), so "landed" cannot be one
     shared number. A blanket --st:5 overshot .twin, whose progress rail is
     width: calc(var(--st) / 4 * 100%) — at --st:5 that is 125% of the rail,
     which pushed 227px of a 908px bar past its parent and put 167px of
     horizontal overflow on the document at 1440x900. The fix is to publish
     each stepper's real settled value, matching its own no-JS default. */
  .twin   { --st: 4 !important; }
  .worlds { --st: 3 !important; }
  .exec, .obs { --st: 5 !important; }
  .twin__track, .worlds__track, .exec__track, .obs__track { height: auto !important;
                                                           min-height: 0 !important; }
  .twin__stick, .worlds__grid, .exec__grid, .obs__stick { position: static !important; }
  .engine__steps li, .world__list li, .reg__row, .frag__c, .execpath li,
  .evrec__r, .tri, .tri-q, .tri-queue, .tri-field {
    opacity: 1 !important; transform: none !important; }
  /* the resolved end state of the triage: the field is cleaned and the
     queue is out, both readable at once */
  .tri--dup, .tri--stale, .tri--unver { display: none !important; }
  .tri__x { opacity: 0 !important; }

  /* CH02 */
  .js .build__raw li { transform: none !important; }
  .js .build[data-stage] .build__out li,
  .js .build__out li { opacity: 1 !important; transform: none !important; }

  /* CH03 */
  .track__car { transition: none !important; }
  .js .stage__act { filter: none !important; transform: none !important; }
  .js [data-seq] .chip, .js .statebar > * { opacity: 1 !important; }
  .stage { transition: none !important; }

  /* CH04 */
  .opfield__glow { opacity: 0.4 !important; }
  .loop__svg .live--2 { opacity: 0.5 !important; }
  .loop__bar i { transition: none !important; }
  .loop__svg .track--2 { opacity: 0.85 !important; }
  .mem__c { stroke-dashoffset: 0 !important; }
  .cands rect { transform: scaleY(var(--p2)) !important; }
  .nextact { opacity: 1 !important; }

  /* CH05 · CH06 · CH07 · close: the end state of every sequence */
  .js .questions[data-stage] :is(.questions__q, .questions__n),
  .js .questions[data-stage] .questions__pair > div,
  .js .narrow[data-stage] .narrow__x { opacity: 1 !important; transform: none !important; }
  .sf__rule { stroke-dashoffset: 0 !important; }
  .js .gatechain[data-lit] .gate, .js .chain[data-lit] li,
  .js .linelist[data-alloc] li, .js .sequence--proof[data-lit] li { opacity: 1 !important; }
  .js .linelist[data-alloc] .amt { clip-path: none !important; }
  .js .pair[data-stage] .person__feed i { opacity: 1 !important; }
  .js .close[data-stage] .close__marks .mark--q,
  .js .close[data-stage] .close__marks .mark--b,
  .js .close[data-stage] .close__travel svg,
  .js .close[data-stage] .close__travel > span,
  .js .close[data-stage] .close__ticks i,
  .js .close[data-stage] .close__field i {
    opacity: 1 !important; transform: none !important;
  }
}

/* ==================================== R4 · SEMANTIC STEP TRACK + INDICATOR
   CH04's learning loop and CH06's monetisation model are the two scenes on
   this page whose meaning is a SEQUENCE of states rather than one composed
   picture. R4 hands the order of that sequence to the reader: scroll selects
   a discrete semantic step, the step plays on its own authored clock, and the
   settled state then holds until the reader asks for the next one.

   Everything below is plumbing and one indicator. Nothing here re-draws
   either scene: the world, the geometry, the materials, the light, the
   camera, the model's spine and its five revenue families are all exactly
   what R3 shipped. Without .js the wrappers are inert, the run of scroll does
   not exist, and both scenes flow and read as their settled selves.        */
.steptrack { position: relative; }
.steptrack__run, .mm__run { display: none; height: 0; }

.js .steptrack { --pin-top: 88px; --run: 0px; --run-alt: 0px; }
/* PIN: the scene fits the viewport, so it is held still while a measured run
   of scroll travels past it. No snap, no lock, no wheel interception -- the
   page scrolls the way the browser scrolls it and the scene reads the result. */
.js .steptrack[data-mode='pin'] > .steptrack__pin { position: sticky; top: var(--pin-top); z-index: 2; }
.js .steptrack[data-mode='pin'] > .steptrack__run { display: block; height: var(--run); }
/* FLOW: taller than the phone, so the model's own traversal is the track. */
.js .steptrack[data-mode='flow'] > .steptrack__run { display: block; height: var(--run); }
/* WORLD: CH04 on a phone. The shell is taller than the screen, so the world
   alone is held and the reading dock plus the run travel past it. The shell's
   clip has to be released, because an overflow-hidden ancestor would freeze
   the sticky; the body drops to block for the same reason, since a grid item
   is confined to its own grid area. */
.js .steptrack[data-mode='world'] .mm__shell { overflow: visible; display: block; }
.js .steptrack[data-mode='world'] .mm__body { display: block; }
/* the shell head is held as well, so the phone reader can still see which
   semantic step is selected while the reading dock travels past the world */
.js .steptrack[data-mode='world'] .mm__rail {
  position: sticky; top: var(--pin-top); z-index: 4; background: var(--bg);
}
.js .steptrack[data-mode='world'] .mm__world {
  position: sticky; top: calc(var(--pin-top) + var(--head-h, 0px)); z-index: 2;
}
.js .steptrack[data-mode='world'] .mm__run { display: block; height: var(--run-alt); }
/* The object's frame has to end where its content ends. The run of scroll lives
   inside the shell, because a sticky box can only travel inside its own
   containing block, so the shell's own border would otherwise draw a tall empty
   box under the held world. The border is stood down and redrawn on the three
   parts that carry content -- with box-shadow and one pseudo-element, never
   with a border, so not a pixel of geometry changes and the phone's canvas
   keeps the exact size R3 gave it. */
.js .steptrack[data-mode='world'] .mm__shell { border-color: transparent; }
.js .steptrack[data-mode='world'] .mm__rail {
  box-shadow: inset 1px 1px 0 0 var(--rule-2), inset -1px 0 0 0 var(--rule-2);
}
.js .steptrack[data-mode='world'] .mm__dock {
  box-shadow: inset 1px 0 0 0 var(--rule-2), inset -1px -1px 0 0 var(--rule-2);
}
.js .steptrack[data-mode='world'] .mm__world::after {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-left: 1px solid var(--rule-2); border-right: 1px solid var(--rule-2);
  border-bottom: 1px solid var(--rule-2);
}
/* that bottom edge IS the separator the dock used to draw, so it is drawn once:
   the dock's own rule is stood down rather than doubled */
.js .steptrack[data-mode='world'] .mm__dock { border-top-color: transparent; }

/* ------------------------------------------------ the current-step indicator
   Restrained and secondary: three hairline segments carrying the name of each
   state. It is not a wizard, not a card system and not a tutorial. The rule
   under the current segment fills on that step's own authored clock, so the
   reader can see a transition running as well as which state they are in.
   Publishing is one attribute and one number: data-sstep says which state is
   selected, --sp says how far its authored transition has run.             */
.smeter { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
          gap: clamp(0.5rem, 1.2vw, 1.15rem); align-items: end; min-width: 0; }
.smeter li { display: grid; grid-template-columns: auto minmax(0, 1fr);
             column-gap: 0.34rem; align-items: baseline; min-width: 0; --f: 1; }
.smeter em {
  font-style: normal; font-size: var(--t6-fs); letter-spacing: 0.09em; line-height: var(--t6-lh);
  color: var(--text-tertiary); font-variant-numeric: tabular-nums;
  transition: color 0.28s var(--e-out);
}
.smeter b {
  font-size: var(--t6-fs); font-weight: 600; line-height: var(--t6-lh); color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
  transition: color 0.28s var(--e-out);
}
.smeter__r {
  grid-column: 1 / -1; display: block; position: relative; overflow: hidden;
  height: 2px; margin-top: 0.22rem; background: var(--rule);
}
.smeter__r i {
  position: absolute; inset: 0; display: block; background: var(--mark);
  transform-origin: left; transform: scaleX(var(--f, 1));
}
/* with no script every state is shown resolved, matching the settled scene */
.js .smeter li { --f: 0; }
.js .smeter[data-sstep='1'] li:nth-child(1),
.js .smeter[data-sstep='2'] li:nth-child(2),
.js .smeter[data-sstep='3'] li:nth-child(3) { --f: var(--sp, 0); }
.js .smeter[data-sstep='2'] li:nth-child(1),
.js .smeter[data-sstep='3'] li:nth-child(-n+2) { --f: 1; }
/* the selected state is the only one at full contrast; a completed one keeps
   its mark, so the reader can see what has already happened */
.js .smeter[data-sstep='1'] li:nth-child(1) em,
.js .smeter[data-sstep='2'] li:nth-child(-n+2) em,
.js .smeter[data-sstep='3'] em { color: var(--mark); }
.js .smeter[data-sstep='1'] li:nth-child(1) b,
.js .smeter[data-sstep='2'] li:nth-child(2) b,
.js .smeter[data-sstep='3'] li:nth-child(3) b { color: var(--fg); }
.js .smeter[data-sstep='2'] li:nth-child(1) b,
.js .smeter[data-sstep='3'] li:nth-child(-n+2) b { color: var(--fg-2); }

/* the orientation state is not step one, and has to be distinguishable from
   it: nothing is filled, nothing is at full contrast, and one small word says
   what will move the scene on. It leaves the moment the reader advances. */
.smeter__cue { display: none; }
.js .smeter[data-sstep='0'] li:nth-child(1) .smeter__cue {
  display: inline; font-size: var(--t6-fs); line-height: var(--t6-lh); font-style: normal; text-decoration: none;
  letter-spacing: 0.09em; color: var(--mark); margin-left: 0.42rem;
  animation: smCue 2.6s var(--e-soft) infinite;
  /* the same ambient law as every other infinite animation in this build: a
     scene that has scrolled away stops animating */
  animation-play-state: var(--amb, paused);
}
@keyframes smCue { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.95; } }

/* CH04: the indicator takes the rail's middle column, where the single pass
   bar used to be, so the shell head keeps its height and the world keeps its
   share of the viewport. */
.loop__meter .smeter { align-self: center; }
@media (max-width: 560px) {
  .loop__meter .smeter { grid-area: bar; }
  .smeter b { font-size: var(--t6-fs); line-height: var(--t6-lh); }
}

/* CH06: the indicator rides in the label row, taken out of flow so that the
   row cannot grow by even a layout unit. The model's one-screen relationship
   with the five revenue families is a locked R3 result: the indicator has to
   cost it nothing. The guide reserves the width instead, so its own hairline
   stops where the indicator begins. */
.um__g { display: flex; align-items: center; gap: 0.6rem;
         position: relative; padding-right: calc(min(42%, 28rem) + 0.9rem); }
.um__gt { flex: 0 1 auto; min-width: 0; }
.um__g .smeter {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  width: min(42%, 28rem);
}
@media (max-width: 900px) {
  .um__g { flex-wrap: wrap; padding-right: 0; }
  .um__g .smeter { position: static; transform: none; width: 100%; order: 3; flex: 1 0 100%; }
}

/* reduced motion: the three states stay, and stay the reader's. Only the
   authored transitions go, so a step resolves directly to its settled state
   and nothing jumps to the end merely because motion is reduced. */
@media (prefers-reduced-motion: reduce) {
  .smeter__r i, .smeter em, .smeter b { transition: none !important; }
  .js .smeter[data-sstep='0'] li:nth-child(1) .smeter__cue { animation: none !important; }
}

/* ---------------------------------------------------------------- print */
@media print {
  .nav, .u-skip, .instruments, .acts-row, .dl, .scene { display: none !important; }
  /* a run of scroll is a screen affordance. On paper it is four blank pages,
     so the track collapses and the pinned scene simply flows in place. */
  .steptrack__run, .mm__run { display: none !important; height: 0 !important; }
  .steptrack__pin { position: static !important; }
  .um__g { padding-right: 0 !important; }
  .um__g .smeter { position: static !important; transform: none !important; }
  .smeter li { --f: 1 !important; }
  body::after { display: none; }
  .chapter, .close, .foot { background: #fff !important; color: #14171a !important; }
  .chapter[data-fold]::before, .close[data-fold]::before { display: none !important; }
  .datum[data-cut]::before { display: none !important; }
  .hero { min-height: auto; padding-top: 0; }
  .chapter { padding-block: 1.4rem; break-inside: avoid; }
  .js .stage__act { opacity: 1 !important; visibility: visible !important; position: static;
                    transform: none !important; filter: none !important; }
  .stage { background: none !important; border-color: #ccc !important; box-shadow: none !important; }
  .stage::after { display: none !important; }
  .stage__frame { display: grid; gap: 1.5rem; }
  .js .stage__act { grid-area: auto; }
  .act__truth { position: static !important; background: #f2f2f0 !important; }
  .more__in { display: grid !important; }
  [data-fig] { clip-path: none !important; }
  .q { color: #0b6b53 !important; }
  .b { color: #14171a !important; }
}


/* ============================ SCENE-INSTRUMENTATION TYPE ROLE EXCEPTIONS ==
   Text drawn INSIDE a scaled <svg> is measured in the drawing's own user
   units, so pinning it to a CSS role token would move it out of its authored
   slot as the drawing resizes. These four scenes therefore carry the role in
   user units instead, chosen so the MEASURED rendered size lands inside the
   role band it is mapped to. Every value below is verified in-browser.
     .pworld     drawn scale ~1.018   .obs__scene  drawn scale ~1.178
   role -> measured rendered px is reported in the typography audit. */
.pworld     { --svg-t3: 17px; --svg-t4: 15px; --svg-t5: 13.5px; --svg-t6: 12px; }
.obs__scene { --svg-t5: 12.5px; --svg-t5b: 12px; --svg-t6: 11px; }
.loop__svg  { --svg-t6: 12px; }

/* the same roles, re-solved in the drawing's user units where the scene is
   rendered at a different scale (narrow viewports enlarge the product world
   and the memory loop, so the role must enlarge with it) */
@media (max-width: 1023px) {
  .pworld { --svg-t3: 21px; --svg-t4: 19px; --svg-t5: 17px; --svg-t6: 15px; }
}
@media (max-width: 760px) {
  .loop__svg { --svg-t6: 23px; }
}

/* A layer comparison is a COMPARISON: on a desktop Screen the layers read
   side by side, which is both what the argument means and what lets one
   complete thought settle inside one frame. Stacked below tablet. */
@media (min-width: 1024px) {
  .layers--cols { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
                  gap: clamp(1rem, 2.2vw, 2rem); align-items: start; }
  /* where a layer holds only one or two named entries, they stack: a 180px
     flex basis would otherwise split that layer into two five-word columns,
     which is not a reading measure. Layers carrying five or six criteria
     keep the wrapping grid -- there the short measure is the point. */
  .layers--stack .sequence li { flex: 1 1 100%; }
}

/* The triage tiles shrink to 0.67 of their drawn scale on a phone, which put
   their quantity labels at ~8px rendered. Re-solved in the drawing's own
   units so the label lands back inside the T5/T6 bands. */
@media (max-width: 700px) {
  .obs__scene { --svg-t5: 19px; --svg-t5b: 18px; --svg-t6: 17px; }
}
