:root{
  --paper:#F8F4EA; --paper-2:#FCFAF4; --sunk:#EFE8D8; --peri-wash:#E8EDFB;
  --ink:#12162F; --ink-2:#3B4168; --ink-3:#6E7391;
  --blue:#2E42C0; --blue-lt:#6379E8; --peri:#BECDF4;
  --gold:#B8821B; --gold-lt:#F2CE7E;
  --violet:#6E4CC8; --red:#B93A24; --green:#2E7D5B;
  --display:'Fraunces',Georgia,'Times New Roman',serif;
  --body:'Newsreader',Georgia,serif;
  --ui:'Space Grotesk',ui-sans-serif,system-ui,sans-serif;
  --mono:'Space Mono',ui-monospace,'Courier New',monospace;
  --hair:rgba(18,22,47,.22);
  --shadow-amb:0 32px 70px -28px rgba(30,38,90,.42);
  --shadow-con:0 2px 6px rgba(30,38,90,.10);
  --e-spring:cubic-bezier(.16,1,.3,1);
}
*{box-sizing:border-box}
html{scroll-behavior:auto}
body{
  margin:0;background:var(--paper);color:var(--ink);
  font-family:var(--body);font-size:17px;line-height:1.58;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* ── paper grain + column-rule substrate ───────────────────────── */
.grain{
  position:fixed;inset:0;pointer-events:none;z-index:9999;opacity:.30;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}
.warp{
  position:fixed;inset:0;pointer-events:none;z-index:0;
  background:
    radial-gradient(90% 62% at 6% -8%, rgba(190,205,244,.62), transparent 62%),
    radial-gradient(62% 48% at 96% -4%, rgba(242,206,126,.30), transparent 64%);
}
main{position:relative;z-index:1}

/* ── shared bits ───────────────────────────────────────────────── */
/* ── THE GUTTER ────────────────────────────────────────────────────
   ONE NUMBER, AND EVERYTHING THAT TOUCHES THE PAGE EDGE READS IT. Added
   7 Sep 2026 after a community member on a phone: "it's really outlined to the
   left... I'd love a little space to left" and "more nicer if it was screen
   filling."

   THOSE TWO SOUND CONTRADICTORY AND ARE NOT. Text wants a gutter; the rails and
   the stage wash want the whole screen. Both are the same measurement, once from
   each side, which is exactly what a variable is for. Before this they were three
   unrelated literals -- 16px of wrap padding, -22px on two rails, -26px on the
   stage wash -- and nothing kept them in step.

   THE BUG HE WAS ACTUALLY SEEING, and it was not subtle once measured: `.wrap`
   set this padding and EVERY SECTION CLASS THREW IT AWAY. `.stage`,
   `.wire`, `.numbers`, `.reading`, `.directory`, `.gallery`, `.colophon` and
   `.folio-in` each declared a `padding` SHORTHAND with a horizontal `0`, at equal
   specificity, later in the file. Computed `padding-left` on `.wrap.stage` at
   390px: `0px`. Desktop never showed it because max-width leaves natural margin
   at any real screen size; on a phone the viewport IS the wrap, so every line of
   type sat flat against the glass. Those eight rules are longhand now
   (`padding-top`/`padding-bottom`), which is the actual fix: a section has no
   business having an opinion about the horizontal gutter. */
:root{ --gutter:clamp(20px,3vw,44px) }
.wrap{max-width:1400px;margin:0 auto;padding:0 var(--gutter)}
.rule{border:0;border-top:1px solid var(--hair);margin:0}
.rule-thick{border:0;border-top:3px solid var(--ink);margin:0}
.rule-dbl{border:0;border-top:1px solid var(--ink);border-bottom:1px solid var(--ink);height:4px;margin:0}
.kicker{
  font-family:var(--ui);font-size:11px;font-weight:600;letter-spacing:.19em;
  text-transform:uppercase;color:var(--blue);
}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}
/* Off-screen for eyes, present for screen readers. The direction arrow is a
   coloured triangle: green-up / red-down carries the whole meaning visually and
   none of it to anyone who cannot see the colour or the shape, so the word rides
   along beside it. clip-path rather than display:none, which would remove it from
   the accessibility tree as well. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0;
}
.marker{
  font-family:var(--ui);font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  background:var(--gold-lt);color:#4A3405;padding:2px 7px;border-radius:2px;
  box-shadow:inset 0 -1px 0 rgba(74,52,5,.25);white-space:nowrap;
}
.marker.fact{background:var(--peri);color:#1B2A66;box-shadow:inset 0 -1px 0 rgba(27,42,102,.2)}

/* ── folio ─────────────────────────────────────────────────────── */
.folio{
  border-bottom:1px solid var(--hair);background:#FBF8F1;
  position:sticky;top:0;z-index:60;
}
.folio-in{
  display:flex;gap:22px;align-items:center;justify-content:space-between;
  padding-top:7px;padding-bottom:7px;font-family:var(--mono);font-size:11px;color:var(--ink-2);
}
/* THE PAGE SCROLLED 3,269px SIDEWAYS INTO BLANK PAPER, AND THIS ONE LINE IS WHY.
   Found 8 Sep 2026 while wiring the swipe handler, present since the folio was built,
   and NOT what the 7 Sep note claimed when it recorded horizontal overflow as zero.
   The measurement that was taken reads `documentElement.scrollWidth`; the one that
   catches this reads `document.body.scrollWidth`, because body carries `overflow-x:
   hidden` and html does not, so body is the scroll container and the html-level figure
   is blind to it. Verified by scrolling the live page to x=2000 and screenshotting the
   result: the entire broadsheet leaves the screen and the reader is looking at cream.

   `overflow:hidden` CLIPS THE TICKER AND DOES NOT STOP IT. The strip is 350px wide at
   390 and holds 5,199px of board, and every pixel of that is painted nowhere and
   counted anyway: the clipped content still joins the scrollable overflow of an
   ancestor. `overflow:clip` does not fix it either, and `html{overflow-x:hidden}` only
   suppresses the symptom while leaving the page 4,829px wide.

   `contain:paint` IS THE FIX AND IT WAS MEASURED, not reasoned about. It promises the
   box paints nothing outside itself, which is the promise `overflow:hidden` was already
   making visually, and with it body.scrollWidth returns to exactly the viewport width.

   IT IS ALSO WHAT UNBLOCKS THE SWIPE. A sideways flick on a phone was reaching the
   document and dragging the page off screen before any handler saw it. */
.folio-tick{display:flex;gap:20px;overflow:hidden;contain:paint;white-space:nowrap;flex:1;min-width:0}
.folio-tick b{color:var(--ink);font-weight:700}
.folio-tick i{font-style:normal;color:var(--ink-3);opacity:.8}
/* A slot with no data says so. It never shows a number nobody read. Red team #9. */
.folio-tick b.unwired{color:var(--ink-3);font-weight:400;font-style:italic;opacity:.65}
/* .chip-mock DELETED 4 Sep 2026 along with the span it styled. It read "Layout
   mockup · not published" in red at the top of the page, which stopped being true
   the day the apex was deployed and would have been the first thing a visitor saw
   on launch day. Removing the markup and leaving the rule would have left a dead
   selector nobody could explain, so both went together.
   The old rule, if a status chip is ever wanted again:
     font-family:var(--ui);font-size:10px;letter-spacing:.13em;font-weight:700;
     text-transform:uppercase;color:var(--red);border:1px solid currentColor;
     padding:2px 7px;border-radius:2px;white-space:nowrap;
   Render it from data next time rather than typing it. */

/* ── masthead ──────────────────────────────────────────────────── */
.masthead{padding-top:30px;padding-bottom:0;position:relative}
.mast-grid{display:grid;grid-template-columns:1fr auto;gap:20px;align-items:end}
.mast-word{position:relative;line-height:.78}
.mast-the{
  font-family:var(--display);font-size:clamp(15px,1.5vw,21px);font-weight:400;
  font-variation-settings:'SOFT' 60,'WONK' 1;color:var(--gold);
  letter-spacing:.42em;text-transform:uppercase;display:block;margin-left:.35em;margin-bottom:6px;
}
.mast-title{
  font-family:var(--display);font-weight:700;
  font-variation-settings:'opsz' 144,'SOFT' 24,'WONK' 1;
  font-size:clamp(52px,12.4vw,186px);letter-spacing:-.028em;margin:0;color:var(--ink);
  text-wrap:nowrap;
}
/* BOOE IS THE TOKEN AND LIEVERS IS THE REST OF THE WORD. This used to colour only
   the two O's, which made the wordmark read as a typographic flourish rather than
   as the ticker sitting inside the name. the owner, 4 Sep: BOOE in blue, the rest in
   black. Same colour, different four letters, and now it says something. */
.mast-title .booe{color:var(--blue)}
.mast-strap{
  font-family:var(--ui);font-size:clamp(11px,1.05vw,13.5px);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-2);margin:16px 0 0;max-width:none;
}
.mast-strap em{color:var(--red);font-style:normal;font-weight:600}
.mast-book{
  width:clamp(96px,12vw,168px);transform:rotate(-9deg) translateY(10px);
  filter:drop-shadow(0 20px 26px rgba(46,66,192,.30));
  transition:transform .8s var(--e-spring);
}
.mast-book:hover{transform:rotate(-3deg) translateY(2px) scale(1.05)}
.reg{position:absolute;width:16px;height:16px;opacity:.4}
.reg::before,.reg::after{content:'';position:absolute;background:var(--ink)}
.reg::before{left:50%;top:0;width:1px;height:100%}
.reg::after{top:50%;left:0;height:1px;width:100%}
.reg.tr{top:14px;right:8px}

/* ── THE STAGE ─────────────────────────────────────────────────── */
.stage{
  margin-top:22px;padding-top:30px;padding-bottom:26px;position:relative;
  display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,.92fr);
  gap:clamp(20px,3.2vw,52px);align-items:start;
}
.stage::before{
  /* inset IS RELATIVE TO .stage's PADDING BOX, WHICH ALREADY REACHES BOTH SCREEN
     EDGES on a phone -- .wrap is full width and holds the gutter INSIDE it. So a
     negative horizontal inset here does not bleed into the gutter the way the rails'
     negative margins do, it bleeds past the glass, and it was the last thing on the
     page still doing that (20px each side, measured 7 Sep). 0 puts the wash exactly
     on the wrap edge, which is where it was always meant to stop. */
  content:'';position:absolute;inset:-10px 0;
  background:linear-gradient(172deg,rgba(226,233,250,.92),rgba(248,244,234,0) 78%);
  border-radius:8px 8px 46% 6px;z-index:-1;
}
/* align-items WAS stretch AND THAT IS THE WHOLE BUG, 5 Sep 2026. The owner: the card
   "seems to be too big for having no content in it at all."
   THE SPEECH BUBBLE WAS NEVER SIZED BY WHAT IT SAYS. stretch made the speaker column
   match the taller of the two columns, and the taller one is always ON THE WIRE -- ten
   wire cards, ~800px, no matter what is on the stage. Measured on the live mockup at
   1440px: the Saint one-liner is 310px of content inside an 804px bubble. 494px of
   empty paper, with `Next · Esc` parked at the bottom of it by .bubble-cta's
   margin-top:auto, which is what makes the emptiness look deliberate rather than
   accidental. Doge 388px, Human 388px; only the chart items came close to filling it.
   start sizes the bubble by its own content, which is the only thing that was ever
   supposed to size it. The two columns still align at the TOP, which is the alignment
   a reader actually perceives; matching bottoms across two unrelated stacks is not.
   THIS SUPERSEDES the two `.stage:has(.expand.open) { align-self:start }` patches that
   stood here until now. They existed only to let #expand escape this same ceiling, one
   ancestor at a time. With no stretch there is no ceiling and nothing to escape, so
   they are deleted rather than left as no-ops -- a rule that does nothing is the thing
   that gets copied into the next layout. openPanel()'s measured max-height stays: it
   was the more robust mechanism regardless of what the ancestor does, and it is what
   the reading room uses too. */
.speaker{position:relative;perspective:1500px;display:flex;flex-direction:column}
.speaker-head{
  width:clamp(118px,15vw,182px);height:clamp(118px,15vw,182px);display:block;
  filter:drop-shadow(0 22px 30px rgba(30,38,90,.30));
  animation:idle 3.6s ease-in-out infinite;
}
.speaker.talking .speaker-head{animation:talk .42s ease-in-out infinite}
@keyframes idle{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-7px) scale(1.012)}}
@keyframes talk{0%,100%{transform:translateY(-2px) scale(1)}50%{transform:translateY(-6px) scale(1.022)}}
/* flex:1 WAS HERE and went with the stretch above. It filled a height the parent no
   longer has, so it only ever mattered as the second half of the same ceiling. The
   align-items:stretch STAYS and does something useful now: it is what keeps a very
   short bubble as tall as the avatar beside it rather than a stub floating at the top
   of a 182px head. */
.speaker-row{display:flex;gap:22px;align-items:stretch}
.speaker-head{align-self:flex-start}
.bubble{
  position:relative;flex:1;min-width:0;background:var(--paper-2);
  border:1.5px solid var(--ink);border-radius:4px 22px 22px 22px;
  padding:20px 22px 18px;box-shadow:var(--shadow-amb),var(--shadow-con);
  transform-origin:0 0;animation:pop .62s var(--e-spring) both;
}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.94) rotate(-1.2deg)}to{opacity:1;transform:none}}
/* THE TAIL MOVED FROM THE BOTTOM-LEFT CORNER TO THE TOP-LEFT, 5 Sep 2026. An external
   tester (via the owner) flagged that the tail did not read as pointing at the avatar:
   the bubble's height is driven by its content (a one-line quip and a full article with
   an embedded chart render at wildly different heights), but the avatar is always
   top-aligned, so a bottom-anchored tail could sit hundreds of pixels away from the
   avatar it was supposed to be attached to. Anchoring it near the top keeps it close to
   the avatar regardless of how tall the body underneath grows. */
.bubble::after{
  content:'';position:absolute;left:-9px;top:22px;width:16px;height:16px;
  background:var(--paper-2);border-left:1.5px solid var(--ink);border-top:1.5px solid var(--ink);
  transform:rotate(-45deg) skew(6deg,6deg);border-radius:4px 0 0 0;
}
/* WRAPS, AND THE DATE NEVER BREAKS. Since the Share pill joined this row (2 Oct 2026),
   a phone-width Stage had no room for name, marker, date and pill on one line, and the
   date gave way word by word into four stacked lines (measured 6 Oct at 375px). Now the
   date stays whole and, with the pill, drops to a second line, still pushed right by its
   auto margin. At any width where all four fit, nothing moves. */
.bubble-who{display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin-bottom:8px}
.bubble-who .name{
  font-family:var(--ui);font-weight:700;font-size:12px;letter-spacing:.15em;text-transform:uppercase;
}
.bubble-who .time{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-left:auto;white-space:nowrap}
.bubble{display:flex;flex-direction:column}
.bubble-line{
  font-family:var(--display);font-variation-settings:'opsz' 60,'SOFT' 30,'WONK' 1;
  font-size:clamp(21px,2.7vw,36px);line-height:1.14;font-weight:600;margin:0;letter-spacing:-.015em;
  min-height:2.28em;
}
/* THE CARET LIVES ONLY WHILE THE LINE IS BEING TYPED. It used to be left in place
   when the typing finished, blinking away under a completed sentence, which reads as
   a promise that more is coming. the owner, 4 Sep: "there is a flashing cursor as though
   more text will come, I dont like it." speak() now removes it on the last character.
   Delete this rule and the two spans in apex.js to lose it during typing as well. */
.caret{display:inline-block;width:.5ch;background:var(--blue);animation:blink .9s steps(1) infinite;color:transparent}
@keyframes blink{50%{opacity:0}}
.bubble-cta{
  margin-top:auto;padding-top:16px;display:flex;gap:10px;align-items:center;
  font-family:var(--ui);font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2);
}
.btn{
  font-family:var(--ui);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  background:var(--ink);color:var(--paper-2);border:0;padding:9px 16px;border-radius:999px;cursor:pointer;
  transition:transform .3s var(--e-spring),background .3s;
}
.btn:hover{transform:translateY(-2px);background:var(--blue)}
.btn.ghost{background:transparent;color:var(--ink-2);border:1px solid var(--hair)}
.btn.ghost:hover{background:var(--sunk);color:var(--ink)}

/* expanded body */
/* MAX-HEIGHT, MEASURED BY JS, NOT grid-template-rows:0fr/1fr. See the long note beside
   openPanel()/closePanel() in apex.js: the fr-unit trick never actually opened this
   panel on this page, because .stage's align-items:stretch left no room for a 0fr row
   to grow into. THAT STRETCH IS NOW GONE (see the note on .stage), so the original
   cause is gone with it -- but max-height stays, because a real pixel target from
   scrollHeight does not depend on the grid sizing algorithm at all and is therefore
   immune to whatever a future ancestor does. It is the mechanism the reading room
   uses as well; one mechanism for both is worth more than reverting this one. */
.expand{
  margin-top:16px;border:1px solid var(--hair);border-radius:16px;background:var(--paper-2);
  overflow:hidden;box-shadow:var(--shadow-con);
  max-height:0;transition:max-height .55s var(--e-spring);
}
.expand-in{overflow:hidden}
.expand-pad{padding:20px 22px 22px}
.block{padding:13px 0}
.block+.block{border-top:1px dashed var(--hair)}
.block h4{
  font-family:var(--ui);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;
  margin:0 0 7px;color:var(--ink-3);display:flex;gap:8px;align-items:center;
}
.block p{margin:0;font-size:16.5px}
.src{
  margin-top:9px;font-family:var(--mono);font-size:11px;color:var(--ink-3);
  display:flex;gap:8px;flex-wrap:wrap;align-items:center;
}
.src a{color:var(--blue);text-decoration:none;border-bottom:1px solid var(--peri)}
/* An unlinked source is deliberately styled as NOT a link. A reproducible on-chain read is
   legitimate here (the method is the citation); a web claim with no URL is not, and should be
   visible as such at a glance rather than passing as sourced. */
.src-nolink{color:var(--ink-3);border-bottom:1px dotted var(--ink-3)}

/* ── the VOICE item: picture first, comment short, no explainer ─── */
.item-fig{margin:0 0 14px;border:1px solid var(--rule);border-radius:4px;overflow:hidden;background:var(--card)}
.item-fig img{display:block;width:100%;height:auto}
/* `.item-fig.missing` WAS HERE AND IS GONE, 15 Sep 2026, with the branch in apex.js
   that emitted it. It drew a dashed box reading "NO VISUAL YET / set img: for a
   picture, or quote: for a text post" on any voice item carrying neither, which was
   an instruction to the desk rendered for the reader, on 12 of the 41 items in CAST.
   The rule two lines down has said the right thing the whole time: a text post has no
   picture, the words are the visual. `.item-fig` itself stays; it still frames real
   pictures. */
.block.voice{padding-top:2px}
.comment{font-size:17px;line-height:1.42;color:var(--ink);margin:0 0 10px}
.credit{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin:0 0 10px;letter-spacing:.02em}
/* A text post has no picture. The words are the visual. */
.core{margin:4px 0 2px}
.core .block:last-child{margin-bottom:0}
.article{padding-top:2px}
.article h4{font-family:var(--ui);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;
            color:var(--ink-3);margin:18px 0 8px}
.article h4:first-child{margin-top:0}
.article p{margin:0 0 13px;font-size:15.5px;line-height:1.62;color:var(--ink-2)}
.article p:last-child{margin-bottom:0}
.hero{margin:14px 0 4px}
.hero .item-fig{margin:0}
.hero .item-quote{margin:0}
.od-n{font-family:var(--mono);font-size:9.5px;color:var(--ink-3);border:1px solid var(--rule);
      border-radius:2px;padding:1px 4px;margin-left:6px}
.item-quote{
  margin:0 0 14px;padding:16px 20px;background:var(--card);
  border:1px solid var(--rule);border-left:3px solid var(--gold);border-radius:4px;
  font-family:var(--serif,Georgia,serif);font-size:16.5px;line-height:1.5;color:var(--ink-2);
}
.src a:hover{border-bottom-color:var(--blue)}

/* on-deck stack (z-plane) */
.deck{position:relative;perspective:1300px;perspective-origin:60% 40%;padding-top:6px}
.deck-h{
  font-family:var(--ui);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;color:var(--ink-3);
  display:flex;align-items:center;gap:10px;margin-bottom:14px;
}
.deck-h::after{content:'';flex:1;border-top:1px solid var(--hair)}
.ondeck{
  display:flex;flex-direction:column;gap:0;transform-style:preserve-3d;
}
/* THE GROUP ROW. A HEADING NEVER TOUCHES A CARD, and that is the rule two earlier
   versions broke in opposite directions: one left 26px of air and split the column into
   blocks, the other pulled the label into the stack until the date bled across the top
   edge of a card. The row sits in its own space, the card below it starts where the row
   ends, and nothing overlaps anything.

   NO translateZ AND NO z-index HERE ANY MORE. Both existed only to keep a label painting
   on top of a card it was deliberately overlapping. With no overlap there is nothing to
   fight, which is the better fix. */
.od-grp{
  display:flex;align-items:center;gap:9px;width:100%;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);margin:16px 0 9px;padding:0;
  background:none;border:0;text-align:left;
}
.od-grp:first-child{margin-top:2px}
.od-rule{flex:1;border-top:1px solid var(--hair);display:block}
.od-c{font-variant-numeric:tabular-nums;color:var(--ink-3);opacity:.85}

/* THE FOLDED WEEKS ARE THE ONLY THING THAT KEEPS THIS COLUMN SMALL. Deliberately small,
   the owner's words, and the deck grows and never shrinks, so a design that renders
   everything has a height set by how long the page has existed.
   Folded, the height is one thin row per WEEK, which is seven times slower to grow. */
.od-toggle{cursor:pointer;transition:color .2s}
.od-toggle:hover,.od-toggle:focus-visible{color:var(--blue)}
.od-toggle:hover .od-rule,.od-toggle:focus-visible .od-rule{border-top-color:var(--peri)}
.od-chev{
  width:0;height:0;flex:none;
  border-left:4px solid currentColor;border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  transition:transform .25s var(--e-spring);transform-origin:35% 50%;
}
.od-toggle.is-open .od-chev{transform:rotate(90deg)}

/* A QUIET WEEK SAYS SO. It never opens an older week to look busy: see the note beside
   OPEN_WEEKS in apex.js, and Saint's standing item for the same rule applied to a beat. */
.od-none{
  margin:0;font-family:var(--ui);font-size:11.5px;color:var(--ink-3);
  font-style:italic;opacity:.8;padding:2px 2px 4px;
}

/* The first card under any row starts clean; only card-on-card keeps the stack's own
   overlap, which is what the overlap was ever for. */
.od-grp + .od{margin-top:0}

.od{
  --dz:0px;--dy:0px;--ry:0deg;
  position:relative;background:var(--paper-2);border:1px solid var(--hair);
  border-radius:14px 14px 14px 3px;padding:13px 15px;margin-top:-10px;cursor:pointer;text-align:left;
  font-family:inherit;color:inherit;width:100%;
  transform:translate3d(0,var(--dy),var(--dz)) rotateY(var(--ry));
  transition:transform .6s var(--e-spring),filter .45s,opacity .45s,box-shadow .45s,border-color .45s;
  box-shadow:var(--shadow-con);
  animation:odIn .4s ease-out both;
}
@keyframes odIn{from{opacity:0}to{opacity:1}}
.od:first-child{margin-top:0}
.od:hover,.od:focus-visible{outline:0;border-color:var(--blue);box-shadow:var(--shadow-amb)}
.ondeck[data-focus="1"] .od:not(.is-hot){filter:saturate(.5);opacity:.46}
/* THE CLICKED CARD LEAVES BEFORE THE LIST REBUILDS UNDER IT. External feedback,
   5 Sep 2026: without this, the slot you clicked was filled by a different card in the
   same frame -- looked exactly like your click had been ignored and swapped. Moving
   left and shrinking reads as "gone to the stage", which is what actually happened.
   apex.js adds this class and waits for it before calling speak()+buildDeck(). */
.od.promoting{
  opacity:0;transform:translate3d(-28px,-6px,30px) scale(.9);
  transition:transform .22s ease-in,opacity .22s ease-in;pointer-events:none;
}
.od-top{display:flex;align-items:center;gap:9px;margin-bottom:4px}
.od-av{width:30px;height:30px;border-radius:50%;object-fit:cover;flex:none;background:var(--peri-wash)}
.od-name{font-family:var(--ui);font-size:10.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.od-t{font-family:var(--mono);font-size:10px;color:var(--ink-3);margin-left:auto}
.od-line{margin:0;font-size:15px;line-height:1.34;color:var(--ink-2)}

/* ── THE WIRE, overlapping z-plane rail ───────────────────────── */
.wire{padding-top:30px;padding-bottom:12px}
.sec-head{display:flex;align-items:baseline;gap:16px;margin-bottom:8px;flex-wrap:wrap}
.sec-head h2{
  font-family:var(--display);font-variation-settings:'opsz' 90,'SOFT' 20,'WONK' 1;
  font-size:clamp(28px,4.2vw,54px);font-weight:700;letter-spacing:-.022em;margin:0;
}
.sec-head p{margin:0;color:var(--ink-3);font-size:15px;max-width:46ch}
/* WHY THE HOVERED CARD WAS BEING CUT OFF, and why the first attempt at fixing it
   made things worse. Both halves are worth keeping written down.

   THE ORIGINAL CUT was translateZ. The hot card was brought forward 96px, and
   translateZ moves an element along a line running from the vanishing point THROUGH
   the element -- so the further a card sits from that point, the more sideways travel
   it picks up on its way toward you. For the first and last cards in a wide rail that
   was ~31px of drift straight into the clipping edge of .rail-scroll, which is where
   the slice came from. The fix is in apex.js: the lifted card is SCALED rather than
   pushed forward. Scale grows an element about its own centre and cannot move it.

   THE FIRST ATTEMPT ALSO MOVED `perspective` HERE, onto .rail-scroll, on the theory
   that a viewport-sized box gives a saner vanishing point. That required
   transform-style:preserve-3d on .rail so the cards could still inherit it through
   the wrapper -- AND THAT IS WHAT BROKE THE FAN. Once a parent is preserve-3d the
   browser sorts its children by their real position in 3D space and STOPS HONOURING
   z-index between them. The whole rail is built on z-index: layoutRail sets one per
   card to say which overlaps which, and the hot card claims the top of the stack.
   All of that silently stopped applying. Cards cut each other in whatever order their
   dz happened to fall in, and the hovered card scaled up while still buried under its
   neighbour, which read as no hover at all.

   the owner, 4 Sep: "the wire section is still broken. no hover action. overlapping
   cards." Both symptoms, one cause.

   SO PERSPECTIVE STAYS ON .rail, where it started. It was never the problem --
   translateZ was -- and .card is a direct child of .rail, so it applies cleanly with
   no preserve-3d needed and z-index goes on working.

   .rail-scroll clips on BOTH axes -- setting overflow-x to auto computes overflow-y
   to auto as well -- so the padding here is not decoration, it is the only headroom
   the lifted card has. That part of the first attempt was right and is kept. */
/* THE EDGE FADES. Positioned at -22px because .rail-scroll bleeds 22px past the
   frame on each side, so these have to sit on the SCROLLER's real edges, not the
   frame's. They are pointer-events:none so they never swallow a hover, and they sit
   at z-index 40: above the resting cards (0-6) so the fade reads, and below the
   hovered one (100) so the card you are actually reading is never dimmed. */
.rail-frame{position:relative}
.rail-frame::before,.rail-frame::after{
  content:'';position:absolute;top:0;bottom:0;width:64px;pointer-events:none;z-index:40;
  opacity:0;transition:opacity .4s;
}
/* Same story as .stage::before, 2px of it: these fades sit on .rail-frame, which is a
   .wrap child and so starts INSIDE the gutter. One gutter out puts them on the screen
   edge; the old hardcoded 22 overshot it by however far the gutter was not 22. */
.rail-frame::before{left:calc(-1 * var(--gutter));background:linear-gradient(90deg,var(--paper) 12%,transparent)}
.rail-frame::after{right:calc(-1 * var(--gutter));background:linear-gradient(270deg,var(--paper) 12%,transparent)}
.rail-frame[data-more-left="1"]::before{opacity:1}
.rail-frame[data-more-right="1"]::after{opacity:1}
.rail-scroll{overflow-x:auto;padding:46px 0 58px;margin:0 calc(-1 * var(--gutter));scrollbar-width:none}
.rail-scroll::-webkit-scrollbar{display:none}
.rail{
  display:flex;padding:0 22px;perspective:1600px;perspective-origin:50% 42%;
  min-width:min-content;
}
.card{
  --dx:0px;--dz:0px;--ry:0deg;--tx:0deg;--ty:0deg;--mx:50%;--my:50%;--lift:0;--pop:1;
  /* NARROWED FROM clamp(210px,20vw,272px) ON 4 SEP so the whole cast fits the frame.
     At 272px the seven cards measured 1672px against a 1444px box, so the rail
     overflowed by 228px and the last card was permanently half outside it -- the owner:
     "pepe doesnt appear to be allowed in the frame lol". A fan whose last member is
     never visible is not a fan, it is a bug with a scrollbar.
     7 x 236 minus six 46px overlaps plus 44px of rail padding = 1420, inside 1444.
     Below roughly 1390px wide it still scrolls, which is what the edge fades are for. */
  /* THE CARDS ARE <button> ELEMENTS, AND THAT IS WHY THEIR TOPS DID NOT LINE UP.
     A button does not lay its content out like a div: the browser gives it an
     anonymous internal box that is VERTICALLY CENTRED. The rail is a flex row, so
     every card is stretched to the tallest one, and each card then centred whatever
     it held inside that height -- splitting its spare space evenly above and below.
     The card with the most text (Vitalik) had none to split and sat flush to the top;
     the card with the least (Shiba) had 60px of it and sat far down. Nothing in the
     CSS asked for that and nothing in the CSS could be tweaked to fix it.
     the owner, 4 Sep: "all the cards have differentm top blank sections, vitalik has none
     at all, make them equal please."
     Declaring the button a column flex container replaces that anonymous box, so the
     content starts at the top like everything else on the page. */
  display:flex;flex-direction:column;align-items:stretch;
  /* 236px -> 230px, 7 Sep 2026, AND THE 4 SEP ARITHMETIC ABOVE IS NOW WRONG ON ONE
     NUMBER. It reads "7 x 236 minus six 46px overlaps plus 44px of rail padding = 1420,
     inside 1444." The 1444 was the rail's width WHEN .wrap's horizontal padding was
     silently computing to 0 -- the bug fixed earlier today. Restoring the gutter gave
     the wrap its 43px back on each side, which took the rail to 1400, and the fan did
     not fit any more: measured, the last card's right edge sat 20px past the scroller
     and Pepe was clipped exactly as he was on 4 Sep. Same symptom, opposite cause, and
     caused by the fix for something else.
     7 x 230 - 276 + 44 = 1378, inside 1400, with room to spare rather than 24px of it.
     THE DEPENDENCY IS THE POINT: this width is a function of the rail's width, and the
     rail's width is now a function of --gutter. Change the gutter and re-measure this. */
  position:relative;flex:0 0 auto;width:clamp(200px,17vw,230px);
  /* -46px, between the -56px this started at and the -38px of the first attempt.
     the owner on the -56: "was better before the fix, but width issues forced the ask."
     The width was never really the overlap -- it was the paint order putting the cut
     on the wrong side of every card -- so most of the original tightness comes back
     and the fan keeps its depth. Dial this one number to taste. */
  margin-left:-46px;transform-style:preserve-3d;text-align:left;cursor:pointer;
  background:var(--paper-2);border:1px solid var(--hair);
  border-radius:20px 20px 26px 6px;padding:0 0 16px;overflow:hidden;
  font-family:inherit;color:inherit;
  transform:translate3d(var(--dx),calc(var(--lift)*-16px),var(--dz)) rotateY(var(--ry)) rotateX(var(--tx)) rotateZ(var(--ty)) scale(var(--pop));
  transition:transform .62s var(--e-spring),filter .5s,opacity .5s,box-shadow .5s,border-color .4s;
  box-shadow:0 1px 3px rgba(30,38,90,.09);
}
.card:first-child{margin-left:0}
.card:nth-child(even){border-radius:6px 22px 20px 22px}
.card:nth-child(3n){border-radius:24px 8px 24px 8px}
.card:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
/* THE HOVERED CARD HAS TO WIN ON MORE THAN GEOMETRY. It is 11% larger and 16px up,
   but it is also sitting against six neighbours at half opacity, so the shadow does
   as much of the work as the transform: a deep ambient one to lift it off the paper,
   a tight contact one under its edge, and a ring in the house blue. */
.card.is-hot{
  box-shadow:var(--shadow-amb),0 6px 16px -4px rgba(30,38,90,.28);
  border-color:rgba(46,66,192,.55);
  background:var(--paper-2);
}
.rail[data-focus="1"] .card:not(.is-hot){filter:saturate(.45);opacity:.5}
.card-sheen{
  position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .5s;
  background:radial-gradient(210px 170px at var(--mx) var(--my),rgba(255,255,255,.92),rgba(255,255,255,0) 68%);
}
.card.is-hot .card-sheen{opacity:1}
/* THE COLOUR ACCENT. the owner, 4 Sep: "each card has a colour accent fading top to
   bottom, can that be made a little more vibrant? maybe another 10%?"
   Two changes, because saturation alone would have been fought by the gradient:
     - the seven --tint hexes in apex.js are each pushed up in saturation and down
       slightly in lightness, which is the "10%" itself;
     - the fade is squared up to a true 180deg top-to-bottom, holds the tint at full
       strength for the first 16% instead of decaying from the very first pixel, and
       runs out at 94% rather than 78%. The old 170deg tilt also meant the accent was
       weaker on one side of every card than the other, for no reason anyone chose. */
.card-top{
  position:relative;padding:20px 18px 14px;
  background:linear-gradient(180deg,
    var(--tint,var(--peri-wash)) 0%,
    var(--tint,var(--peri-wash)) 16%,
    transparent 94%);
}
/* Source art is 500x500 whole-body. The head sits at roughly 51% x / 33% y,
   so every avatar is a circular window zoomed onto that point. Interim only:
   the real heads are a cut-out job for a human, not a framing job. */
.av{
  position:relative;border-radius:50%;overflow:hidden;display:block;flex:none;
  background:var(--peri-wash);
}
.av img{
  width:100%;height:100%;object-fit:cover;display:block;
  transform:scale(var(--zoom,1.75)) translate(var(--px,0%),var(--py,13%));
  transform-origin:50% 50%;
}
.card-av{
  width:74px;height:74px;border-radius:50%;object-fit:cover;display:block;
  background:rgba(255,255,255,.6);
  box-shadow:0 12px 22px -8px rgba(30,38,90,.42),0 0 0 1px rgba(255,255,255,.7) inset;
  transform:translateZ(34px);
}
.card-name{
  font-family:var(--display);font-variation-settings:'opsz' 40,'SOFT' 28,'WONK' 1;
  font-size:21px;font-weight:700;margin:13px 0 2px;letter-spacing:-.012em;transform:translateZ(20px);
}
/* TWO LINES ARE RESERVED WHETHER OR NOT TWO ARE USED. Four of the seven beats wrap
   ("Protocol, standards, EIPs & forks", "Wallets, custody & staying safe") and three do
   not, so .card-top came out 161px on some cards and 174px on others and every body
   paragraph below started at a different height. Reserving the second line costs 13px
   on three cards and buys one straight line across all seven. */
.card-beat{
  font-family:var(--ui);font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-3);
  line-height:1.3;min-height:2.6em;
  transform:translateZ(14px);
}
.card-body{padding:12px 18px 0}
.card-body p{margin:0 0 12px;font-size:15px;line-height:1.44;color:var(--ink-2)}
/* THE FOOT IS PUSHED TO THE BOTTOM so the tier pills and item counts sit on one line
   across the whole rail. Without this they float wherever their own card's text stopped,
   which is the same raggedness as the tops, just at the other end. */
.card-foot{
  margin-top:auto;
  padding:0 18px;display:flex;align-items:center;gap:8px;
  font-family:var(--mono);font-size:10.5px;color:var(--ink-3);
}
.tier{
  font-family:var(--ui);font-size:9.5px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  padding:2px 7px;border-radius:999px;border:1px solid currentColor;
}
.tier.f{color:var(--blue)}.tier.r{color:var(--gold)}.tier.v{color:var(--violet)}

/* ── DIRECTORY ─────────────────────────────────────────────────── */
.directory{padding-top:34px;padding-bottom:46px}
.dir-grid{
  display:grid;grid-template-columns:1.15fr .85fr 1fr;gap:0;
  border-top:3px solid var(--ink);border-bottom:1px solid var(--ink);
}
.dir-col{padding:22px 26px 26px;border-right:1px solid var(--hair)}
.dir-col:last-child{border-right:0}
.dir-col h3{
  font-family:var(--display);font-variation-settings:'opsz' 30,'WONK' 1;
  font-size:19px;margin:0 0 3px;font-weight:700;
}
.dir-col .sub{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-bottom:15px}
.lnk{
  display:flex;align-items:baseline;gap:10px;padding:8px 0;border-bottom:1px dotted var(--hair);
  text-decoration:none;color:var(--ink);position:relative;
  transition:padding-left .35s var(--e-spring),color .3s;
}
.lnk:last-child{border-bottom:0}
.lnk:hover{padding-left:11px;color:var(--blue)}
.lnk::before{
  content:'→';position:absolute;left:-4px;opacity:0;font-family:var(--mono);color:var(--blue);
  transition:opacity .3s,left .35s var(--e-spring);
}
.lnk:hover::before{opacity:1;left:-2px}
.lnk .t{font-size:15.5px;font-weight:500}
.lnk .u{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-left:auto;text-align:right}
/* A ROW THAT IS LISTED BUT HAS NOWHERE TO GO. It is a <span>, not an <a>, so it is
   not focusable, not clickable and not announced as a link -- the styling and the
   markup agree rather than one of them lying. The arrow and the hover shift are
   suppressed for the same reason: they are the page's affordance for "this goes
   somewhere", and this does not.

   9 Sep 2026: these rules nearly died with the Ledger's "soon" row, which was removed
   the same day. They are KEPT because the Gallery took the treatment over on Chris's
   call -- it becomes a collection of ecosystem pictures and contributor art, and until
   that intake exists it is listed and not linked. The selector is live, not a dead one. */
.lnk.is-soon{color:var(--ink-3);cursor:default}
.lnk.is-soon:hover{padding-left:0;color:var(--ink-3)}
.lnk.is-soon::before{content:none}
.lnk.is-soon .t{font-weight:400}
.lnk.is-soon .u{font-style:italic}

/* ── NUMBERS ───────────────────────────────────────────────────── */
.numbers{padding-top:20px;padding-bottom:54px}
/* The scroll box bleeds to the screen edge so the row you are dragging runs the
   full width of the glass, and its own padding puts the gutter back INSIDE the
   scroller -- so the first column still clears the edge while the table is free
   to be wider than the phone. min-width keeps the four columns from crushing
   into unreadable wraps instead of scrolling, which is the failure mode of just
   putting overflow on it and hoping. */
.table-scroll{
  overflow-x:auto;margin:0 calc(-1 * var(--gutter));padding:0 var(--gutter);
  scrollbar-width:thin;
}
.table-scroll:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.ntable{width:100%;min-width:560px;border-collapse:collapse;margin-top:14px}
.ntable th{
  font-family:var(--ui);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3);
  text-align:left;padding:0 12px 8px 0;border-bottom:1px solid var(--ink);font-weight:600;
}
.ntable td{padding:11px 12px 11px 0;border-bottom:1px solid var(--hair);vertical-align:baseline}
.ntable tr:hover td{background:rgba(232,237,251,.5)}
.ntable .fig{
  font-family:var(--mono);font-size:17px;font-weight:700;color:var(--ink);white-space:nowrap;
}
.ntable .lbl{font-size:15.5px}
/* THE DIRECTION MARK. Green up, red down, beside the figure -- and it is NEVER shown
   without the gap it was measured over, which lives in the "read" column next to it.
   A bare arrow implies "since yesterday" whether or not that is what happened.
   currentColor on the glyph so the arrow and the percentage can never disagree. */
.dir{
  font-family:var(--mono);font-size:11.5px;font-weight:700;letter-spacing:.02em;
  margin-left:8px;white-space:nowrap;vertical-align:1px;
}
.dir.up{color:var(--green)}
.dir.down{color:var(--red)}
.dir.flat{color:var(--ink-3)}
.dir .glyph{font-size:12.5px;margin-right:2px}
/* The interval travels with the mark, always. "+1.4%" alone is a claim about a
   period nobody named. */
.dir .dir-gap{color:var(--ink-3);font-weight:400;font-size:10px;letter-spacing:.04em}
/* The same mark in the folio ticker, where everything is one line of mono. */
.folio-tick .dir{margin-left:5px;font-size:10.5px}
.folio-tick .dir .glyph{font-size:11px}
/* WHAT THE FIGURE WAS MEASURED AGAINST, said in words, under the source line.
   "vs our own reading 24h earlier" is a different claim from "24h change" and the
   page has to make the difference visible: this number is our arithmetic on our own
   two readings, not a figure the provider publishes. */
.dir-basis{display:block;color:var(--ink-3);opacity:.85;margin-top:3px}
.ntable .when{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);white-space:nowrap}
.ntable .how{font-family:var(--mono);font-size:10.5px;color:var(--ink-3)}
/* THE GROUP HEADINGS. Sixteen figures in one undifferentiated column is a list;
   grouped, it is a board you can read a section of. Styled as a quieter echo of
   the table's own <th> so it reads as structure rather than as another figure --
   same uppercase mono-ish treatment, no bottom rule of its own, and a hairline
   above to separate it from the group before it. The first one needs no rule
   because the table head already draws one. */
.ntable .board-group th{
  padding:22px 12px 6px 0;border-bottom:0;border-top:1px solid var(--hair);
  color:var(--ink-2);letter-spacing:.18em;
}
.ntable tr.board-group:first-child th{padding-top:10px;border-top:0}
/* No hover override needed: `.ntable tr:hover td` tints TD only, and these are TH.
   Checked rather than assumed, and written down so nobody adds a dead rule here. */

/* ── GALLERY ───────────────────────────────────────────────────── */
.gallery{padding-top:10px;padding-bottom:40px}
/* THE PLACEHOLDER BANNER. The rail below it is relic art standing in for content that
   does not exist yet, and on a page whose whole claim is that it never shows you
   something it has not actually got, an unmarked placeholder is the worst possible
   thing to leave lying about. It is styled as a notice, not as a caption. */
.gal-note{
  margin-top:16px;padding:10px 14px;border-left:3px solid var(--gold);
  background:rgba(242,206,126,.16);
  font-family:var(--mono);font-size:11px;line-height:1.55;color:var(--ink-2);
}
.gal-note b{color:var(--ink);font-weight:700;letter-spacing:.04em;text-transform:uppercase;font-size:10px}
.gal-rail{display:flex;padding:34px var(--gutter) 50px;perspective:1500px;margin:0 calc(-1 * var(--gutter));overflow-x:auto;scrollbar-width:none}
.gal-rail::-webkit-scrollbar{display:none}
.gal{
  --ry:0deg;--dz:0px;
  flex:0 0 auto;width:clamp(140px,15vw,196px);margin-left:-34px;position:relative;
  background:var(--paper-2);padding:11px 11px 40px;border-radius:4px;
  border:1px solid var(--hair);cursor:pointer;
  transform:rotate(var(--rot,0deg)) translateZ(var(--dz)) rotateY(var(--ry));
  transition:transform .6s var(--e-spring),box-shadow .5s,filter .45s,opacity .45s;
  box-shadow:0 2px 5px rgba(30,38,90,.1);
}
.gal:first-child{margin-left:0}
/* THE FAN ANGLES. These were seven inline style="--rot:…" attributes. A strict
   style-src 'self' forbids inline style attributes outright, and it fails SILENTLY:
   the CSP would have flattened the fan into a straight row with nothing in the
   console to say why. Identical values, moved where the CSP can see them. */
.gal:nth-of-type(1){--rot:-2.4deg}
.gal:nth-of-type(2){--rot:1.8deg}
.gal:nth-of-type(3){--rot:-1.2deg}
.gal:nth-of-type(4){--rot:2.6deg}
.gal:nth-of-type(5){--rot:-2deg}
.gal:nth-of-type(6){--rot:1.4deg}
.gal:nth-of-type(7){--rot:-2.8deg}

/* Was style="margin-left:auto", injected into all seven wire cards via innerHTML. */
.card-count{margin-left:auto}

/* A STALE FIGURE HAS TO LOOK STALE. It is not hidden and not dropped -- the last real
   reading is still the best information there is -- but it stops being presented as
   current, and the badge says how old in plain words rather than making the reader do
   date arithmetic against a stamp with no year on it. */
/* THE POLICY BLOCK. Native <details>, so the collapse costs no JS and the summary is a
   real button to a keyboard and a screen reader without any ARIA being written. */
.policy{
  margin-top:34px;padding-top:22px;border-top:1px solid var(--hair);
}
.policy > summary{
  font-family:var(--ui);font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-2);cursor:pointer;list-style:none;display:flex;align-items:center;gap:10px;
  padding:4px 0;
}
.policy > summary::-webkit-details-marker{display:none}
.policy > summary::after{
  content:'+';font-family:var(--mono);font-size:15px;color:var(--ink-3);margin-left:auto;
  transition:transform .3s var(--e-spring);
}
.policy[open] > summary::after{content:'\2013'}
.policy > summary:hover{color:var(--ink)}
.policy > summary:focus-visible{outline:2px solid var(--blue);outline-offset:3px;border-radius:3px}
.policy-in{
  padding-top:14px;max-width:64ch;
}
.policy-in h4{
  font-family:var(--ui);font-size:10px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--ink-3);margin:18px 0 6px;
}
.policy-in h4:first-child{margin-top:0}
.policy-in p{margin:0 0 10px;font-size:14px;line-height:1.6;color:var(--ink-2)}

/* THE UNSOURCED MARKER SAYS THE WORD NOW. Dotted grey alone meant nothing to a screen
   reader and nothing to a first-time visitor who had not been told the convention.
   The style still carries the emphasis; the word carries the meaning. */
.nolink-tag{
  font-family:var(--ui);font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--paper-2);background:var(--ink-3);border-radius:2px;padding:1px 5px;margin-right:6px;
  border:0;white-space:nowrap;
}

/* The Reading Room's longer read. Same collapse mechanism as the eli5 panel beside it,
   because they are siblings in behaviour even though they differ in purpose: one is
   Pepe shortening the thing, the other is us lengthening it. */
.long-panel{
  overflow:hidden;max-height:0;transition:max-height .5s var(--e-spring);
  margin-top:10px;
}
.long-pad{
  padding:16px 18px 18px;border:1px solid var(--hair);border-radius:12px;
  background:var(--paper-2);box-shadow:var(--shadow-con);
}

/* ── THE LONGER READ TAKES THE PAGE ────────────────────────────────
   The owner, 5 Sep 2026: *"the open card when selected does not open well into the
   page, I dont like that at all. Its too long and narrow, so the card with longer read
   needs to be larger an main point of focus on the page, then close goes back to the
   actual reading room layout page."*

   HE IS DESCRIBING A MEASURE PROBLEM, AND IT WAS UNFIXABLE INSIDE THE GRID. A long
   read is 800-1200px of prose. Dropped into a 467px grid cell it becomes a column
   roughly forty characters wide and two thousand pixels tall, with the rest of the
   section sitting empty beside it -- the reader scrolls past eleven screens of nothing
   to finish one article. Widening the cell would not have fixed it either: a 1400px
   line is as unreadable as a 467px one is endless. **The article needed to stop being
   a panel inside a card and start being the page.**

   SO OPENING ONE PUTS THE SECTION IN A READING VIEW. Its row-mates and every other
   card are removed, the card takes the full twelve columns, and it splits: the
   headline, the summary, the source and the controls stay together in a fixed left
   rail, and the long read runs beside them in a column of its own at a real measure.
   Close puts the grid back exactly as it was, which is the second half of what he
   asked for and the reason the scroll position is restored rather than reset.

   WHY THE OTHERS ARE `display:none` RATHER THAN FADED. A faded card is still a
   scroll target, still tabbable and still 2,000px of page under the article you are
   reading. Removing them is what makes the article the whole section. */
.rr-grid.focused{border-bottom:1px solid var(--hair)}
.rr-grid.focused .art:not(.focused){display:none}
/* `.rr-grid` is on the front of this one selector ON PURPOSE. `.art.fitted` and the two
   narrow-breakpoint rules that set grid-column are all one class heavier than nothing
   and all appear LATER in this file, so a bare `.art.focused` would lose the column to
   whichever of them applied. The descendant lifts it above all three at every width. */
.rr-grid .art.focused{
  grid-column:1/-1;border-right:0;border-bottom:0;background:var(--paper-2);
  padding:30px 30px 34px;
  display:grid;grid-template-columns:minmax(0,330px) minmax(0,70ch);
  column-gap:clamp(28px,4vw,64px);align-items:start;justify-content:center;
}
/* Explicit placement, not auto-flow: the left rail is five separate children and the
   panel has to sit beside all of them, which auto-placement cannot express. Rows are
   named individually so the order survives a child being added or hidden. */
.art.focused > .art-kick{grid-column:1;grid-row:1}
.art.focused > h3       {grid-column:1;grid-row:2;font-size:29px;
                         font-variation-settings:'opsz' 90,'SOFT' 22,'WONK' 1}
.art.focused > p        {grid-column:1;grid-row:3;font-size:16.5px}
.art.focused > .art-src {grid-column:1;grid-row:4;margin-top:16px}
.art.focused > .art-foot{grid-column:1;grid-row:5}
/* The ELI5 panel's negative margins exist to bleed to the edges of a grid CELL. In the
   rail they would bleed into the gutter, so they are cancelled here. */
.art.focused > .eli5-panel{grid-column:1;grid-row:6;margin:14px 0 0;border-radius:8px;
                           border-top:0;border:1px solid #C4DEB6}
.art.focused > .long-panel{grid-column:2;grid-row:1/span 6;margin-top:0;overflow:visible}
.art.focused .long-pad{padding:0;border:0;background:transparent;box-shadow:none}
/* THE MEASURE IS THE COLUMN, and it is stated once, up in grid-template-columns as
   `minmax(0,70ch)`. It was briefly also a max-width down here, which is two places to
   change one number and therefore one place to get it wrong. The column caps at 70ch
   and `justify-content:center` centres the pair, so a wide screen gets margins rather
   than 950px lines with 290px of dead paper on the right -- which would have been the
   same empty-space complaint this whole change came from, one level in. */
.art.focused .article p{font-size:17px;line-height:1.66}
.art.focused .article h4{font-size:11px;margin:26px 0 10px}
/* Below the two-column breakpoint the rail and the article stack, and a 330px rail
   would be a stub. One column, article under the controls, same content order. */
/* EVERY SELECTOR IN HERE CARRIES `.rr-grid` FOR THE SAME REASON THE MAIN RULE DOES.
   A media query does not add specificity. Written as a bare `.art.focused` this block
   lost `grid-template-columns` to the `.rr-grid .art.focused` rule above it and the
   card kept its two columns below the breakpoint — everything stacked into the 330px
   rail with the 382px article column standing empty beside it. That is a 330px-wide
   long read, which is the narrow column the whole reading view exists to abolish,
   reappearing on the one screen size where it hurts most. Measured at 820px before
   the fix: article 330px wide, second column empty. */
@media (max-width:900px){
  .rr-grid .art.focused{grid-template-columns:minmax(0,1fr);padding:24px 22px 28px}
  .rr-grid .art.focused > .art-kick,.rr-grid .art.focused > h3,.rr-grid .art.focused > p,
  .rr-grid .art.focused > .art-src,.rr-grid .art.focused > .art-foot,
  .rr-grid .art.focused > .eli5-panel,.rr-grid .art.focused > .long-panel{grid-column:1;grid-row:auto}
  .rr-grid .art.focused > .long-panel{margin-top:20px}
}
.long-foot{
  margin:14px 0 0;padding-top:10px;border-top:1px dashed var(--hair);
  font-family:var(--mono);font-size:10.5px;color:var(--ink-3);letter-spacing:.02em;
}
.rr-long{margin-left:auto}

.is-stale{opacity:.66}
.stale-flag{
  font-family:var(--mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--paper-2);background:var(--ink-3);border-radius:2px;padding:1px 5px;margin-left:6px;
  white-space:nowrap;
}
/* Was style="margin-top:20px" on the masthead rule wrapper. */
.rule-gap{margin-top:20px}
.gal img{width:100%;display:block;border-radius:2px;background:var(--sunk)}
.gal figcaption{
  position:absolute;left:11px;right:11px;bottom:12px;
  font-family:var(--mono);font-size:9.5px;color:var(--ink-3);line-height:1.3;
}
.gal:hover{transform:rotate(0deg) translateZ(74px) scale(1.03);box-shadow:var(--shadow-amb);z-index:9}
.gal-rail[data-focus="1"] .gal:not(:hover){filter:saturate(.45);opacity:.58}

/* ── COLOPHON ──────────────────────────────────────────────────── */
/* THE FROG BAR'S RULES WERE HERE AND ARE GONE with the bar itself, 7 Sep 2026. It was
   a fixed bottom strip that slid up on Pepe's items and carried the page's only
   disclaimer; the POLICY block does that job properly now. Removing the markup and
   leaving the CSS is how a stylesheet fills up with rules for elements nobody can
   find. */
.colophon{padding-top:40px;padding-bottom:90px;border-top:3px solid var(--ink);margin-top:20px}
/* ONE COLUMN, 7 Sep 2026. This was 1.5fr/1fr and the right column held exactly one
   thing: the colophon note the owner has just removed as no longer true. A half-width
   empty column beside text reads as something that failed to load, which on a page
   whose whole pitch is that it can be checked is the worst possible accident. Rebuilding
   that area is a separate job the owner has flagged; until then it is not a column. */
.colo-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:44px;padding-top:22px;max-width:860px}
.colo-grid p{margin:0 0 12px;font-size:15.5px;color:var(--ink-2)}
.colo-grid .big{
  font-family:var(--display);font-variation-settings:'opsz' 40,'SOFT' 40,'WONK' 1;
  font-size:22px;line-height:1.34;color:var(--ink);
}
.colo-note{
  font-family:var(--mono);font-size:11px;color:var(--ink-3);line-height:1.6;
  border-left:2px solid var(--gold);padding-left:14px;
}
.logo-mark{width:52px;opacity:.85;margin-bottom:14px}
/* THE HOLDINGS DISCLOSURE. Given a rule and a little weight so it does not read as the
   fourth paragraph of a colophon nobody finishes. It is the one thing on this page a
   reader is entitled to know before they weigh anything else on it. */
.holds{
  margin-top:16px!important;padding:12px 0 0;border-top:1px solid var(--hair);
  font-size:14.5px!important;line-height:1.55;
}
.holds b{color:var(--ink);font-weight:700}
.contact{font-size:14.5px!important}
.contact b{font-family:var(--mono);font-size:13.5px;color:var(--blue);font-weight:700}
/* THE DONATION LINE. Deliberately quiet: it is a footnote in the colophon, not a
   call to action, and it never appears in the masthead or the Directory. The name
   is the loudest thing in it because the name is the part a reader has to check. */
.donate{
  margin-top:18px!important;padding-top:14px;border-top:1px dashed var(--hair);
  display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;
}
.donate-k{
  font-family:var(--ui);font-size:10px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3);
}
.donate-ens{
  font-family:var(--mono);font-size:15px;font-weight:700;color:var(--blue);
  text-decoration:none;border-bottom:1px solid var(--peri);letter-spacing:-.01em;
}
.donate-ens:hover{border-bottom-color:var(--blue)}
/* The enquiries address. Same slot and weight as .donate-ens so the two colophon
   lines read as a pair, but NOT a link and never styled as one: it is plain text
   on purpose (see the note in the markup), and underlining it would promise a
   click that does not exist. */
.contact-addr{
  font-family:var(--mono);font-size:14px;font-weight:700;color:var(--ink);
  letter-spacing:-.01em;
}
.donate-t{flex:1 1 100%;font-size:13.5px;line-height:1.5;color:var(--ink-3)}
.donate-t b{color:var(--ink-2);font-weight:700}



/* ── THE READING ROOM ──────────────────────────────────────────── */
.reading{padding-top:26px;padding-bottom:40px}
.rr-grid{
  display:grid;grid-template-columns:repeat(12,1fr);gap:0;
  border-top:3px solid var(--ink);margin-top:16px;
}
.art{
  grid-column:span 4;position:relative;padding:22px 24px 22px;
  display:flex;flex-direction:column;
  border-right:1px solid var(--hair);border-bottom:1px solid var(--hair);
  transition:background .4s,transform .5s var(--e-spring);
  transform-style:preserve-3d;
}
.art.lead{grid-column:span 6}
.art.wide{grid-column:span 6}
/* THE RULED GRID HAD HOLES IN IT, 5 Sep 2026, and a hole in a bordered grid does not
   read as space -- it reads as a card that failed to load. Twelve columns, leads take
   six and everything else takes four, so a row only comes out flush by luck. Measured
   on the live mockup at 1400px: row one ran lead(6)+normal(4) and left a 233px gap
   under the section's own top rule, and the seventeenth item sat alone on the last row
   with 933px of empty paper beside it and a hairline running the whole way across.
   fitRows() in apex.js walks the cards the same way the auto-placement algorithm does
   and widens the LAST card of any short row to close the gap. Last, not first, so the
   lead keeps the size it was given editorially and the filler is always the item that
   was going to sit next to the hole anyway. --span is set inline by that function.
   The two media queries below deliberately override this, because a --span measured
   against a twelve-column grid is meaningless once the grid is two columns wide or
   one. The two-column breakpoint can still orphan a card, so it closes that case in
   pure CSS instead: :last-child:nth-child(odd) is true only when the card count is
   odd, which is exactly when the last card is alone on its row. */
.art.fitted{grid-column:span var(--span,4)}
/* External feedback, 5 Sep 2026: "the controls of the neighbouring one are pushed
   down" when a panel opens. .rr-grid has no align-items, so it defaults to stretch --
   every .art in a row is forced to match the row's height, which is decided while
   everything is still closed. Opening one card's panel then has to fight that: the
   whole row (including row-mates that are not open) gets stretched again to the new,
   taller height. Only the card that is actually open needs to opt out of the shared
   height. Its still-closed row-mates keep the shared height (and the neat ruled-grid
   look) because .art-foot has no margin-top:auto -- a stretched neighbour just gains
   empty space below its own last line, its own headline and buttons never move. */
.art:has(.eli5-panel.open),.art:has(.long-panel.open){align-self:start}
/* THE 5 SEP FIX ABOVE WAS HALF A FIX AND THE HALF IT MISSED IS THE HALF THAT WAS
   REPORTED. Its own note claimed a stretched neighbour "just gains empty space below
   its own last line, its own headline and buttons never move", on the grounds that
   .art-foot has no margin-top:auto. .art-foot does not -- but .art-src, the SOURCE line
   directly above it, does, and an auto margin carries everything after it down too.
   Measured: opening the lead's longer read moved its row-mate's SOURCE line and Pepe
   button DOWN BY 818px, the exact height of the panel that opened. That is the tester's
   original report, unfixed, in the fix for it.
   ROW-MATES NOW TAKE THEIR CONTENT HEIGHT while anything in their row is open, rather
   than being stretched and then having the auto margin push their controls to the foot
   of the taller row. Neutralising the margin instead would have kept the shared height
   and left an 818px empty card sitting beside the open one, which is the same defect
   the stage was just fixed for. markRows() in apex.js decides who is a row-mate, by
   measuring offsetTop rather than by trusting the twelve-column arithmetic, so it stays
   correct at the two narrow breakpoints where the rows are shaped differently. */
/* UPDATE 9 Sep 2026: .art-src no longer carries margin-top:auto (it was changed to a
   fixed margin to close the idle-card whitespace, see the note beside .art-src). So the
   "auto margin push" described above can no longer strand a row-mate's controls at the
   foot. This rule is KEPT anyway, because it still earns its place: without it a closed
   row-mate stretches to match the now much taller opened card and gains a tall band of
   empty paper below its own foot. align-self:start lets it keep its own content height
   instead. The mechanism the rule defends against changed; the defect it prevents did not. */
.art.row-open{align-self:start}
/* THE FOLDED TAIL. See the long note beside RR_OPEN in apex.js for why the section
   needed an upper bound that the two-month window was never going to give it.

   THE CARD STAYS IN THE DOM AND IS HIDDEN, rather than being rendered on demand. Every
   card's ELI5 panel and longer-read panel are addressed by `eli5-<i>` and `long-<i>`,
   where <i> is its index in RR_SHOWN, and the click handler looks them up by that id.
   Rendering the tail later would mean either a second index space or renumbering on
   reveal, and getting that wrong binds a button to the wrong article SILENTLY -- the
   panel opens, it just explains a different piece. Hiding costs about thirty elements
   and cannot do that. */
.art.rest{display:none}

/* THE SAME ROW THE DECK USES, at the Reading Room's full width. Mono, uppercase, a rule
   that runs out to the count -- see .od-grp, which this deliberately echoes rather than
   shares, because .od-grp carries margins measured for a narrow column. */
.rr-more{
  display:flex;align-items:center;gap:10px;width:100%;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);margin:0;padding:15px 24px;
  background:none;border:0;border-bottom:1px solid var(--hair);text-align:left;
  cursor:pointer;transition:color .2s;
}
.rr-more:hover,.rr-more:focus-visible{color:var(--blue)}
.rr-more:hover .rr-more-rule,.rr-more:focus-visible .rr-more-rule{border-top-color:var(--peri)}
.rr-more-rule{flex:1;border-top:1px solid var(--hair);display:block}
.rr-more-c{font-variant-numeric:tabular-nums;color:var(--ink-3);opacity:.85}
.rr-more-chev{
  width:0;height:0;flex:none;
  border-left:4px solid currentColor;border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  transition:transform .25s var(--e-spring);transform-origin:35% 50%;
}
.rr-more.is-open .rr-more-chev{transform:rotate(90deg)}
/* The count answers "how many more", so it is meaningless once they are all showing. */
.rr-more.is-open .rr-more-c{display:none}
/* IT HAS NO PLACE IN A READING VIEW. .rr-grid.focused hides every card but one; a
   "further reading" row under it would offer to unfold a grid that is not on screen. */
.rr-grid.focused + .rr-more{display:none}

.art:hover{background:var(--paper-2);z-index:5}
.rr-grid[data-focus="1"] .art:not(:hover){opacity:.55}
.art-kick{
  font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--ink-3);
  display:flex;gap:8px;align-items:center;margin-bottom:9px;flex-wrap:wrap;
}
.art-kick .desk{color:var(--blue);font-weight:700}
/* .art-kick .who WAS HERE and is deliberately gone. See the render in apex.js:
   the pill named which anchor's voice the summary was written in, and on a card
   headed "Ethereum Foundation" with a Vitalik pill under it, that reads as an
   article BY Vitalik. Removed at source rather than restyled. */
.art h3{
  font-family:var(--display);font-variation-settings:'opsz' 48,'SOFT' 22,'WONK' 1;
  font-size:20px;line-height:1.18;font-weight:700;margin:0 0 9px;letter-spacing:-.014em;
}
/* The headline opens the source in a new tab -- external feedback, 5 Sep 2026, asked
   for exactly this instead of hunting for the small SOURCE link below. Reads as a
   headline, not as a link, until you hover it. */
.art h3 a{color:inherit;text-decoration:none}
.art h3 a:hover{text-decoration:underline;text-decoration-color:var(--ink-3)}
.art.lead h3{font-size:31px;font-variation-settings:'opsz' 90,'SOFT' 22,'WONK' 1}
.art p{margin:0 0 11px;font-size:15.5px;line-height:1.5;color:var(--ink-2)}
/* THE SUMMARY IS CLAMPED TO SIX LINES, 9 Sep 2026 (Chris: "whitespace still an issue").
   .rr-grid has no align-items so it defaults to STRETCH: every card in a row is forced to
   the height of the tallest card in that row. Summaries currently run 1 to 19 lines, so a
   three-line card beside a nineteen-line one inherits the tall height and the difference
   becomes a void. The 9 Sep .art-src change moved that void below the foot; it could not
   remove it, because the row height is not set by .art-src.

   WHY :has(.long-panel) AND NOT EVERY CARD. A clamp hides text. On a card that carries a
   longer read the full text is one click away, so nothing is lost and the clamp is purely
   presentational. On a card WITHOUT one the cut text would be reachable only by leaving for
   the SOURCE link, and a page whose whole standard is that its statements are checkable
   should not quietly truncate the only copy it shows. Those cards are also the short ones --
   the "as reported by" briefs -- so they are not what makes the big holes anyway. The tall
   cards are exactly the ones that carry a longer read, which is why this selector closes the
   voids that were actually reported while hiding nothing.

   :not(.focused) because the expanded single-card view IS the full read, and clamping it
   there would defeat the button the reader just pressed. `:has()` is already load-bearing
   in this file (see .art:has(.eli5-panel.open)), so it is not a new dependency.

   THIS IS THE HOLDING HALF OF THE FIX. The permanent cure is summaries written to an even
   length, which is a desk rule and now lives in READING-ROOM-BRIEF.md. When every summary
   is written short this clamp will stop firing on its own and can be deleted. */
.rr-grid .art:not(.focused):has(.long-panel) > p{
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:6;overflow:hidden;
}
.art.lead p{font-size:17px}
/* THE SOURCE LINE SITS UNDER THE SUMMARY, NOT AT THE FOOT OF A STRETCHED CARD.
   Changed 9 Sep 2026 (Chris: "items taking up massive amounts of white space").
   It carried margin-top:auto, which in a stretched flex card pushed SOURCE and the
   buttons all the way to the bottom -- so a two-line card sharing a row with a
   nineteen-line one opened a ~400px gap of blank paper between its summary and its
   source line, with the controls stranded at the foot. Packing to the top puts the
   controls directly under the text they belong to and lets the row's spare height
   fall BELOW the foot, where it reads as a short column ending rather than a hole.
   This RELOCATES the blank, it does not remove it: the row is still as tall as its
   tallest card, and the real cure for that is summaries of more even length (they
   currently run 1 to 19 lines) -- a desk call, not a layout one. The equal-height
   ruled grid is deliberately kept (see .art.fitted); only the auto margin is gone. */
.art-src{
  margin-top:18px;font-family:var(--mono);font-size:10.5px;color:var(--ink-3);
  display:flex;gap:7px;align-items:baseline;flex-wrap:wrap;
}
.art-src a{color:var(--blue);text-decoration:none;border-bottom:1px solid var(--peri)}
.art-src a:hover{border-bottom-color:var(--blue)}
.art-foot{display:flex;align-items:center;gap:10px;margin-top:13px}
.eli5{
  display:inline-flex;align-items:center;gap:7px;background:#E4F3DC;border:1px solid #A9CE97;
  border-radius:999px;padding:3px 11px 3px 4px;cursor:pointer;font-family:var(--ui);
  font-size:10px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:#2E5B20;
  transition:transform .35s var(--e-spring),box-shadow .35s;
}
.eli5:hover{transform:translateY(-2px) scale(1.04);box-shadow:0 8px 18px -8px rgba(46,91,32,.5)}
.eli5 .av{width:22px;height:22px}
/* max-height, measured by JS -- see openPanel()/closePanel() in apex.js. Same reason as
   .expand: .rr-grid's align-items:stretch (kept, for the ruled-grid look while every
   card is closed) left a 0fr row nothing to grow into. */
.eli5-panel{
  margin:14px -24px -22px;background:#EDF7E7;border-top:1px solid #C4DEB6;
  overflow:hidden;max-height:0;transition:max-height .5s var(--e-spring);
}
.eli5-in{overflow:hidden}
.eli5-pad{padding:17px 24px 19px;display:flex;gap:15px;align-items:flex-start}
.eli5-pad .av{width:46px;height:46px;flex:none}
.eli5-pad .txt{flex:1;min-width:0}
.eli5-pad h4{
  font-family:var(--ui);font-size:10px;letter-spacing:.16em;text-transform:uppercase;margin:0 0 7px;
  color:#2E5B20;display:flex;gap:9px;align-items:center;
}
.eli5-pad p{
  margin:0;font-family:var(--display);font-variation-settings:'opsz' 40,'SOFT' 46,'WONK' 1;
  font-size:16.5px;line-height:1.42;color:#22391A;
}
@media (max-width:1080px){
  .art,.art.lead,.art.wide,.art.fitted{grid-column:span 6}
  .rr-grid .art:last-child:nth-child(odd){grid-column:1/-1}
}
@media (max-width:680px){ .art,.art.lead,.art.wide,.art.fitted{grid-column:1/-1;border-right:0} }

/* ── responsive ────────────────────────────────────────────────── */
@media (max-width:1080px){
  .stage{grid-template-columns:1fr}
  .dir-grid{grid-template-columns:1fr 1fr}
  .dir-col:nth-child(2){border-right:0}
  .dir-col:nth-child(3){border-top:1px solid var(--hair);grid-column:1/-1}
  .colo-grid{gap:26px}
}
@media (max-width:640px){
  .mast-grid{grid-template-columns:1fr}
  .mast-book{width:96px;justify-self:end;margin-top:-30px}
  .dir-grid{grid-template-columns:1fr}
  .dir-col{border-right:0;border-bottom:1px solid var(--hair)}
  .card{margin-left:-30px;width:210px}
  .speaker-row{flex-direction:column}
  .folio-tick{gap:14px}
}

/* ── THE DECK IS CAPPED ON DESKTOP, 8 Sep 2026 ──────────────────────────
   The owner: "the wire is large now that two could fit on the left side...
   only on desktop, either we collapse the wire section to make it smaller or
   rethink its layout."

   HE IS DESCRIBING A RATIO, NOT A HEIGHT. Measured at 1440 with this week at
   fourteen items: the deck ran about 740px against a stage card of about 230px.
   The card is sized by its own content and always will be, because `align-items:
   start` on .stage was the fix for the opposite bug in September (a bubble
   stretched to the deck's height with 494px of empty paper in it). So the two
   columns cannot be made to match: one is a single item, the other is the week.

   WHAT IS CAPPED AND WHAT IS NOT. The deck keeps ONE chronological column and
   every item stays exactly where it was, because turning a dated list into two
   columns changes the reading order from "down" to "down then across" and the
   timestamps stop running in a straight line. The column simply stops growing at
   a ceiling and scrolls inside itself.

   WHY A CEILING AND NOT A COUNT. Trimming to the newest N would hide items with
   no way to reach them, and the deck's whole job is that everything on the wire
   is reachable. A scroll hides nothing; it just stops the page being paced by
   how many things happened this week.

   DESKTOP ONLY, and the breakpoint matches the one where .stage stops being two
   columns. Below 1080px the deck is full width under the card, where its height
   is the natural way to read it and a nested scroller would be actively wrong.

   `overscroll-behavior:contain` stops a scroll that reaches the end of the deck
   from carrying on and scrolling the whole page underneath it. */
@media (min-width:1081px){
  .deck{
    max-height:min(64vh, 560px);
    overflow-y:auto;
    overscroll-behavior:contain;
    scrollbar-width:thin;
    scrollbar-color:var(--hair) transparent;
    /* The fade tells the reader there is more below without printing a control.
       It is masked rather than painted so it works over any background. */
    -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 34px),transparent);
            mask-image:linear-gradient(to bottom,#000 calc(100% - 34px),transparent);
  }
  /* A deck short enough to fit needs no fade and no scrollbar gutter. :has is
     not usable here (the height is not a selectable state), so the mask is
     removed when the content cannot overflow: nothing to hint at. */
  .deck::-webkit-scrollbar{width:6px}
  .deck::-webkit-scrollbar-thumb{background:var(--hair);border-radius:3px}
  .deck::-webkit-scrollbar-track{background:transparent}
}

/* ── SHARE ─────────────────────────────────────────────────────────
   Added 2 Oct 2026, placement and contents ruled by Chris from a rendered mock: a blue
   Share pill beside the date on the Stage, a small icon on every Wire line and Reading
   Room card, and a box holding the card and two buttons with no explanatory text.
   The pill is the one solid-blue control on the Stage on purpose: it is the thing the
   page wants a reader to do next, and it should not need finding. */
.share-btn{
  display:inline-flex;align-items:center;gap:6px;margin-left:10px;flex:none;
  padding:5px 11px 5px 10px;border:0;border-radius:999px;cursor:pointer;
  background:var(--blue);color:#fff;font-family:var(--mono);font-size:10.5px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;box-shadow:0 0 0 3px rgba(46,66,192,.16);
}
.share-btn:hover,.share-btn:focus-visible{outline:0;box-shadow:0 0 0 4px rgba(46,66,192,.32)}
.share-btn svg,.share-ic svg{width:12px;height:12px;flex:none}
.share-ic{
  display:inline-grid;place-items:center;width:24px;height:24px;flex:none;padding:0;border:0;
  border-radius:50%;background:transparent;color:var(--ink-3);cursor:pointer;
}
.share-ic:hover,.share-ic:focus-visible{outline:0;background:var(--blue);color:#fff}
.od-share{margin:-4px -6px -4px 0}
.rr-share{margin:-5px -6px -5px auto}
/* THE BOX IS POSITIONED FROM ITS BUTTON IN apex.js and kept 16px inside the viewport at
   any width, so it never causes sideways scroll on a narrow screen. Above the sticky
   folio (60) and below the grain (9999), which is pointer-events:none anyway. */
.sharepop{
  position:absolute;z-index:70;width:min(400px,calc(100vw - 32px));padding:12px;
  background:#fff;border-radius:14px;
  box-shadow:0 30px 70px -20px rgba(18,22,47,.55),0 0 0 1px rgba(18,22,47,.10);
}
.sharepop::before{
  content:"";position:absolute;top:-7px;left:var(--ax,50%);width:14px;height:14px;margin-left:-7px;
  background:#fff;transform:rotate(45deg);box-shadow:-1px -1px 0 rgba(18,22,47,.10);
}
.sharepop-card{display:block;width:100%;height:auto;border-radius:8px;box-shadow:0 0 0 1px rgba(18,22,47,.12)}
.sharepop-acts{display:flex;gap:8px;margin-top:10px}
.sharepop-acts > *{
  flex:1;display:flex;align-items:center;justify-content:center;min-height:44px;padding:0 10px;
  border-radius:9px;font-family:var(--ui);font-weight:600;font-size:15px;text-decoration:none;cursor:pointer;
  overflow-wrap:anywhere;text-align:center;
}
.sharepop-x{background:var(--ink);color:#fff}
.sharepop-copy{background:#fff;color:var(--ink);border:1px solid var(--hair)}
.sharepop-x:focus-visible,.sharepop-copy:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
/* A reader arriving from a Reading Room card is shown which card it was. */
.art.shared-hit{box-shadow:0 0 0 3px var(--blue);border-radius:6px}

.still,.still *{animation:none!important;transition:none!important}
@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  .speaker-head{animation:none}
}

/* ── THE CHARACTER PANEL (8 Oct 2026) ─────────────────────────────
   Opens under the fan when a card is clicked. Carries the clicked card's tint at the
   top so it reads as that card opened up, not as a new section. */
.who-panel{
  position:relative;display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:30px;
  margin:-30px 0 22px;padding:26px 28px 24px;
  border:1px solid rgba(46,66,192,.42);border-radius:22px 22px 26px 8px;
  background:linear-gradient(180deg,var(--tint,var(--peri-wash)) 0,var(--paper-2) 150px);
  box-shadow:var(--shadow-amb);animation:whoIn .38s var(--e-spring) both;
}
.who-panel[hidden]{display:none}
@keyframes whoIn{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:none}}
.who-top{display:flex;align-items:center;gap:14px;margin-bottom:18px}
.who-av{width:64px;height:64px;border-radius:50%;overflow:hidden;flex:none;
  box-shadow:0 0 0 3px var(--paper-2),var(--shadow-con)}
.who-av img{width:100%;height:100%;object-fit:cover;display:block}
.who-name{font-family:var(--display);font-weight:800;font-size:30px;line-height:1.04;letter-spacing:-.01em}
.who-beat{font-family:var(--ui);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3);margin-top:5px}
.who-k{font-family:var(--ui);font-size:10.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--blue);display:flex;align-items:center;gap:10px;margin-bottom:9px}
.who-c{font-family:var(--mono);font-weight:400;letter-spacing:0;color:var(--ink-3);
  border:1px solid var(--hair);border-radius:999px;padding:0 7px;font-size:10px}
.who-text{margin:0 0 12px;font-size:18.5px;line-height:1.5;color:var(--ink)}
.who-k-covers{margin-top:20px}
.who-covers{margin:0;font-size:15.5px;line-height:1.45;color:var(--ink-2)}
.who-note{margin:0;font-family:var(--ui);font-size:12.5px;color:var(--ink-3);font-style:italic}
.who-list{display:flex;flex-direction:column;gap:8px;max-height:min(56vh,440px);overflow:auto;padding:2px 6px 6px 2px}
.who-row{display:grid;grid-template-columns:58px 1fr;gap:12px;align-items:baseline;text-align:left;
  background:var(--paper-2);border:1px solid var(--hair);border-radius:12px 12px 12px 3px;padding:10px 14px;
  font:inherit;color:inherit;cursor:pointer;box-shadow:var(--shadow-con);
  transition:border-color .3s,box-shadow .3s,transform .3s var(--e-spring)}
.who-row:hover,.who-row:focus-visible{outline:0;border-color:var(--blue);box-shadow:var(--shadow-amb);transform:translateY(-1px)}
.who-d{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);white-space:nowrap}
.who-l{font-size:15.5px;line-height:1.38;color:var(--ink-2)}
.who-none{margin:4px 0 0;color:var(--ink-3);font-style:italic}
.who-close{position:absolute;top:16px;right:18px;font-family:var(--ui);font-size:10.5px;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;background:var(--paper-2);border:1px solid var(--hair);
  border-radius:999px;padding:5px 12px;cursor:pointer;color:var(--ink-2)}
.who-close:hover,.who-close:focus-visible{outline:0;border-color:var(--blue);color:var(--blue)}
.card.is-picked{border-color:var(--blue)}
.card.is-picked .card-sheen{opacity:0}   /* the sheen would wash out the opened card's text */
/* Dim the others WITHOUT opacity: the fan overlaps, so a see-through card shows the
   text of the one behind it. */
.rail[data-picked="1"] .card:not(.is-hot){opacity:1;filter:saturate(.35) brightness(.97)}
@media (max-width:760px){
  .who-panel{grid-template-columns:1fr;gap:22px;margin-top:-34px;padding:22px 18px 20px}
  .who-list{max-height:none;overflow:visible}
  .who-name{font-size:25px}
  .who-close{top:12px;right:12px}
}

/* ── THE BOARD, FOLDED (8 Oct 2026) ──────────────────────────────── */
.board-more{display:inline-flex;align-items:center;gap:10px;margin-top:14px;
  font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  background:var(--paper-2);border:1px solid var(--hair);border-radius:999px;padding:8px 16px;
  cursor:pointer;color:var(--ink-2);box-shadow:var(--shadow-con);
  transition:border-color .25s,color .25s,box-shadow .25s}
.board-more[hidden]{display:none}
.board-more::after{content:'';width:7px;height:7px;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:translateY(-2px) rotate(45deg);transition:transform .25s}
.board-more[aria-expanded="true"]::after{transform:translateY(2px) rotate(-135deg)}
.board-more:hover,.board-more:focus-visible{outline:0;border-color:var(--blue);color:var(--blue);box-shadow:var(--shadow-amb)}
