
:root{
  color-scheme:dark;
  --pap:#151716; --ink:#E8E6E0; --dim:#9BA09B;
  --rule:#2B2E2C; --rule2:#232624; --tint:#1C1F1D; --surf:#1E2220;
  --acc:#6FC9AB; --live:#D9B75E; --up:#72CFA4; --down:#D97C74;
  --live-ink:#D9B75E; --kmax:46%;
  --c-crypto:#1B2B30; --c-defi:#1A2B26; --c-markets:#1F2A1D;
  --c-regulation:#2E2320; --c-tech:#21272E; --c-general:#242623;
  --g-crypto:#93D2DC; --g-defi:#96E0C1; --g-markets:#C2E3A4;
  --g-regulation:#EFB89E; --g-tech:#B8CDE2; --g-general:#CBCDC2;

  --bg:var(--pap); --panel:var(--tint); --card:var(--pap);
  --line:var(--rule); --text:var(--ink); --body:var(--ink);
  --muted:var(--dim); --accent:var(--acc); --on-accent:var(--pap);
  --header:var(--pap);
  /* TWO NAMES NINE RULES SAID AND NOTHING DEFINED (batch Q11 §4).
     `var(--chip)` with no declaration anywhere is not a fallback to the
     inherited value — it is the `background` shorthand's INITIAL value, which
     is `transparent`. So eight rules that meant "an inset panel" painted
     nothing at all, and one of them is `.fig-src`, which floats over body
     copy at `z-index:40`: a card you read the paragraph through. `--paper` is
     the same defect with one rule behind it, `.band-new`, a pill that floats
     over the live band.

     Aliases, not new colours. `--chip` is the same tint `.integ` uses through
     `--panel` — the surfaces are the same claim (something set INTO the page
     rather than on top of it) and a second value for it would be a second
     place to drift. `--paper` is the page's own ground, which is what a pill
     floating over moving rows has to be to be legible. Both resolve per
     theme on their own, because `--tint` and `--pap` are declared in both
     palettes; that is the whole reason the alias block exists.

     `check_stylesheet_tokens` is the standing version of this: every
     `var(--x)` the stylesheet uses with no fallback has to be declared in
     it. Mechanical, and it would have caught this the day it was written. */
  --chip:var(--tint); --paper:var(--pap);
  /* Tiers keep their identity and lose their crayons. FLASH is a filled
     oxblood block, DESK the same oxblood outlined, STORY plain ink, DIGEST
     dim — four marks a reader can tell apart without a fifth and sixth hue,
     and none of them amber. The desk's reserved gold was #f0b429, which is
     --live to within a few degrees, and a human column is the opposite of a
     machine reading. */
  --flash:var(--acc); --story:var(--ink); --digest:var(--dim);
  --desk:var(--acc);
  --flash-bg:var(--tint); --story-bg:var(--tint); --digest-bg:var(--tint);
  --accent-bg:var(--tint); --desk-bg:var(--tint);
  /* Elevation is a rule and a tint. There are no shadows, so the variable
     every old rule still names resolves to nothing rather than being hunted
     down one `box-shadow` at a time. */
  --shadow:transparent;

  --ease:cubic-bezier(.2,.6,.2,1);
  /* 4px baseline grid, and a shell that is FLUID WITH A CEILING (R38 §3).
     It was a flat `--wrap:1180px`, so `main` capped at 1220 and a 1920
     display was given a 1220px page with 350px of nothing down each side
     (measured). Every full-bleed surface on this page derives its gutter
     from this one variable — `calc((100% - var(--wrap)) / 2)`, in eleven
     rules — so this is the whole of the outer constraint and the whole of
     the fix.

     THE FLOOR IS TODAY'S NUMBER, on purpose. `clamp`'s lower bound is
     1180px, so at any viewport below ~1340 this resolves to exactly what it
     resolved to before and no phone, tablet or small laptop moves by a
     pixel. The fluid band runs 1340 → 1760; above that the ceiling holds.

     THE CEILING IS 1600px, AND IT IS MEASURED, not chosen. The widest
     surface on the page is the BYTE 100 treemap, and it is authored on a
     1000-unit grid (`_market_map` draws `draw(1000, 395, "wide")`) with its
     smallest tile labels set at 9 of those units. Its rendered scale is the
     carousel card's inner width over 1000, and a browser was asked where
     that reaches 1:1: **shell 2033px** — measured, not derived. Past that
     every label on the map, the 9-unit ones included, is drawn LARGER than
     the size the map was cut at. 1600 puts the map at 0.78x — up from
     0.566x today, which is the 38% the surface was starved by — and leaves
     27% of headroom before it would be upscaled. Unbounded, a 3440 display
     would draw it at 1.62x.

     `100vw` and not `100%`: a custom property's percentage resolves against
     whichever element uses it, and this one is used by eleven different
     elements. The 160px subtracted is 40px more than any scrollbar, so the
     `main` max-width below cannot exceed the viewport on the widths where
     it binds — verified, `documentElement.scrollWidth` is 1920 at 1920 for
     every width in the sweep. */
  --grid:4px; --wrap:clamp(1180px, calc(100vw - 160px), 1600px);
  /* THE SLAB DOES NOT MOVE, and it is the only thing this sizes now (batch
     R51). It was `--river` and it held the card grid too; R51 gave the grid
     the page's content edge (`--wrap`) and Kai kept the slab where it was,
     so a variable called "river" that the river no longer reads would be a
     name describing the wrong surface. Renamed rather than left: the next
     reader should not have to measure the page to find out what it sizes.

     R38 §3's measurement stands for this surface — across shells from 1180
     to 3280 the slab stays 1180.0. */
  --slab:1180px;
  /* The carousel card, as a FRACTION of the shell rather than a pin. It was
     `min(600px,86vw)`: a fixed child inside a fluid parent, which does not
     fill — it leaves a gap and drags the treemap and the movers table down
     with it, because both are cards in this carousel. 0.5085 is 600/1180,
     the ratio the pin already encoded, so the row shows the same 1.92 cards
     at every width and the 600px `max` leaves everything at or below the
     floor exactly as it was. */
  --gc-w:min(86vw, max(600px, calc(var(--wrap) * .5085)));
  /* ── ONE SPACING SCALE (batch J §2) ───────────────────────────────────
     Every vertical gap on this site comes from a step on this scale, and
     every step is a multiple of --grid. An ad-hoc margin is the thing this
     replaces: the page shipped with a 56px void between the instrument
     strip and the first card that belonged to no element and said nothing,
     because nothing anywhere said what a gap was allowed to be.

     The ceiling is deliberate. --s8 is 48px and there is no step above it,
     so a gap over 48px cannot be written as a scale step — it has to be a
     literal, which `check_vertical_rhythm` finds and the batch report has
     to justify. A rule you can satisfy by reaching for a bigger token is
     not a rule.

     Sections are separated by A RULE PLUS ONE STEP (--section-gap), never
     by empty space: a hairline says "a new thing starts here" in 1px, where a
     void says it in 56 and says it worse. */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px;
  --s5:20px; --s6:28px; --s7:36px; --s8:48px;
  --section-gap:var(--s5);
  /* the strip pulls back by a cell's own inline padding so it is the
     LABEL that lands on the page's content edge, not the cell's box */
  --edge-pull:6px;
}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){
  color-scheme:light;
  /* REDESIGN 2026-09, second pass (Kai: "smooth, soothing for the eye",
     no coral, no blue-black). WARM NEUTRALS: paper a warm off-white, ink a
     warm near-black, the one editorial colour a deep teal-green, gold for
     what is live, and green/brick bound to direction on a figure. `--surf`
     is the raised surface a card and a cell sit on, one step lighter than
     the paper in the light theme and one step lighter in the dark. Every pair below
     is measured in Chrome on the rendered page, both themes (batch report
     §5); the light amber is split as before because amber WORDS need
     4.5:1 and amber MARKS need 3:1. */
  --pap:#F5F4F0; --ink:#1C1F1D; --dim:#5D6561;
  --rule:#E1DFD8; --rule2:#EAE8E1; --tint:#ECEAE4; --surf:#FFFFFF;
  --acc:#1F6F5F; --live:#9A7524; --up:#276F51; --down:#A94A3C;
  --live-ink:#7A5B12; --kmax:72%;
  --c-crypto:#E2EDF0; --c-defi:#E0F0E9; --c-markets:#E6EFDE;
  --c-regulation:#F3E6E0; --c-tech:#E4E9EF; --c-general:#EBEAE5;
  --g-crypto:#93D2DC; --g-defi:#96E0C1; --g-markets:#C2E3A4;
  --g-regulation:#EFB89E; --g-tech:#B8CDE2; --g-general:#CBCDC2;
}}
:root[data-theme="light"]{
  color-scheme:light;
  /* REDESIGN 2026-09, second pass (Kai: "smooth, soothing for the eye",
     no coral, no blue-black). WARM NEUTRALS: paper a warm off-white, ink a
     warm near-black, the one editorial colour a deep teal-green, gold for
     what is live, and green/brick bound to direction on a figure. `--surf`
     is the raised surface a card and a cell sit on, one step lighter than
     the paper in the light theme and one step lighter in the dark. Every pair below
     is measured in Chrome on the rendered page, both themes (batch report
     §5); the light amber is split as before because amber WORDS need
     4.5:1 and amber MARKS need 3:1. */
  --pap:#F5F4F0; --ink:#1C1F1D; --dim:#5D6561;
  --rule:#E1DFD8; --rule2:#EAE8E1; --tint:#ECEAE4; --surf:#FFFFFF;
  --acc:#1F6F5F; --live:#9A7524; --up:#276F51; --down:#A94A3C;
  --live-ink:#7A5B12; --kmax:72%;
  --c-crypto:#E2EDF0; --c-defi:#E0F0E9; --c-markets:#E6EFDE;
  --c-regulation:#F3E6E0; --c-tech:#E4E9EF; --c-general:#EBEAE5;
  --g-crypto:#93D2DC; --g-defi:#96E0C1; --g-markets:#C2E3A4;
  --g-regulation:#EFB89E; --g-tech:#B8CDE2; --g-general:#CBCDC2;
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --pap:#151716; --ink:#E8E6E0; --dim:#9BA09B;
  --rule:#2B2E2C; --rule2:#232624; --tint:#1C1F1D; --surf:#1E2220;
  --acc:#6FC9AB; --live:#D9B75E; --up:#72CFA4; --down:#D97C74;
  --live-ink:#D9B75E; --kmax:46%;
  --c-crypto:#1B2B30; --c-defi:#1A2B26; --c-markets:#1F2A1D;
  --c-regulation:#2E2320; --c-tech:#21272E; --c-general:#242623;
  --g-crypto:#93D2DC; --g-defi:#96E0C1; --g-markets:#C2E3A4;
  --g-regulation:#EFB89E; --g-tech:#B8CDE2; --g-general:#CBCDC2;
}

*{box-sizing:border-box;margin:0}

/* ── Type: TWO roles, two families, nothing else (batch J2 §1) ────────────
   Newsreader carries every word on the site — nav, labels, kickers,
   headlines, standfirsts, body, footer, buttons — and IBM Plex Mono every
   figure (batch K2 §0, which puts back the face J2 §1 swapped out and
   changes neither the count nor the roles). There is no third role: IBM
   Plex Sans is gone with no successor,
   because a newspaper runs ONE text family across the whole paper and a
   second face only for data. The faces are VENDORED (assets/fonts/,
   provenance in PROVENANCE.json) and never hotlinked: a wire that renders its
   headlines only when a third party answers has a dependency it did not
   declare.

   Per EDITION, because coverage is not a matter of taste. Newsreader has no
   Arabic, Bengali, Devanagari or CJK, so the four non-Latin editions do not
   download 189KB of Latin outlines to render a wordmark — they get their
   platform script face for every word, and the mono ALONE, because every
   figure in every edition of this site is a Western numeral. Measured, not
   assumed: 271 ASCII digits and 0 local digits in the intel strip of each of
   ar, bn, hi and zh.

   THERE IS NO ITALIC FILE ANY MORE (batch J3 §3). J2 shipped Newsreader's own
   italic so the label register could be a real one rather than a synthesised
   oblique; J3 retired that register and the second file went with it. No rule
   on this site asks for `font-style:italic` on furniture now, in any edition,
   which is why the four non-Latin editions no longer need a rule putting them
   back to roman — they were the only reader of it. */

@font-face{font-family:'BN Mono';font-style:normal;font-weight:400 700;
  font-display:swap;src:url(/f/jetbrains-mono-latin.woff2) format('woff2')}

body{background:var(--pap);color:var(--ink);font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
     font-size:17px;line-height:1.5;
     -webkit-text-size-adjust:100%;text-size-adjust:100%;
     /* Every figure on this site is the product, so numerals are
        load-bearing: lining globally, and TABULAR on every figure register
        (`.num` and friends below declare it), so a column of them lines up
        and a countdown does not resize its own tile once a second.
        NOT tabular on the prose face (redesign 2026-09): Schibsted Grotesk
        implements `tnum` by widening its punctuation too, so a body-wide
        tabular setting printed "Shares , Calling" in every headline. */
     font-variant-numeric:lining-nums}
.num,.wnum,.fig,td,th,time,.mono,code,kbd,samp{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
     font-variant-numeric:tabular-nums lining-nums}
nav,button,input,select,textarea,.ui,.lab,.meta,.navlink,.chip{
     font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
h1,h2,h3,h4,.hed{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-weight:600;
     line-height:1.15;letter-spacing:-.018em;
     /* §9. A headline wrapped greedily puts as much as it can on line one and
        leaves a word alone on line two — the widow a sub-editor re-writes the
        headline to avoid. `balance` asks the browser to even the lines
        instead, which is the one typographic job that used to cost a person
        an afternoon. Capped at six lines by the spec, which every headline on
        this site is well inside; where a browser does not support it the
        value is ignored and the wrap is what it always was. */
     text-wrap:balance}
/* §9. A FIGURE MAY NOT BREAK ACROSS A LINE. "18.9759 BTC" wrapped after the
   number leaves a bare unit on the next line, and "$1.5M" split at the
   thousands separator is a different number for the length of a blink. Every
   surface that carries a reading gets `nowrap`; the containers around them
   already wrap, so this costs a break opportunity that was never wanted
   rather than an overflow. `.wnum` is deliberately NOT here — a strip figure
   is one line at a fixed 19px with its unit set beside it, and `.wsub` under
   it already ellipsizes, so a `nowrap` on the pair would clip a reading
   rather than protect one. */
.num,.v,.c,time,.stalemark,.tk b{white-space:nowrap}
/* ── TWO FURNITURE REGISTERS, AND THEY MAY NOT LOOK ALIKE (batch J2 §3) ──
   A newspaper distinguishes furniture from furniture by CASE and POSTURE,
   not by reaching for a third family. Both of these are Newsreader now — the
   desk rejected the mono kicker — so what separates them is everything else:

     KICKER   roman, UPPERCASE, letterspaced .14em, small, in --acc.
              `ON-CHAIN · WHALE MOVE · BTC`. It names the story's beat and it
              is editorial, so it takes the editorial colour.
     LABEL    roman, lowercase, untracked, in --dim. `sentiment`,
              `dominance`, `hack tracker · 2026`. It names a COLUMN or a
              CELL — it is the furniture around a reading, not a claim about
              it — so it recedes and it never takes the accent.

   THE LABEL WAS ITALIC AND IS NOT ANY MORE (batch J3 §3). J2 vendored a
   second face to make that italic real; J3 spends the 64,648 bytes back. The
   two registers were never separated by posture in the first place — they
   differ in CASE, in TRACKING, in SIZE and in COLOUR, four ways at once, and
   any one of them is enough to tell a kicker from a label. Dropping the fifth
   costs nothing a reader can lose, and it means no rule on this site asks for
   a style the shipped file cannot draw. That equivalence is what
   `check_furniture_registers` asserts, so it cannot quietly stop being true.

   A BADGE IS NEITHER. FLASH / STORY / DIGEST / DESK is a tier mark with its
   own colour system (see `.badge.flash` below), so it keeps the uppercase and
   the tracking and takes none of the kicker's accent. */
/* THE KICKER IS `.kick`, IT IS MONO, AND IT IS 9.5 (batch K2 §0 and §3).
   Three changes and all three are the drawing's. The NAME is the mock's
   (`.kicker` and `.eyebrow` were two names for one register and neither was
   emitted by anything — the card's kicker is drawn into the SVG, which is why
   a grep of the built tree finds no element carrying either). The FACE is
   Plex Mono, which is §0's ruling for every kicker, label and figure on the
   site. The SIZE is 9.5 with .13em of tracking, down from 12.5/.14em, because
   a kicker is the smallest thing on a card and K's ladder had it larger than
   the meta row under it. */
.kick,.eyebrow{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-transform:uppercase;
     letter-spacing:.13em;font-weight:400;font-size:9.5px;color:var(--acc)}
.lab,.colhead,table.data th,table.whales th{
     font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-style:normal;text-transform:lowercase;
     letter-spacing:0;font-weight:400;color:var(--dim)}
.badge{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;
     letter-spacing:.13em;font-weight:400}

/* ── Geometry ─────────────────────────────────────────────────────────────
   Radius 0. No shadows. No gradients. Newspapers have used hairline rules for
   150 years because a rule carries structure without pretending to be an
   object, and a soft-shadowed rounded card on a light grey field is the house
   style of every AI-built dashboard since 2023. Circles survive only where
   the shape IS the meaning — a status dot. */
*,*::before,*::after{border-radius:0;box-shadow:none}
.dot,.status-dot,[data-shape="dot"],.themebtn .i-sun,.themebtn .i-moon,
.themebtn .i-auto{border-radius:50%}
a{color:var(--acc)}
a:focus-visible,button:focus-visible,input:focus-visible,
select:focus-visible,[tabindex]:focus-visible{
      outline:2px solid var(--acc);outline-offset:2px}

/* ── Header ───────────────────────────────────────────────────────────────
   §3a. A masthead, not an app bar: opaque paper, one hairline under it, and
   no backdrop blur — a blur is a soft effect standing in for a rule, and this
   design has rules. */
/* ── THE HEADER OCCUPIES ONE ROW AT EVERY WIDTH (batch I §11) ─────────────
   It used to say `flex-wrap:wrap`, and that one declaration is why every gate
   stayed green over a masthead that was visibly breaking into four lines:
   `scrollWidth <= clientWidth` is SATISFIED by wrapping. Flex-wrap is
   precisely the browser's mechanism for not overflowing, so 1,134
   measurements passed while `Archive`, the language control and the theme
   toggle sat on a second row at 1440 — and, measured here before the fix,
   FOUR rows at 768–1920, FIVE at 740 (179px of sticky header), and two at
   320 where the menu button dropped below the wordmark.

   `nowrap` makes the row count STRUCTURAL rather than emergent: a header
   that cannot wrap cannot gain a row because content grew. What content
   growth does instead is overflow — which the sweep has always caught, and
   which is now the only failure mode left. The two checks together are
   complete; either alone is the house defect, measuring the property next to
   the one it is named for.

   THE COLLAPSE ORDER IS DECIDED HERE AND WRITTEN DOWN, not left to the
   browser's discretion:

     >= 1200px   wordmark | categories | tier tabs · search · Archive · lang · theme
     721-1199px  wordmark | tier tabs · search · Archive · lang · theme · MENU
                 ^ categories collapse into the overflow menu, which already
                   contains every one of them
     <= 720px    wordmark | search(icon) · theme · MENU
                 ^ Archive and the language control join them in the menu.
                   The search is a magnifier at EVERY width as of
                   2026-08-22, and the tier tabs are retired — see
                   `tools/ui_inventory.py` → RETIRED.

   Only two things may shrink — the categories and the tier tabs — and both
   shrink into their OWN scroll region, never into the document's. */
header{position:sticky;top:0;z-index:5;background:var(--pap);
       border-bottom:1px solid var(--rule);
       padding:8px 20px;display:flex;align-items:center;
       /* `nowrap` as of 2026-08-22, and it is the whole one-row claim.
          Batch I §11 made the second row DESIGNED — an ordered full-width
          break — which answered the accidental half of the complaint and
          left `wrap` in place, so a row was still something the browser
          could add without anyone being told. With the tier tabs retired and
          the search reduced to its magnifier the content fits (959 of
          1,180), so `nowrap` costs nothing today and changes what a future
          overgrowth DOES: it overflows, which the sweep measures, instead of
          quietly becoming a second line, which is the failure this rule
          exists to make impossible. Verified across 2,106 measurements —
          81 widths x 13 pages x 2 modes — one row and no overflow. */
       column-gap:12px;row-gap:0;flex-wrap:nowrap;
       /* G1 §4.1: ONE MEASURE. The masthead and the widget strip are
          full-bleed and the card grid was inset a further ~130px at 1440, so
          the page had two left edges. The RULES still bleed — that is the
          instrument half of the design — and the CONTENT now starts on the
          same line the cards do. Clamped to the 20px gutter (and the notch)
          below the measure, so nothing changes on a phone. */
       padding-inline-start:max(20px,env(safe-area-inset-left),
                                calc((100% - var(--wrap)) / 2));
       padding-inline-end:max(20px,env(safe-area-inset-right),
                              calc((100% - var(--wrap)) / 2))}
/* The wordmark is `bytenews.io`, set in the headline face, IN THE INK — and
   as of batch J2 §5 it wears the mark, which is the one thing the previous
   note said it would not. Two changes and both are the same argument:

     THE .io IS NOT AN ACCENT. `--acc` is EDITORIAL — links, kickers, section
     rules, corrections — and lending it to the second half of our own name
     spends the one colour that means "read this" on the thing a reader is
     never asked to read. It is ink now, and what separates the two halves is
     the weight step the wordmark already has.

     AND IT IS A LOCKUP NOW. "The thing a reader types is the thing the
     masthead says" is still true — the words did not change — but a masthead
     with nothing but its own URL on it has no mark at 16px, in a tab, on a
     home screen or in a share sheet, which is four of the five places a
     brand is actually seen. */
header h1{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:24px;font-weight:800;
       letter-spacing:-.03em;line-height:1;font-variant-numeric:normal}
header h1 a{color:inherit;text-decoration:none}
header h1 b{font-weight:inherit}
header h1 span{color:var(--ink)}
/* Category nav. On a phone it takes a row of its own and scrolls sideways
   rather than wrapping the header to four lines — five links plus the tier
   chips plus search do not fit across 375px on any arrangement. */
/* ── TWO DELIBERATE ROWS, AND THE BREAK IS AN ELEMENT ─────────────────────
   §11 permits a designed second row and forbids an accidental one. This is
   the designed one, and the mechanism is what makes it designed: an empty
   flex item of full width, ordered between the two groups, so the line break
   is a thing in the layout rather than a consequence of running out of room.
   The header cannot gain a THIRD row unless a group overflows, and neither
   group can — every item in row 1 is `flex:0 0 auto` and sums to 322px, and
   the two items in row 2 that could be wide are scroll regions.

   WHY TWO AND NOT ONE, measured rather than argued. The masthead keeps the
   content edge G1 §4.1 gave it, which is `(100% - 1180px) / 2` of padding —
   260px a side at 1440 and 740px at 1920. One row of English needs
   137 (wordmark) + 509 (categories) + 319 (tier tabs) + 150 (search) + 48
   (Archive) + 93 (language) + 44 (theme) + 84 (gaps) = 1,384px of content,
   so it needs 1,644px of viewport at 1440 and 2,124px at 1920. IT HAS NEVER
   FITTED ON ONE ROW AT ANY WIDTH, in any edition, and `flex-wrap:wrap` is
   why nobody was told: wrapping is precisely the browser's mechanism for not
   overflowing, so 1,134 sweep measurements passed over a header that was
   four rows tall.

     row 1   wordmark ............... Archive · language · theme · [menu]
     row 2   categories ............. tier tabs · search

   ONE ROW AS OF 2026-08-22, and the arithmetic above is why it is possible
   now and was not before. The desk asked twice for a single row; what stood
   in the way was 319px of tier tabs and 150px of search field. The tabs are
   RETIRED (see `tools/ui_inventory.py` → RETIRED) and the field is a 44px
   magnifier until it is asked for, so the same sum reads:

     137 (wordmark) + 509 (categories) + 44 (search icon) + 48 (Archive)
     + 93 (language) + 44 (theme) + 84 (gaps) = 959px

   against the 1,180px measure — 221px of slack at 1440, and it is slack
   rather than luck: the editions whose category labels run longest are the
   ones this has to survive, which is why the sweep asserts ONE row on all
   eight rather than on English.

   Opening the search costs 150px inline above 720px (1,109 of 1,180, still
   one row) and nothing at all below it, where the field is an absolutely
   positioned overlay and the row never changes at all.

   THE COLLAPSE ORDER, decided here rather than by the browser:
     >= 1200px  categories visible
     721-1199   categories collapse into the overflow menu (which has carried
                every one of them since the mobile nav shipped)
     <= 720px   Archive and the language control join the menu, and the
                search overlays rather than expanding in flow

   The BREAK ELEMENT IS GONE. It was `header::before` with `flex-basis:100%`
   — an ordered, full-width item that made the second row a thing in the
   layout rather than a consequence of running out of room. With one row
   there is no line to declare, and `flex-wrap:nowrap` below is what keeps it
   that way: the header cannot grow a row, it can only overflow, and the
   sweep measures rows rather than overflow precisely so that a wrap is a
   failure rather than a silent second line. */
header h1{order:1}
header>.cats{order:2}
header>.search{order:3}
header>a.navlink{order:4}
header>.langpick{order:5}
header>.themebtn{order:6}
header>.menubtn{order:7}
/* The utility end. `margin-inline-start:auto` on the FIRST of them pushes
   the whole group to the row's logical end without splitting the free space
   between each pair — and it is on `.search` rather than on `.navlink`
   because the search is the first of the group at every width, while
   Archive and the language control leave for the menu below 720px. */
header>.search{margin-inline-start:auto}
/* Shrinkable, and it shrinks into ITS OWN region. `min-width:0` is what
   allows a flex item to be narrower than its content; without it the row
   cannot be `nowrap` without widening the document. */
/* `flex-basis:0`, and this is the load-bearing part of the two-row header.
   A WRAPPING flex container breaks lines using items' HYPOTHETICAL main
   sizes — their base sizes, before any shrinking — so an item that could
   shrink to fit is put on a new line instead. The full sweep caught it in
   four measurements of 2,106: Spanish and Portuguese category labels are
   longer than English, and at exactly 1200px — the width where the
   categories come back — row 2's base sizes summed past the line and the
   search field was pushed to a THIRD row. English never showed it, which is
   why §11 says assert all eight.
   With a basis of zero this contributes nothing to that sum, takes whatever
   the line has left, and scrolls inside itself when that is not enough. */
.cats{display:flex;align-items:center;gap:0;min-width:0;flex:1 1 0;
      overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none}
.cats::-webkit-scrollbar{display:none}
.cats a{white-space:nowrap}
/* Two groups, one hairline between them — redesign 2026-09. */
.cats .grp{display:flex;align-items:center;gap:10px;flex:0 0 auto}
.cats .grp+.grp{margin-inline-start:10px;padding-inline-start:10px;
      border-inline-start:1px solid var(--rule)}
.search .mag svg,.langpick .i-globe{width:15px;height:15px;display:block}
.langpick .i-globe{margin-inline-end:2px}
.chip-tl .i-tl{width:13px;height:13px;vertical-align:-2px;margin-inline-end:2px}
/* Everything that is NOT one of the two shrinkable regions keeps its size:
   a wordmark that shrinks is a wordmark that has been cut. */
header h1,header>.search,header>a.navlink,header>.langpick,
header>.themebtn,header>.menubtn{flex:0 0 auto}
/* Plain mono labels, not pill buttons. 44x44 is the touch floor (§5c) and it
   is reached with padding rather than with a box, so the control is legible
   as a label and still large enough to hit. */
.themebtn{background:transparent;border:0;color:var(--dim);
      min-width:44px;min-height:44px;padding:0 4px;
      cursor:pointer;display:inline-flex;align-items:center;
      justify-content:center;flex:0 0 auto;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
.themebtn:hover{color:var(--acc)}
.themebtn svg{width:16px;height:16px;display:block}
/* Which glyph shows is the CURRENT state, and batch R64 §B1 turned it round
   deliberately. It used to show the mode you would switch TO, which a binary
   toggle can do because "the other one" is a single thing. With three states
   it is not, and the reader's real question changed with it: they have to be
   able to tell Auto from a pinned palette that happens to agree with the
   system, and those two look identical everywhere else on the page.

   KEYED ON THE ATTRIBUTE ALONE — no `prefers-color-scheme` branch. Auto IS
   the absence of the attribute, in both system settings, so the media query
   the old rule needed to work out "the other one" has nothing left to
   decide. */
.themebtn .i-sun,.themebtn .i-moon,.themebtn .i-auto{display:none}
:root:not([data-theme]) .themebtn .i-auto{display:block}
:root[data-theme="light"] .themebtn .i-sun{display:block}
:root[data-theme="dark"] .themebtn .i-moon{display:block}

/* ── Tier filters: a rule-underlined tab row ──────────────────────────────
   §3a. They were rounded pills, which is tell number three. A tab is a label
   with a rule under it; the active one carries a 2px oxblood rule and the
   ink weight, so the state is readable without a fill. The row keeps the
   `.chip` class the client JS and the §0b inventory both name — a rename
   that erases the record is not a rename. */
/* 16.5, the ladder's nav rung (batch K §1). It was 12.5, and the header's
   own "one row at 1280" rule is what pays for the difference — see the gap
   and padding below, both re-measured rather than the header growing. */
.navlink{color:var(--dim);font-size:13.5px;text-decoration:none;
         letter-spacing:-.005em;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-weight:500;
         /* a nav link is a touch target too */
         display:inline-flex;align-items:center;min-height:44px}
.navlink:hover{color:var(--acc)}
.search{display:flex;align-items:center;gap:6px}
/* The in-field close button is a MOBILE control — it belongs to the
   search overlay, which only exists below 720px. Hidden here rather
   than only shown there: the first render of this batch put a stray ✕
   in the desktop header and wrapped it onto a second line. */
.search .clear{display:none}
.search input{background:transparent;border:0;
      border-bottom:1px solid var(--rule);
      color:var(--ink);padding:6px 2px;font-size:12.5px;
      width:150px;outline:none;min-height:44px;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
      /* NO width transition. Animating the width of an input inside a sticky
         header is what made the mobile search nudge on every keystroke — the
         reflow lands a frame late and the caret moves with it. The overlay
         below is revealed with a transform instead. */
      transition:border-color .18s var(--ease)}
.search input::placeholder{color:var(--dim)}
.search input:focus{border-bottom-color:var(--acc)}
/* THE SEARCH IS A MAGNIFIER AT EVERY WIDTH as of 2026-08-22, which is what
   pays for the one-row header: a 150px field that is empty on arrival buys
   the reader nothing 150px of masthead cannot. It was already the phone's
   behaviour and the JS was already width-agnostic (`toggleSearch` toggles a
   class and focuses); only these two declarations were inside the ≤720px
   block. Above 720 the field then expands IN FLOW beside the icon — the
   absolutely-positioned overlay stays the phone's, where the row has no
   room to grow into. */
.search .mag{display:inline-flex;background:transparent;
      border:1px solid var(--line);
      color:var(--muted);width:30px;height:30px;
      font-size:13px;cursor:pointer;align-items:center;justify-content:center}
.search:not(.open) input{display:none}
/* §2c: the measure caps at 1180px and the field around it stays composed
   above that — the page is centred paper on a paper ground, not a narrow
   site stranded in the middle of an ultrawide screen. */
/* G1 §4.1: the page has ONE content edge, at `max(20px,(100% - wrap)/2)`.
   main's box is the measure PLUS its own two 20px gutters, so its content
   lands on that line rather than 20px inside it — which is what made the card
   grid start 20px to the right of the masthead and the strip. */
/* One scale step top and bottom (batch J §2). It was 24px, which is not a
   step on the 4px scale this design now states, and it stacked with the
   band's own 24px margin to make the 48px void §0 named — a gap that
   belonged to no element and said nothing. The band's margin is gone; this
   padding is the whole of the space between the tape and the first card, and
   the tape's own bottom rule is what says a section ended. */
main{max-width:calc(var(--wrap) + 40px);margin:0 auto;
     padding:var(--section-gap) max(20px,env(safe-area-inset-left))
             var(--s6) max(20px,env(safe-area-inset-right))}
/* Three across, not "as many as fit". The auto-fill track list this replaced
   put four 290px cards on a 1280px container, and at that width a card is a
   thumbnail with a paragraph under it: 16.5px headlines, three-line blurbs,
   nothing with room to be looked at. The counts are explicit rather than
   derived from a min-width because "3 / 2 / 1" is the design, and auto-fill
   reaches 1 column at 820px — a tablet would skip the two-up step entirely.
   `colCount()` reads whichever of these CSS resolved, so the signup band
   still lands at the end of the first row. */
/* `minmax(0,1fr)` and not `1fr`. G1: `1fr` is `minmax(auto,1fr)`, so a track
   cannot shrink below its item's MIN-CONTENT — and on the Spanish front page
   the card's meta row ("resumen · hace 23 h · 7 fuentes" plus the share
   button) has a min-content of 305px, which grew both tracks to 338 inside a
   560px grid and scrolled the whole page sideways at every width from 580 to
   620. English never showed it, which is why the sweep now walks all eight
   editions. */
/* THREE EQUAL COLUMNS (batch K §6). It was six tracks with a lead spanning
   all six and two "second tier" cards spanning three each — and that grid is
   what forced the top of the page to inflate: a card 1,220px wide has to fill
   its box, so its headline went to 44px and its figure to 126, and the rest
   of the page had to whisper underneath. A card in a 1/3 column at 25px needs
   no display type to fill anything. The lead is still the lead — it is FIRST,
   and it is the only card that carries a figure with a display figure in it
   — but it is the same size box as its neighbours, which is what J §1's own
   note said the hierarchy was allowed to be: order and scale, never layout. */
.grid{display:grid;--cols:3;grid-template-columns:repeat(3,minmax(0,1fr));
      gap:0;border-top:1px solid var(--rule);
      /* G1 §4.1: pulled back by a card's own gutter, so it is the card's
         TEXT that lands on the page's content edge rather than its box —
         the same trick the widget strip uses on its cells. Without this the
         card text sat 20px inside the masthead at desktop and flush with it
         on a phone, which is the inconsistency the desk asked to be rid of.
         THE PULLBACK IS GONE (batch K2 §3): a card is a BOX now, so what
         belongs on the page's content edge is the box's own rule, not the
         text inside it, and -20px would hang the leftmost card's border off
         the edge of the page. */
      /* ONE CONTENT EDGE (batch R51, Kai: "make the cards river same width
         as the live band/todays wire, theres like 3 different width size
         right now in one page").

         R38 §3 pinned the river at 1180 while `--wrap` grew, so at 1440 the
         band sat at 1280 and the cards at 1180 — two content edges on one
         page, measured 50px apart. **That pin is reversed here, by the
         desk.** R38's reasoning was about what to spend width ON — "data
         surfaces take the room", "widening the story column is not the win"
         — not that a wider column reads badly, and the prose is capped by
         its own measure anyway, so the box growing does not lengthen a
         line.

         The LEAD SLAB keeps 1180 and is the exception Kai asked for by
         name, which is why `--slab` is now the variable's name: it
         describes the one surface that still uses it. */
      max-width:var(--wrap);margin-inline:auto}
@media (max-width:1100px){.grid{--cols:2;
      grid-template-columns:repeat(2,minmax(0,1fr))}}
/* §5b: single column below 560px, in source order. `minmax(0,1fr)` for the
   same reason the wider rules use it — a bare `1fr` is `minmax(auto,1fr)` and
   the Spanish card's meta row has a 306px min-content, which made the single
   track 298.7px wide inside a 280px grid and scrolled the page sideways at
   320px. */
@media (max-width:560px){.grid{--cols:1;
      grid-template-columns:minmax(0,1fr)}}

/* ── A card is a figure, not an object ────────────────────────────────────
   §3c. It was a tinted, rounded, drop-shadowed box that lifted 3px on hover.
   It is now a column of the page divided by hairlines: the rule IS the
   container. Nothing lifts, because nothing is floating. */
/* DIRECTION A · LIGHT (batch K2 §3). K made the card a column of the page
   divided by hairlines — two rules, no box. The drawing makes it a BOX again,
   and not the tinted rounded shadowed one G threw out: a hairline rectangle,
   16px of padding, 186px of minimum height, and its contents stacked so the
   meta row can be pushed to the foot of it. `min-block-size` rather than
   `height` — a card with four lines of headline is taller and nothing clips.

   THE BORDERS COLLAPSE. The grid is `gap:0` (batch K §6, and it stays), so
   two adjacent cards would draw two 1px rules where the design has one. The
   negative margins pull each card onto its neighbour's edge — the standard
   collapse, and the reason the grid's own `border-top` is still the top rule
   of the first row rather than a doubled one. */
.card{background:var(--pap);
      border:1px solid var(--rule);
      margin-inline-start:-1px;margin-block-start:-1px;
      display:flex;flex-direction:column;
      grid-column:span 1;
      padding:16px;min-block-size:186px;
      transition:background-color .15s var(--ease)}
/* THE PARITY RULES GOT SIMPLER WITH THE GRID (batch K §6, and true again as
   of R45 §2). Every card in this grid is one track wide, so there are no
   special children in front of the uniform run to shift `nth-child`.

   IT SAID THAT ALREADY AND WAS WRONG FOR A WHILE. K §6 removed the spans;
   the 2026-09 redesign put one back — `.grid.has-lead .card.lead` spanned
   two of three tracks above 561px — and this paragraph went on claiming
   otherwise 1,340 lines above the rule that contradicted it. R45 §2 moved
   the lead out of the grid entirely and deleted that rule, which is what
   makes the sentence true rather than what makes it survive. */
/* Nothing here has to change with the column count any more: a collapsed
   border is a collapsed border at three tracks, at two and at one, which is
   what the `nth-child` parity rules that used to live here were doing by
   hand — and getting wrong every time the span count moved. */

/* ── The lead, and the two beside it (batch J §1, regraded by batch K) ────
   Nine cards at one size is a feed with good typography. Three levels is an
   edited page. WHICH story leads is `newsagent/lead.py` — a written rule
   with arithmetic behind it, printed in the ops report — and this block is
   only how the levels are drawn.

   THERE IS NO LEAD CARD ANY MORE (batch R45 §2). It was order AND SCALE AND
   LAYOUT: the lead spanned all six tracks at 44px over a 126px figure, K §6
   took the layout half and K §1 the scale half, and what was left to say
   "this is the lead" was that it came first and carried the page's one
   display figure. R45 takes the figure too — and the card. The lead is a
   SLAB under the price rail (`.lead-slab`), the first thing on the page
   after the ticker, and this block is now only how the SECOND TIER is
   drawn. A signal that has to be inferred from a card's ornament is a
   weaker signal than being first on the page.

   The phone case is still free and for the same reason: the slab is the
   first thing in the markup at every width, so a reader at 390px gets the
   same page and a reader with no CSS at all gets the arrangement as
   document order.

   `.has-lead` STAYS ON THE GRID, and it means something narrower now: not
   "a card in here is the lead" — none is — but "this grid is the page's
   EDITED state", so its first two cards are the second tier and the signup
   band counts from them. `lead.py` decides it, `cardLevel` and
   `_lead_level` read it, `render()` sets the class from the same `led` that
   draws the slab, and the §0b inventory counts it. Kept rather than dropped
   because it still gates real arithmetic in two renderers; if it ever stops
   doing that it goes from the app and the inventory in one commit. */
/* ── THE LEAD SLAB (batch R45 §2) ─────────────────────────────────────
   Four elements between two rules and nothing else: no box, no shadow, no
   radius, no border on three sides. The only frame is the 2px rule it hangs
   from — the drawing's, and the reason the slab needs no card around it.

   LOGICAL PROPERTIES THROUGHOUT. `border-block-start`, `padding-inline`,
   `margin-inline`: this ships in Arabic, and a physical `top`/`right` on a
   divider is exactly the bug `RTL_CSS` exists to catch. The two rules are
   block-direction and the meta row's spread is `justify-content`, so the
   whole thing mirrors without a single direction-specific rule.

   THE HEADLINE IS TWICE A CARD'S AND NEVER MORE. Cards are 17px (batch K2
   §3); this clamps between 24 and 34, which is `2 x 17` at the top and
   still under the masthead's own size at every width. It is fluid because
   the shell is (R38 §3) and a fixed headline in a fluid page is the gap
   that batch removed. `clamp` rather than a media query: one declaration,
   and no breakpoint to keep in step with the shell's.

   The slab is content-width, not full-bleed — it is prose, and the river's
   measure is what running text wants. */
/* THE TOP MARGIN IS `--s2`, NOT `--s5` (batch R47 §4a). The price rail
   carries its own bottom border and the slab opens with a 2px rule; 20px of
   blank between them read as a gap rather than as a start. 8px closes it so
   the rail and the slab read as one unit. The BOTTOM stays at `--s5`,
   because the space under the slab is doing the opposite job. */
.lead-slab{--ls-hd:clamp(24px, 2.1vw + 12px, 34px);
      max-width:var(--slab);margin-inline:auto;
      padding-inline:20px;
      margin-block:var(--s2) var(--s5);
      border-block-start:2px solid var(--ink)}
.lead-slab[hidden]{display:none}
.ls-meta{display:flex;flex-wrap:wrap;gap:6px 14px;
      justify-content:space-between;align-items:baseline;
      padding-block-start:10px;
      font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;letter-spacing:.12em;
      text-transform:uppercase;color:var(--dim)}
.ls-kick{font-weight:600;color:var(--acc)}
.ls-hd{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-weight:600;
      font-size:var(--ls-hd);line-height:1.14;letter-spacing:-.015em;
      margin-block:10px 0}
.ls-hd a{color:inherit;text-decoration:none}
.ls-hd a:hover{text-decoration:underline;text-underline-offset:3px}
.ls-sum{font-size:17px;line-height:1.5;color:var(--body);
      margin-block:10px 0;max-width:62ch}
/* The dateline's right-hand cell carries two values now (R47 §3) - the
   stamp and the source count - so it gets its own separator. An `<i>` and
   not a `::before`, because the count is conditional and a pseudo-element
   would print a bare pipe on a post with no sources. */
.ls-when i{font-style:normal;opacity:.45;margin-inline:7px}
/* NARROW PHONES DROP THE KICKER TO ITS OWN LINE (batch R47 §3). Measured:
   `08 Sep 2026 · 05:51 UTC | 1 source` does not fit beside the kicker at 360
   or 320, and the dateline wrapped to THREE lines — the slab grew 24.8px at
   both. 390 and up fit on one. So below 390 the row stacks, which is two
   lines instead of three, and the stamp cell itself never wraps: the brief's
   own rule, that a wrapped stamp is worse than a kicker on its own line. */
@media (max-width:389px){
  .ls-meta{flex-direction:column;align-items:flex-start;gap:2px}
  .ls-when{white-space:nowrap}
}
.ls-foot{margin-block-start:14px;padding-block-start:10px;
      border-block-start:1px solid var(--rule2);
      font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;letter-spacing:.08em;
      color:var(--dim)}
/* THE LIVE LINE (R47 §3). One line, always, at every width from 320 up.
   `min-width:0` on the text plus `overflow:hidden` is what actually lets a
   flex child ellipsis - without it the child's min-content width wins and
   the line wraps, which is the thing this may never do.

   It takes the space the foot's source line vacated, so the slab is still
   four elements and its height should not move; `_LEAD_RECORD` checks that,
   and if it grew the trade did not land. */
/* `padding-block-start:8px`, where the foot it replaced used 10. The line
   it carries is 12.5px body text against the foot's 11px mono, so at equal
   padding the slab grew 2.3px at every width — measured. Two pixels back
   makes the trade cost zero, which is what R47 §3 asked for and what
   `_LEAD_RECORD` records. */
/* IT IS THE BAND'S OWN ROW, SO IT IS SET LIKE ONE (batch R48, Kai: "this
   should be the exact font size and link to the same thing as the one in
   actual live band"). It was 12.5px against the row's 16/15 and clipped to
   a single line, so a phone got half a sentence and an ellipsis. `--bd-fs`
   is the row's size and `1.5` is the row's line-height at both steps
   (22.5/15 and 24/16, measured on the live page) — one declaration each,
   not two numbers kept in step by hand.

   NO `overflow:hidden` AND NO `nowrap` ANY MORE, which is three fixes in
   one property: the sentence wraps to as many lines as it needs, the phone
   gets the whole title, and the pulsing dot stops being cut in half. The
   dot scales to 1.4 (`bnpulse`) about its own centre, so it reached 1.4px
   past the line's left edge and the clip took that half away — Kai saw it
   before any check did. Nothing here needs to clip: the line is the last
   element in the slab and has the slab's padding under it. */
.ls-live{margin-block-start:14px;padding-block-start:8px;
      border-block-start:1px solid var(--rule2);
      display:flex;align-items:baseline;gap:8px;
      font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;letter-spacing:.08em;
      color:var(--dim);text-decoration:none}
.ls-live b{color:var(--acc);font-weight:600;letter-spacing:.12em;
      text-transform:uppercase;flex:0 0 auto}
.ls-live .age{flex:0 0 auto}
.ls-live .ls-live-t{flex:1 1 auto;min-width:0;color:var(--body);
      letter-spacing:0;font-size:var(--bd-fs,16px);line-height:1.5;
      font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
/* A ROW WITH NOWHERE TO GO IS NOT A LINK, and the band says so the same way
   (`.bd:not([data-dest]) .tx{cursor:default}`): the anchor is always in the
   markup so the client can give it a destination when the tape turns over,
   and it carries no `href` until there is one to carry. The arrow is the
   affordance, so it goes with the href rather than standing over nothing. */
.ls-live:not([href]){cursor:default}
.ls-live:not([href]) .ls-arrow{display:none}
/* The dot is a shape and mirrors by sitting in the flex order; the arrow is
   a GLYPH and does not, so it is turned by direction rather than swapped for
   a different character - one element, both editions. */
/* ON THE FIRST LINE'S BASELINE, NOT THE BLOCK'S MIDDLE (batch R48). While
   the sentence could not wrap, `align-self:center` and "beside the LIVE
   label" were the same place. Now that it wraps, centring puts the dot
   halfway down a three-line block — 20.5px below the label, measured — and
   it reads as a bullet for the second line. `baseline` aligns it with the
   label to 0.25px at every width. */
.ls-dot{flex:0 0 auto;inline-size:7px;block-size:7px;border-radius:50%;
      background:var(--acc);align-self:baseline}
.anim .ls-dot{animation:bnpulse 2.4s var(--ease) infinite}
/* INSIDE the sentence as of R48, not beside it: a flex sibling would sit on
   the FIRST line's baseline while the words wrapped past it, leaving the
   arrow stranded top-right. Inline after the last word, it wraps with it. */
.ls-arrow{opacity:.55;margin-inline-start:.35em}
[dir="rtl"] .ls-arrow{transform:scaleX(-1);display:inline-block}
@media (hover:hover){.ls-live:hover .ls-live-t{text-decoration:underline;
      text-underline-offset:3px}
  .ls-live:hover .ls-arrow{opacity:1}}
.card.lead .top{margin-bottom:var(--s4)}
/* ONE HEADLINE SIZE FOR EVERY CARD (batch K §1), AND IT IS THE DRAWING'S
   (batch K2 §3): 17, Newsreader, 600, 1.18, -.01em. It was 44 on the lead, 27
   on the two beside it and 19.5 on the rest before K; K merged them at 25;
   the mock sets `.hd` at 17 in a card that is a 16px-padded box, and a
   headline is 17 at every width now — the phone override K1 kept goes with
   it, because the drawing has one size and a phone is not a different paper.

   `.hd` ON ITS OWN, not `.card .hd`. The weight allowance in §4 names this
   selector exactly, and a rule the allowance can only reach by substring is a
   rule that stops being named the day somebody adds a compound in front of
   it. 600 is the one weight above 400 anywhere on a front-page surface apart
   from `.wnum`, and both are named. */
.hd{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:17px;line-height:1.18;
      letter-spacing:-.01em;font-weight:600;cursor:pointer}
/* The standfirst is one size too — 13.5, the mock's `.lines li`, on every
   card and NOT smaller on a phone. It was 16.5, which is only a point and a
   half under K's 25px headline; against a 17px headline it would have been
   the same size as the thing it sits beneath. */
.card p,.card.lead p{font-size:13.5px;-webkit-line-clamp:3;line-clamp:3}
.card.lead p{max-width:64ch}
.card.lead .pad{gap:var(--s3)}
.card.second p{-webkit-line-clamp:3;line-clamp:3}
/* THE LEAD KEEPS ITS PICTURE ON A PHONE (batch K §0). `.card.lead>.top
   {display:none}` lived here from J4 §1 — the second of two donors bought to
   keep a band cap true — and it took the front page's only image off every
   phone. The cap is retired and so is the donor. */
/* THE PHONE HEADLINE OVERRIDE IS GONE (batch K2 §3, reversing batch K §1).
   K kept 27 on a phone against 25 on a laptop and called it the one thing on
   the page allowed two sizes. The drawing has one `.hd` and this batch
   follows it: band rows, strip labels, the rail, meta, standfirsts and now
   the headline are all the same number at both widths, which is the rule K
   stated and then made one exception to. */
@media (hover:hover){.card:hover{background:var(--tint)}}
.card svg{display:block;width:100%;height:auto}
.card .top{display:block;cursor:pointer;margin-bottom:14px}
.card .pad{padding:0;display:flex;flex-direction:column;gap:9px}
/* 13, the ladder's meta/source rung (batch K §1). It was 11.5.
   THE MOCK'S `.foot` IS NOT THIS ELEMENT and batch K2 §3 did not reach it:
   the drawing puts a 10px mono row under a hairline at the FOOT of the card,
   and this site's meta row is the first thing inside `.pad`, above the
   headline. Moving it is a change to the card's document order, which two
   renderers emit and `check_front_page_renderers` asserts byte-identical —
   more than this batch was asked for. Named here so the gap is on the record
   rather than looking like an oversight. */
.meta{display:flex;gap:10px;align-items:center;font-size:13px;
      color:var(--dim);flex-wrap:wrap;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
      font-weight:400;min-width:0;overflow-wrap:anywhere}
.meta>*{min-width:0}
/* The tier mark: a kicker, not a crayon. FLASH is a filled oxblood block,
   DESK the same oxblood outlined, STORY plain ink, DIGEST dim — four marks a
   reader tells apart by weight and fill rather than by four more hues, and
   none of them amber, which §2b reserves for things that are moving now. */
.badge{font-size:13px;font-weight:400;letter-spacing:.13em;padding:2px 0;
       text-transform:uppercase;background:transparent;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
.badge.flash{background:var(--acc);color:var(--pap);padding:2px 7px}
.badge.story{color:var(--ink)}
.badge.digest{color:var(--dim)}
/* The desk column's badge — the one thing in a card's meta row that says a
   PERSON wrote this. Outlined rather than filled, so it reads as the same
   editorial voice as flash without claiming to be breaking. Its old reserved
   gold was #f0b429, which is --live to within a few degrees, and a human
   column is the opposite of a machine reading. */
.badge.desk{color:var(--acc);border:1px solid var(--acc);padding:1px 6px}
/* The "story so far" chip a card carries when its story has moved. Lives here
   rather than in ARTICLE_CSS because the cards it appears on are on the
   homepage, which does not load that block — the timeline it points AT is the
   part that only article pages carry. Accent-tinted, not tier-coloured: it is
   a property of the story, not another tier. `--accent-bg` is declared with
   the rest of the palette so no literal colour is needed in either theme. */
.chip-tl{font-size:13px;font-weight:400;letter-spacing:.2px;padding:3px 9px;
      background:var(--accent-bg);color:var(--accent);
      text-decoration:none;white-space:nowrap}
.chip-tl:hover{background:var(--accent);color:var(--on-accent)}
/* The live-blog chip. Same footprint, flash-coloured, with a dot that pulses
   where motion is welcome and simply sits still where it is not. */
.chip-live{font-size:13px;font-weight:400;letter-spacing:.6px;padding:3px 9px;
      background:var(--flash-bg);color:var(--flash);
      text-decoration:none;white-space:nowrap;display:inline-flex;
      align-items:center;gap:5px}
/* A STATIC MARK, NOT A PULSE (batch J §3c). It used to breathe at 1.8s, at
   exactly the same rate on a live blog updated a minute ago and on one whose
   last entry was at breakfast — which is the defect §3c exists to remove,
   in miniature. The dot stays because the shape carries the meaning; the
   animation goes because it carried none. */
.chip-live::before{content:"";width:7px;height:7px;
      background:var(--flash)}
/* 19.5px, which a three-across card has the width for. The catchy short title
   is on the graphic above; this is the full one, and it is what a reader
   scans. */
/* the size lives in the lead/second/uniform rule above (batch K §1) */
.card .hd a{color:inherit;text-decoration:none}
.card .hd:hover{color:var(--accent)}
/* The blurb is clamped to two lines by PRESENTATION, not by the writer:
   card_text stays whole in data/posts, in the feed, in the JSON payload and
   in structured data, and only the tile truncates it. A three-line blurb was
   what made the old grid read as a wall — the headline is the thing above it
   that is supposed to carry the story. Both properties: -webkit-line-clamp is
   what every current engine implements, `line-clamp` is the standard name it
   is being renamed to. */
.card p{color:var(--muted);line-height:1.5;
        display:-webkit-box;-webkit-box-orient:vertical;
        -webkit-line-clamp:2;line-clamp:2;overflow:hidden}
.more{display:flex;justify-content:center;padding:28px 0 4px}
/* an author `display` beats the UA sheet's [hidden]{display:none}, so the
   button stayed on screen with its hidden attribute set */
.more[hidden]{display:none}
.more button{background:transparent;border:1px solid var(--line);
      color:var(--text);padding:11px 26px;font-size:14px;
      cursor:pointer}
.more button:hover{border-color:var(--accent);color:var(--accent)}
.more button[disabled]{opacity:.5;cursor:default}
.empty{text-align:center;color:var(--muted);padding:56px 0;font-size:14.5px}
.share{display:flex;gap:8px;margin:18px 0 6px;flex-wrap:wrap}
/* min-width:0 — a flex item defaults to min-width:auto, so the input refuses
   to shrink below its intrinsic size and pushes the row past the viewport */
.share input{flex:1;min-width:0;background:var(--card);
        border:1px solid var(--line);
        color:var(--muted);padding:9px 12px;font-size:12.5px}
.share button,.share a.btn{background:var(--accent);border:0;
        color:var(--on-accent);
        font-weight:400;padding:9px 16px;cursor:pointer;
        font-size:13px;text-decoration:none;display:inline-block}
.share a.alt{background:transparent;border:1px solid var(--line);
        color:var(--muted);font-weight:400}
.share a.alt:hover{border-color:var(--accent);color:var(--accent)}
.srcs{margin-top:14px;font-size:13px;color:var(--muted)}
.srcs a{color:var(--accent);text-decoration:none}
footer{padding:30px 24px 50px;text-align:center;color:var(--muted);font-size:12px;
       line-height:1.9}
footer a{color:var(--muted);text-decoration:underline;text-underline-offset:3px}
/* The diary is the colophon's second sentence, not a second voice: same
   size and same ink, one line of air between them so the reader can tell
   "how this is made" from "how often it changes" without a rule. */
footer .diary{display:inline-block;margin-block-start:6px}
footer a:hover{color:var(--accent)}
/* ── The follow marks (batch R63 §3.2) ────────────────────────────────────
   The platforms' OWN glyphs, at the footer's own ink, sized off the type
   rather than in pixels so they step down with the row on a phone. 1.25em
   against a 12px row is 15px, which is the smallest either mark stays
   legible at.

   `currentColor` and NOT `--acc`: recolouring a trademark to this site's
   brand colour is what the guidelines prohibit, and it would read as an
   endorsement the platforms have not given. The hover follows the row's own
   hover for the same reason — it is the LINK reacting, not the mark. */
/* ── The header's own strip — batch R64 §B2 ───────────────────────────────
   `order:3` and not a number of its own: `.search` is already 3 and equal
   orders keep DOM order, so the marks land immediately after the magnifier
   without renumbering the four items behind it.

   MEASURED BEFORE IT WAS WRITTEN, because the brief's worry was the
   1100-1220 band. That band is where `.cats` is ALREADY GONE — it is
   hidden at 1199 — so the tight case is 1200 exactly, and there the nav is
   allocated 657px for 455px of content. 202px of slack against the 92px
   two marks and their gaps cost, on bytenews.io, measured in the browser.
   Below 1200 the strip is not drawn at all: the panel carries the accounts
   there, and the phone header keeps exactly the three controls it had. */
header>.socs{order:3;gap:8px;margin-inline-start:2px}
header>.socs a.soc{color:var(--dim);padding:0 2px}
header>.socs a.soc:hover{color:var(--acc)}
@media (max-width:1199px){header>.socs{display:none}}

.socs{display:inline-flex;align-items:center;gap:10px;
      vertical-align:-.2em;margin-inline-end:2px}
.socs a.soc{display:inline-flex;text-decoration:none;border-bottom:0;
      padding:0;line-height:0}
.socg{inline-size:1.25em;block-size:1.25em;fill:currentColor}
/* ── Mobile navigation ────────────────────────────────────────────────────
   Above the breakpoint none of this applies and the header is exactly what
   it was. Below it the header keeps only what a thumb needs — the brand, the
   search icon, the theme toggle and the menu button — and everything else
   moves into a panel.

   The panel is a child of <header>, positioned `absolute; top:100%`. The
   header is sticky (a positioned element and, via backdrop-filter, a
   containing block), so that anchors the panel to the bottom of the header
   at any header height, scrolled anywhere on the page, with no JS measuring
   and no magic number to drift. Being out of flow it also cannot move the
   header's own contents: opening the menu overlays the page rather than
   pushing it down. */
/* ── Language switcher ──────────────────────────────────────────────────────
   A <details>, so it opens and closes with no JavaScript at all and still
   works with the panel script that owns Escape on a phone. Every entry is a
   real link to the SAME page in another edition, which is also what the
   hreflang alternates in <head> say — one set of URLs, stated twice for two
   different readers. */
.langpick{position:relative;flex:0 0 auto}
.langpick>summary{list-style:none;cursor:pointer;border:0;
      color:var(--dim);padding:0 6px;font-size:12px;min-height:44px;
      font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;letter-spacing:.1em;
      display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
.langpick>summary::-webkit-details-marker{display:none}
.langpick>summary:hover,.langpick[open]>summary{color:var(--acc)}
.langmenu{position:absolute;top:calc(100% + 7px);inset-inline-end:0;z-index:12;
      background:var(--panel);border:1px solid var(--line);
      padding:6px;min-width:152px;display:flex;flex-direction:column;
      }
.langmenu a{padding:7px 11px;font-size:13.5px;
      text-decoration:none;color:var(--text);white-space:nowrap}
.langmenu a:hover{background:var(--tint);color:var(--acc)}
.langmenu a[aria-current="true"]{color:var(--accent);font-weight:400}
.menubtn,.navpanel,.navscrim{display:none}
/* ── 721-1199: the first documented collapse ──────────────────────────────
   Eight category links, four tier tabs, a search field, Archive, a language
   control and a theme toggle sum past the viewport well before the phone
   breakpoint — measured at 1,424px for the English edition, which is sixteen
   pixels of headroom at 1440 and none at all in German. The categories go
   first because they are the only items with somewhere to go: the overflow
   panel has carried every one of them since the mobile nav shipped. */
@media (max-width:1199px){
  .cats{display:none}
  .menubtn{display:inline-flex;background:transparent;
        border:1px solid var(--rule);color:var(--dim);
        min-width:44px;min-height:44px;padding:0;cursor:pointer;
        align-items:center;justify-content:center;flex:0 0 auto}
  .menubtn:hover{border-color:var(--acc);color:var(--acc)}
  .menubtn svg{width:16px;height:16px;display:block}
  .menubtn .i-x{display:none}
  .menubtn[aria-expanded="true"]{border-color:var(--acc);color:var(--acc)}
  .menubtn[aria-expanded="true"] .i-bars{display:none}
  .menubtn[aria-expanded="true"] .i-x{display:block}
  /* THE THREE BARS. These lived in the 720 block, and promoting the button
     to 1199 without them rendered an EMPTY BORDERED SQUARE between 721 and
     1199 on every page. Caught by LOOKING at the artifact, not by a gate:
     the button was present, focusable, 44x44 and correctly labelled, so
     every check that could see it passed.
     Centred on the button rather than pinned 13px from its edge — the
     button is `min-width:44px` here and was a fixed 30px there, so the old
     offset put the bars off-centre at the new size. */
  .menubtn{position:relative}
  .menubtn svg{display:none}
  .menubtn i{position:absolute;left:50%;margin-left:-9px;top:50%;
        width:18px;height:2px;background:currentColor;
        transition:transform .2s var(--ease),opacity .12s linear}
  .menubtn i:nth-child(1){margin-top:-7px}
  .menubtn i:nth-child(2){margin-top:-1px}
  .menubtn i:nth-child(3){margin-top:5px}
  .menubtn[aria-expanded="true"] i:nth-child(1){
        transform:translateY(6px) rotate(45deg)}
  .menubtn[aria-expanded="true"] i:nth-child(2){opacity:0}
  .menubtn[aria-expanded="true"] i:nth-child(3){
        transform:translateY(-6px) rotate(-45deg)}
  .navscrim{display:block;position:absolute;top:100%;left:0;right:0;
        height:100vh;background:rgba(0,0,0,.45)}
  .navpanel{display:flex;flex-direction:column;position:absolute;
        top:calc(100% + 1px);left:0;right:0;background:var(--pap);
        border-bottom:1px solid var(--rule);padding:6px 16px 14px}
  .navpanel[hidden],.navscrim[hidden]{display:none}
  .navpanel a{color:var(--ink);text-decoration:none;font-size:15px;
        padding:11px 4px;border-bottom:1px solid var(--rule2);
        letter-spacing:.2px}
  .navpanel a:last-child{border-bottom:0}
  .navpanel .grp{display:flex;flex-direction:column}
  .navpanel .grp+.grp{border-top:1px solid var(--rule);margin-top:4px;
        padding-top:4px}
  .navpanel a:hover,.navpanel a:focus{color:var(--acc)}
}
@media (max-width:720px){
  /* ONE ROW. The break is removed rather than hidden — a zero-height flex
     item with `flex-basis:100%` still forces a line, so `display:none` is
     the only thing that actually collapses the two rows into one. */
  header::before{display:none}
  header{padding:11px 16px;column-gap:9px}
  /* At 320 the row is wordmark(133) + magnifier(44) + theme(44) + menu(44)
     = 265, plus three 9px gaps and 32px of gutter = 324. Four pixels over,
     and a wordmark may not be cut (§10.1) — so the gutter is what gives,
     not the brand. */
  /* The brand takes all the slack, so the right-hand controls sit against
     the edge whatever the page happens to carry. Doing it with margin-left
     on the controls means splitting the free space when a page has two of
     them — the bug the old `:first-of-type` rule was working around. */
  header h1{font-size:17px;letter-spacing:2.6px;margin-inline-end:auto}
  /* Category links, Archive and About are in the panel now. */
  .cats,header>a.navlink{display:none}
  .search{margin-inline-start:0}
  /* Closed, the input is not there at all — the magnifier is the control.
     `display:none` rather than a zero width: a zero-width input is still a
     flex item, and it was the thing being animated. */
  .search:not(.open) input{display:none}
  .search .mag{display:inline-flex}
  .menubtn{display:inline-flex;background:transparent;
        border:1px solid var(--line);color:var(--muted);
        width:30px;height:30px;padding:0;cursor:pointer;align-items:center;
        justify-content:center;flex:0 0 auto}
  .menubtn:hover{border-color:var(--accent);color:var(--accent)}
  .menubtn svg{width:16px;height:16px;display:block}
  .menubtn .i-x{display:none}
  .menubtn[aria-expanded="true"]{border-color:var(--accent);
        color:var(--accent)}
  .menubtn[aria-expanded="true"] .i-bars{display:none}
  .menubtn[aria-expanded="true"] .i-x{display:block}
  .navscrim{display:block;position:absolute;top:100%;left:0;right:0;
        height:100vh;background:rgba(0,0,0,.45)}
  /* No `animation:` here, deliberately — see MOBILE_CSS. The panel opens more
     than once, and an entrance animation is the wrong mechanism for a thing
     that does: the open state has to be something the cascade says, not the
     residue of an animation that has to re-run every time. */
  .navpanel{display:flex;flex-direction:column;position:absolute;
        top:calc(100% + 1px);left:0;right:0;background:var(--panel);
        border-bottom:1px solid var(--line);padding:6px 16px 14px;
        }
  /* An author `display` beats the UA sheet's [hidden]{display:none} — the
     same trap the Load more button fell into. */
  .navpanel[hidden],.navscrim[hidden]{display:none}
  .navpanel a{color:var(--text);text-decoration:none;font-size:15px;
        padding:11px 4px;border-bottom:1px solid var(--line);
        letter-spacing:.2px}
  .navpanel a:last-child{border-bottom:0}
  .navpanel .grp{display:flex;flex-direction:column}
  .navpanel .grp+.grp{border-top:1px solid var(--rule);margin-top:4px;
        padding-top:4px}
  .navpanel a:hover,.navpanel a:focus{color:var(--accent)}
  /* Tier chips are grid controls, not navigation, so they are not in the
     panel: navjs moves the same element into the grid's tool row and back
     as the breakpoint is crossed. One row, scrolled sideways — wrapped, four
     chips cost the grid a second line of vertical space above the fold.
     The padding is NOT touched here: it used to set `padding-bottom:2px`
     alone, which left the track 3px above its chips and 2px below them and
     put the glider — inset by the same token on both edges — a pixel short
     of the chip it sits behind. Even padding is one value, in MOBILE_CSS. */
  header>.langpick{display:none}
  .navpanel .langrow{display:flex;flex-wrap:wrap;gap:7px;
        padding:12px 0 2px;margin-top:4px;border-top:1px solid var(--line)}
  .navpanel .langrow a{padding:6px 11px;border:1px solid var(--line);
        font-size:13px;border-bottom:1px solid var(--line)}
  .navpanel .langrow a[aria-current="true"]{border-color:var(--accent);
        color:var(--accent)}
}
/* At 320 the header row is wordmark(132.9) + magnifier(44) + theme(44) +
   menu(44) = 264.9 of content, and with a 9px gap and the phone's 16px
   gutter it needs 323.9px against 320. THREE POINT NINE PIXELS, in every one
   of the eight editions — the wordmark is the same Latin string in all of
   them — and the header wrapped to a second row for want of it.

   The GAP is what gives. Not the gutter, which is the content edge the
   wordmark shares with the first tile's label and the card headline (G1
   §4.1); not the wordmark, which may not be cut (§10.1); and not the three
   buttons, which are already at the 44x44 touch floor. 6px leaves 5.1px. */
@media (max-width:360px){
  header{column-gap:6px}
}

/* ── The lockup ───────────────────────────────────────────────────────────
   The mark sits at 0.80em with a +0.02em optical nudge and a 0.38em gap.
   Every one of those is a RATIO to the wordmark rather than a pixel, so the
   lockup holds together at the masthead's 23px and at a specimen's 96px
   without a second rule — and the 14px floor is the one absolute. The floor
   SURVIVED batch R63 and its reason did not: it used to be the 1.5-unit
   stroke on the unset cells. On a 13-unit grid 14px puts one unit at
   1.08px, and the thinnest thing the cut b has is the 1-unit STEM — at the
   floor it is a single device pixel, and below it the stem drops out, the
   b loses its ascender, and what is left is a block with a notch in it.

   THE NUDGE IS DOWNWARD AND IT IS NOT A FUDGE. A geometric square aligned to
   a baseline sits optically HIGH beside lowercase letterforms, because the
   baseline is where the letters' round overshoot ends and the square's flat
   edge does not overshoot at all. 0.02em is the correction, measured against
   the x-height of Newsreader at the masthead size.

   LOGICAL, NEVER MIRRORED. The gap is `margin-inline-end` so it follows the
   writing direction — but the lockup itself is `dir="ltr"` in all eight
   editions, because `bytenews.io` is a Latin domain name and a mirrored
   lockup would put the mark after a name that still reads left to right. */
header h1 a{display:inline-flex;align-items:baseline}
.mk-byte{inline-size:.8em;block-size:.8em;
      min-inline-size:14px;min-block-size:14px;
      margin-inline-end:.38em;flex:0 0 auto;
      /* baseline-aligned and then nudged; `align-self` is what puts a
         replaced element's own baseline where the text's is */
      align-self:baseline;position:relative;top:.02em}

/* ── The two fields ───────────────────────────────────────────────────────
   NOT `currentColor`, and that is the mechanism change batch R63 made. The
   four-cell mark that stood here was one colour throughout — a tile in
   `currentColor` with cells knocked out of it — so it could inherit the ink
   of whatever it sat in, and the rule above deliberately set no `color` so
   that it would. The cut b is TWO FIELDS by construction, and no single
   `color` expresses a paper field and an accent field at once.

   So the paint is named, out of the stylesheet's own `:root`: `--acc` for
   the field that must dominate, `--pap` for the field that is the page. Both
   flip with the theme, which is what the pack's own `mark.css` hardcodes a
   media query for and what this does not have to. THE ACCENT SIDE IS THE
   5/8 SIDE and that is the pack's one rule — paper on the 5/8 side is the
   page's own colour, and the masthead becomes a green b beside a green
   block. */
.mk-byte .f{fill:var(--acc)}
.mk-byte .p{fill:var(--pap)}

:root{--ease:cubic-bezier(.2,.8,.2,1)}
/* THE kill switch. One block, and selfcheck greps for it: everything above
   is decoration, and a reader who asked for less of it gets none of it. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;
        animation-iteration-count:1 !important;
        transition-duration:.001ms !important;scroll-behavior:auto !important}
  /* Reveals are INSTANT for this reader, not merely fast: the hidden state
     never applies, so there is no frame in which a card is invisible and no
     observer the page depends on to bring it back. `!important` because this
     block is above the entrance rules and they are the same specificity. */
  .js .card{opacity:1 !important;transform:none !important}
}
/* Entrance. The hidden state hangs off `.js` — a script is running — and NOT
   off `.anim`: motion is a separate question, answered above and in bnObserve.
   With JS off, and for a crawler, neither class is on <html> and every card is
   simply visible.
   `.in` is one-way: a card that has been revealed is never animated again, so
   a filter toggle and a back-navigation are instant. */
.js .card{opacity:0;transform:translateY(14px);
        transition:opacity .4s var(--ease),transform .4s var(--ease),
        border-color .2s var(--ease)}
.anim .wcell{opacity:0;transform:translateY(12px);
        transition:opacity .28s var(--ease),transform .28s var(--ease),
        border-color .2s var(--ease)}
.js .card.in,.anim .wcell.in{opacity:1;transform:none}
/* Arrive with no animation at all: a card that was already on screen when the
   observer first saw it (the reveal would be a fade over something the reader
   is looking at), and every card the grid rebuilds for a filter toggle.
   `now` is worn for ONE FRAME and taken off again (see MOTION_JS's `snap`).
   At 0,3,0 it outranks the card's own `transition` (0,1,0) — the hover lift
   and the theme swap's cross-fade both ride on that one declaration — so a
   card that kept the class would keep neither, and after one filter toggle
   that is every card on screen. */
.js .card.now{transition:none}
/* ── Motion from the data (redesign 2026-09) ─────────────────────────
   The strip's bars GROW to their reading as the cell is revealed — the fill
   is the figure, the growth is it arriving. The head's 48 bars draw up in
   sequence on load. The mark in the masthead TICKS — its two set bits swap
   with its two unset ones — each time the poll lands a row (MOTION_JS
   listens for `bn:arrive`), which is the one moving thing on the page that
   moves only when the wire does. And the lockup eases down as the page
   scrolls under the masthead, on the scroll timeline where the browser has
   one. Transform, opacity and fill only; every rule is behind `.anim`. */
.anim .wcell .wbar i{transform:scaleX(0);transform-origin:left center;
      transition:transform .9s var(--ease) .15s}
[dir="rtl"] .anim .wcell .wbar i{transform-origin:right center}
.anim .wcell.in .wbar i{transform:scaleX(1)}
.anim .band-hist rect{transform-origin:center bottom;
      animation:bnbar .5s var(--ease) both;
      animation-delay:calc(var(--i,0) * 14ms)}
@keyframes bnbar{from{transform:scaleY(0)}to{transform:scaleY(1)}}
/* A row landed: the mark's two fields TRADE PLACES for 700ms (the JS that
   adds the class is unchanged, and so is the 700ms). This replaces an
   opacity inversion on lit and unlit cells, which has no equivalent on a
   two-field mark — there is nothing here to dim. `fill` rather than
   `opacity` because a half-lit field is not a state this drawing has. */
.mk-byte rect{transition:fill .18s var(--ease)}
.mk-byte.tick .f{fill:var(--pap)}
.mk-byte.tick .p{fill:var(--acc)}
@supports (animation-timeline:scroll()){
  .anim header h1 a{animation:bnmast linear both;
      animation-timeline:scroll();animation-range:0 160px;
      transform-origin:left center}
  [dir="rtl"] .anim header h1 a{transform-origin:right center}
  @keyframes bnmast{to{transform:scale(.86)}}
}
/* Offscreen loops are parked. An infinite animation the reader cannot see
   still costs a frame budget the scroll needs — the marquee and the pulses
   are the two that run forever. Applied by bnObserve's second observer, and
   deliberately not behind `.anim`: pausing is never the wrong answer.
   The PSEUDO-elements are named explicitly, and that is the whole rule
   working rather than half of it: both pulses are `::before`s, `*` matches
   elements and never a pseudo-element, and `animation-play-state` is not
   inherited — so the first version of this rule parked the marquee and left
   the dots spinning, once per breaking card, while the reader scrolled. */
.offscreen,.offscreen *,
.offscreen::before,.offscreen *::before,
.offscreen::after,.offscreen *::after{animation-play-state:paused !important}
/* Hover: the lift the cards already had, plus a very slight scale on the
   graphic inside its own clipped box. Touch devices get none of it. */
.card .top{overflow:hidden}
.card .top svg{transition:transform .3s var(--ease)}
@media (hover:hover){
  .card:hover .top svg{transform:scale(1.02)}
}
/* Breaking. A pulse, not a siren: 1 → 1.35 on a 6px dot with the glow doing
   most of the work, on a 1.6s loop. The glow is STATIC and the scale rides on
   top of it — it used to be two box-shadow keyframes, which is a repaint on
   every frame of an infinite loop, once per breaking card in the grid: jank
   you feel in a scroll rather than see in the dot. Transform and opacity
   only, everywhere. */
.badge.flash::before,.chip-live::before{content:"";width:6px;height:6px;
      border-radius:50%;background:currentColor;display:inline-block;
      margin-inline-end:6px;vertical-align:middle}
/* NOTHING ON THIS SITE PULSES ANY MORE (batch J §3c). `bnpulse` drove the
   FLASH badge's dot, the live-blog chip's and the recent-hack dot, and all
   three pulsed at 1.6s whether the thing they marked had happened four
   minutes or fourteen hours ago. A FLASH card already prints its age two
   millimetres to the right — which is the number §3c asks for, and it was
   already there, being talked over by a dot.

   The dots survive as static marks. THEME_CSS allows a circle exactly where
   the shape IS the meaning, and a bullet before a tier word is that; what it
   does not allow is a shape pretending to be a reading. */
/* THE BREATHING DOT IS GONE (batch J §3c). It was the one always-moving
   element in the strip and it moved at exactly the same rate whether the gas
   feed had answered in the last minute or had been dead since Tuesday. Every
   status dot on this site is an age now — see AGE_CSS — and an age cannot be
   faked by a keyframe, which is the entire argument. */
/* A hack inside the last day keeps its dot and loses its pulse — same rule,
   and the tile already prints the date. */
/* A COUNTDOWN INSIDE ITS LAST HOUR NO LONGER FLASHES (batch J §3d). It
   animated to `--accent`, which is oxblood, which is EDITORIAL — links,
   kickers, corrections, FLASH — and lending it to a machine state is the
   same mistake as the amber above. The countdown is already counting, which
   is §3c's whole argument for a number over a light. */
/* The new-bytes pill drops in with a small overshoot. */
.anim #pill:not([hidden]){animation:bndrop .32s var(--ease)}
@keyframes bndrop{
  0%{transform:translate(-50%,-14px);opacity:0}
  70%{transform:translate(-50%,3px);opacity:1}
  100%{transform:translate(-50%,0)}
}
/* A PRINT: a price that has just moved. Applied by the bar's own paint(),
   and only where the NUMBER changed — not on every repaint. See TICKER_HTML.

   NOT CALLED `flash`, deliberately. `.badge.flash` is the breaking-news tier
   — a filled oxblood block with its own colour system — and `--flash` is a
   token. A price tick sharing that name collides in the CSS, in the strings
   and in a reader's head. A terminal calls this a print and so does this.

   TWO ANIMATIONS, NOT ONE, and that is the whole of "colour is not the
   signal". It replaced a single neutral `bnbump` wash on `--accent` that was
   identical up and down, so the animation carried no information at all and
   the direction lived only in the `.c` cell's sign. Around 8% of men cannot
   reliably separate `--up` from `--acc`; the colour is emphasis on a
   direction the row already states as `+1.24%` with an `up`/`down` class,
   and a reader who cannot see the hue loses nothing but the emphasis.

   Colour ONLY — nothing in the bar may change size while it is scrolling. */
.tk .v.print-up{animation:bnprint-up .5s var(--ease)}
.tk .v.print-down{animation:bnprint-down .5s var(--ease)}
@keyframes bnprint-up{0%{color:var(--up)}100%{color:inherit}}
@keyframes bnprint-down{0%{color:var(--down)}100%{color:inherit}}
/* The bar's own age, at the note's size rather than the age element's own
   13px. `.age` is reused rather than reimplemented so `AGE_JS` counts it for
   free — one interval for the page — and so it inherits the reserved `4ch`
   box that stops a number changing once a second from resizing the track. */
/* The reaction panel — batch Q3 §1. A key/value list, not a table: the
   fields are independent readings and a table would imply they are columns
   of one thing. `.rxu` is the UNMEASURED rendering and is deliberately not
   a dash or a zero — it carries a word, because a blank cell and a measured
   zero must not look alike. */
.rxpanel{border-top:1px solid var(--line);margin:28px 0 0;padding:18px 0 0}
.rxpanel h2{font-size:13px;letter-spacing:.13em;text-transform:uppercase;
      color:var(--dim);margin:0 0 12px;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
.rx{display:flex;align-items:baseline;gap:10px;padding:5px 0;
      border-bottom:1px solid var(--line);flex-wrap:wrap}
.rx:last-of-type{border-bottom:0}
.rxk{flex:1 1 40%;color:var(--dim);font-size:13px}
.rxv{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;
      color:var(--text);font-size:14px}
.rxn{color:var(--muted);font-size:12px;opacity:.8}
/* NOT ITALIC. This site ships one face in one style and no italic
   `@font-face`, so `font-style:italic` would be a SYNTHESISED
   oblique — the shipped face cannot draw one and the browser would
   fake it. `check_no_synthesised_oblique` refuses that, correctly,
   and it caught this line. The distinction this rendering needs is
   carried by the WORD anyway: a blank cell and a measured zero must
   not look alike, and a word versus a number is already that. */
.rxu{color:var(--muted);font-size:13px;letter-spacing:.02em}
.tk-note .age b,.tk-note .age .what{font-size:11px}
.tk-note .age b{color:var(--muted);min-width:3ch}
/* The theme swap, and nothing during first paint — THEME_INIT runs before
   the stylesheet, so there is no transition to catch a flash with. */
body,header,.card,.wcell,.chip{transition:background-color .15s linear,
      color .15s linear,border-color .15s linear}

/* ── Desktop header, alive in place ─────────────────────────────────────────
   The LAYOUT is untouched — Kaisar is happy with it and selfcheck asserts the
   1280px box positions are unchanged. What is added is behaviour: a sliding
   underline coloured by destination, so the nav teaches the same colour
   language the cards use. */
.cats a.navlink,header>a.navlink{position:relative;text-decoration:none;
      color:var(--muted);padding:3px 2px;
      transition:color .18s var(--ease),background-color .18s var(--ease)}
.cats a.navlink::after,header>a.navlink::after{content:"";position:absolute;
      left:0;right:0;bottom:-3px;height:2px;
      background:var(--nav-c,var(--accent));transform:scaleX(0);
      transform-origin:left center;
      transition:transform .22s var(--ease)}
[dir="rtl"] .cats a.navlink::after{transform-origin:right center}
.cats a.navlink:hover,header>a.navlink:hover{color:var(--nav-c,var(--accent))}
.cats a.navlink:hover::after,header>a.navlink:hover::after,
.cats a.navlink[aria-current]::after{transform:scaleX(1)}
.cats a.navlink[aria-current]{color:var(--nav-c,var(--accent))}
/* The brand's dot blips every four seconds — and picks up the accent while
   the new-bytes pill is showing, so "there is fresh news" is said twice. */
/* NO BLIP ON THE WORDMARK. What was here scaled the `.io` by 45% every four
   seconds for ever — the last of batch J §3c's animated ornaments, left
   standing because it was §4's to remove and §4 was not reached. A wordmark
   that twitches is a wordmark asking to be looked at, which is the opposite
   of what a masthead is for.
   (The retired keyframe is NOT named here on purpose: this comment ships
    inside every page, and a check grepping the served source for that name
    would find it in the note explaining its removal. Same rule the front
    page's own comments follow about their tokens.) */
.anim body.hasnew header h1 span{color:var(--accent);
      animation-duration:1.6s}


@media (max-width:720px){
  /* 1. Tap targets. The icon stays the size it looks best at; padding does
     the work, so nothing moves and everything is reachable. */
  /* G1 §4.2: NOT CIRCLES. These were the only round shapes on the page in a
     radius-0 system. §2c allows a circle where the shape IS the meaning — a
     status dot is a status dot — and a globe inside a ring is not that: the
     ring carried nothing the glyph did not. The target stays 44x44. */
  .menubtn,.themebtn,.search .mag{min-width:44px;min-height:44px;
        width:44px;height:44px}
  .navpanel a{min-height:48px;display:flex;align-items:center;font-size:16px}


  /* 2. The search is an OVERLAY over the brand, not a row below the header.
     It is laid out at its final size from the start and revealed with a
     transform — animating width (or letting flex reflow) is exactly what
     made it nudge on every keystroke. */
  .search{position:static}
  /* Right-anchored, expanding leftward over the brand — and stopping short
     of BOTH right-hand controls, not just the menu button. The first version
     reserved 60px and put the theme toggle behind the overlay, which is a
     control a reader cannot reach while searching.
     The second reserved 104px, and that was still short. The reserve is not
     a taste value: it is the header's own right-hand geometry, added up.
     16px of header padding, the 44px menu button, the 11px header gap, the
     44px theme toggle and the 11px gap the search itself sits behind = 126.
     At 104 the open box ran to 22px PAST the magnifier it came out of and
     covered the left 11px of the theme toggle — measured at 390px: the
     closed magnifier ends at x=264, the theme toggle starts at x=275, and
     the open overlay ended at x=286. That is the "it expands rightward"
     report, and it holds at 360, 375 and 390 because every term is fixed. */
  .search.open{position:absolute;inset-inline:12px auto;
        inset-inline-end:calc(20px + 44px + 11px + 44px + 11px);
        top:50%;translate:0 -50%;
        /* The anchored edge, and the origin the reveal grows FROM. */
        transform-origin:right center;
        z-index:6;display:flex;align-items:center;gap:6px;order:0;flex:none}
  [dir="rtl"] .search.open{transform-origin:left center}
  .search.open input{flex:1 1 auto;min-width:0;width:auto;
        padding:11px 14px;
        border:1px solid var(--line);
        /* Translucent, and readable in both themes because both --panel and
           --text come from the palette rather than a literal. */
        background:color-mix(in srgb,var(--panel) 85%,transparent);
        -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
        color:var(--text);font-size:16px}
  /* The reveal grows the box leftward out of the anchored edge. A scaleX
     about `transform-origin:right center` cannot put a single frame to the
     right of the final box — the old translateX(14px) start put the leading
     frame 14px further right still, over the theme toggle, which is the
     direction this whole rule is supposed to rule out. It is also why the
     RTL variant is gone: the origin flips, so one keyframe serves both. */
  .anim .search.open{animation:bnsearch .22s var(--ease)}
  @keyframes bnsearch{from{opacity:0;transform:scaleX(.94)}
                      to{opacity:1;transform:none}}
  /* 3. A visible way out, inside the field. Esc and an outside tap do the
     same thing; this is the one a thumb can find. */
  .search.open .clear{display:inline-flex;position:absolute;
        inset-inline-end:8px;width:32px;height:32px;align-items:center;
        justify-content:center;border:0;background:transparent;
        color:var(--muted);font-size:17px;cursor:pointer}
  .search.open .clear:hover{color:var(--text)}
  .search.open .mag{display:none}

  /* 4. The hamburger MORPHS. Two bars rotate into an ✕ and the middle one
     fades — one element, pure transform, no icon swap to mistime. */
  /* the bars are defined once, in the 1199 block above */

  /* THE SECOND-OPEN BUG. Kai's phone opened the menu, closed it, opened it
     again — and "Crypto" and the language switcher were not there.
     Nothing removes them: every row in the panel, the switcher included, is
     rendered into the page by `_chrome` and stays in the DOM whether the
     panel is open or shut (measured: 11 children on open #1 and on open #5).
     What was missing was not the node, it was the PAINT.
     Each row's only route to opacity:1 was the `forwards` fill of a one-shot
     `bnitem` animation. That makes "visible" the residue of an animation
     rather than something the cascade says — and a re-open is a fresh
     animation at time zero, measured: after five open/close cycles every row
     reported `currentTime: 0` with the previous fill discarded. So any
     browser that creates the animation and does not run it — a subtree that
     just left `display:none` inside a `backdrop-filter` containing block is
     exactly where WebKit does this — leaves that row at opacity:0 for good,
     and the reader is looking at a menu with holes in it.
     The reveal is a TRANSITION out of a one-frame snapshot instead, and the
     direction matters more than the mechanism: a row's steady state — open
     or shut — is fully visible, and `.entering` is the hidden state, applied
     and removed by navjs inside a single call so it exists for exactly one
     layout. Every way this can go wrong now ends with the row on screen. No
     JS, no `.anim`: nothing is ever hidden. Class never removed, transition
     refused, motion switched off: the row is at its steady opacity, which is
     1. There is no longer any state in which a menu row's visibility is
     something the browser has to keep for us. */
  .anim .navpanel,.anim .navscrim{
        transition:opacity .24s var(--ease),transform .24s var(--ease)}
  .anim .navpanel.entering,.anim .navscrim.entering{opacity:0}
  .anim .navpanel.entering{transform:translateY(-6px)}
  .anim .navscrim{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}
  /* Rows stagger in on a transition-delay set by navjs, and the delay is
     cleared on close, so the exit has no stagger at all — an exit that takes
     as long as an entrance feels broken. */
  .anim .navpanel a,.anim .navpanel .langrow,.anim .navpanel .themerow{
        transition:opacity .22s var(--ease),transform .22s var(--ease)}
  .anim .navpanel.entering a,.anim .navpanel.entering .langrow,
  .anim .navpanel.entering .themerow{
        opacity:0;transform:translateY(8px)}
  /* The current page carries an accent dot — the same "you are here" the
     desktop nav says with an underline. */
  .navpanel a[aria-current]{color:var(--accent)}
  .navpanel a[aria-current]::before{content:"";width:6px;height:6px;
        background:var(--accent);display:inline-block;
        margin-inline-end:9px}
}

/* ── Touch is not a narrow mouse (§5c) ────────────────────────────────────
   Every interactive target is at least 44x44 where touch is primary. These
   were measured by tools/responsive_sweep.py, not eyeballed: .sharebtn was
   30x30, .copyid 48x27, .perma 85x19 and the source <summary> 280x28 on
   every article page, and the tier chips came out at 40px tall inside the
   segmented control.

   An INLINE link inside a paragraph of prose is deliberately exempt — WCAG
   2.5.8 exempts it, and giving one a 44px box would double-space every
   article on the site. The sweep carries the same exemption, so the rule and
   the assertion agree. */
@media (pointer:coarse),(max-width:768px){
  .sharebtn,.copyid,.themebtn,.menubtn,.search .mag,.chip,
  .langpick>summary,#copy-btn,.btn,.perma,.fig-btn,
  .chip-tl,.chip-live,.entf,button.ent,.langmenu a,.navpanel .langrow a,
  #more-btn,.close,.back,.navlink,#pill,.w-only{
    min-inline-size:44px;min-block-size:44px}
  /* A short nav word ("Mood", "Tech") is 30px of text. The 44px floor is a
     TARGET size, not a type size, so it is reached with padding. */
  .navlink,.back{padding-inline:6px}
  /* a text field is a target too, and 39px of one inside a squeezed flex row
     is not one — let it take the space it needs */
  #share-url{flex:1 1 auto;min-inline-size:44px}
  /* inline-flex so a text control can reach the floor without the text
     growing with it */
  .perma,.copyid,.btn,#copy-btn,.sharebtn,.chip-tl,.chip-live,
  .entf,button.ent,.langmenu a,.back{
    display:inline-flex;align-items:center;justify-content:center}
  /* the sources disclosure is a real control, not prose */
  details>summary{min-block-size:44px;display:flex;align-items:center}
  #share-url{min-block-size:44px}
}

/* ── Tracking is a LATIN device (§5e) ─────────────────────────────────────
   `letter-spacing:.13em` on the kickers, labels, tabs and badges is what
   makes a mono eyebrow read as an eyebrow. Applied to a CURSIVE script it
   pulls joined letterforms apart: the Arabic strip rendered its labels as
   disconnected letters, which is not a styled label but a misspelling.
   Bengali and Devanagari have the same conjunct behaviour, and CJK gains
   nothing from Latin tracking either.

   Scoped on the EDITION's lang, not on dir: Bengali and Hindi are
   left-to-right and are damaged in exactly the same way, so keying this to
   RTL would have fixed one of the four. */
:root[lang="ar"] .kick,:root[lang="ar"] .eyebrow,:root[lang="ar"] .badge,
:root[lang="ar"] .lab,:root[lang="ar"] .chip,:root[lang="ar"] header h1,
:root[lang="bn"] .kick,:root[lang="bn"] .eyebrow,:root[lang="bn"] .badge,
:root[lang="bn"] .lab,:root[lang="bn"] .chip,
:root[lang="hi"] .kick,:root[lang="hi"] .eyebrow,:root[lang="hi"] .badge,
:root[lang="hi"] .lab,:root[lang="hi"] .chip,
:root[lang="zh"] .kick,:root[lang="zh"] .eyebrow,:root[lang="zh"] .badge,
:root[lang="zh"] .lab,:root[lang="zh"] .chip,
:root[lang="ar"] .langpick>summary,:root[lang="bn"] .langpick>summary,
:root[lang="hi"] .langpick>summary,:root[lang="zh"] .langpick>summary{
  letter-spacing:normal}
/* ── THE ITALIC OVERRIDE IS GONE, BECAUSE THE ITALIC IS (batch J3 §3) ─────
   J2 put these four editions back to roman by name, because the label
   register was Newsreader italic and these four do not download Newsreader —
   so the rule reached the platform's script face and the browser answered by
   SLANTING it, which for Arabic or Devanagari is a damaged label rather than
   a styled one, and for CJK is a convention that does not exist.

   §3 makes every edition roman, so this override has no work left: it existed
   only to undo a rule that no longer exists. Deleting it is not a relaxation
   — `check_no_synthesised_oblique` asserts the ban directly now, on the
   rendered CSS of all eight editions, which is a stronger statement than four
   `font-style:normal` declarations that happened to cover the same ground. */
/* …but a FIGURE is Latin in every edition, so the numerals keep theirs. */
:root[lang="ar"] .wnum,:root[lang="bn"] .wnum,
:root[lang="hi"] .wnum,:root[lang="zh"] .wnum{
  letter-spacing:normal;font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
/* The wordmark is a Latin brand name in all eight. Isolated so the bidi
   algorithm cannot reorder "bytenews" and ".io" around each other. */
header h1{unicode-bidi:isolate}
/* CJK must be allowed to break inside a run or a headline overflows at
   320px; Latin words in the same headline keep normal breaking. */
:root[lang="zh"] h1,:root[lang="zh"] h2,:root[lang="zh"] h3,
:root[lang="zh"] .hed{line-break:normal;word-break:normal;
  overflow-wrap:anywhere}

/* The share control. `margin-inline-start:auto` is the whole right-alignment
   rule — the meta row is a flex row, so the control takes whatever space is
   left and sits at the end of it in both directions. */
.sharebtn{position:relative;flex:0 0 auto;margin-inline-start:auto;
      display:inline-flex;align-items:center;justify-content:center;
      width:30px;height:30px;margin-block:-6px;padding:0;border:0;
      background:transparent;color:var(--muted);
      cursor:pointer;-webkit-tap-highlight-color:transparent;
      transition:color .18s var(--ease),background-color .18s var(--ease)}
/* The TOUCH target is 44px and the ink is 18px. A pseudo-element carries the
   difference, so the meta row keeps the height it had: grown to 44px the
   control would set the height of every card's meta row. 30 + 7 + 7 = 44,
   and selfcheck derives that sum out of these two rules rather than reading
   it here. What it costs is stated rather than hidden — a 44px target
   centred on an 18px row reaches 13px below the row and the gap under it is
   8, so the last 5px sit over the headline's BOX. Measured in a browser:
   that is its leading, in the 44px at the trailing edge, and every point of
   the headline's own text still hit-tests to the headline. Making the
   VISIBLE box bigger instead would put the hover tint there, which is the
   same overlap and a visible one. */
.sharebtn::before{content:"";position:absolute;inset:-7px}
/* In the flyout the control lands beside the sticky close button, and a 44px
   target on a 30px box reaches 7px past its own edge. Measured in a browser:
   that strip sat ON TOP of the ✕ — both are positioned, and this one is later
   in the document — and took the left 7px of the one control a reader needs
   to get out. The trailing margin is the reach plus two. */
#flyout .meta .sharebtn{margin-inline-end:9px}
.sharebtn:hover,.sharebtn[aria-expanded="true"]{color:var(--accent);
      background:var(--accent-bg)}
.sharebtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Two rules that size ALL svgs in their scope — `.card svg` and
   `main.article svg` at 100% width, `#flyout svg` the same with a radius and
   a margin — and this icon lives inside every one of those scopes. Both
   selectors here outrank them on class count, which is why the icon carries
   a class of its own rather than being matched as a bare `svg`. */
.sharebtn .shareico,#flyout .sharebtn .shareico{width:18px;height:18px;
      display:block;margin:0}
/* The fallback popover, for a browser with no Web Share API. Built by
   SHARE_JS when there is one to show and removed when it closes, so nothing
   here has a hidden state to leak into a page that is not running scripts.
   Positioned in the BODY, not in the card: `.card` clips its own overflow. */
.sharepop{position:absolute;z-index:40;min-width:186px;padding:6px;
      background:var(--panel);border:1px solid var(--line);
      display:flex;flex-direction:column;
      animation:bnpop .16s var(--ease)}
@keyframes bnpop{from{opacity:0;transform:translateY(-4px)}
                 to{opacity:1;transform:none}}
.sharepop .row{display:flex;align-items:center;min-height:40px;gap:9px;
      padding:0 12px;border:0;background:transparent;color:var(--text);
      font:inherit;font-size:13.5px;font-weight:400;text-align:start;
      text-decoration:none;cursor:pointer}
.sharepop .row:hover{background:var(--accent-bg);color:var(--accent)}

/* MONO, as of batch K2 §0 — the one change this batch makes to the bar, and
   it makes it on BOTH pages at once, which is the point: §2's ruling is that
   the front page and `/c/crypto/` must render the same thing, and §0's is
   that the rail is set in the figure face. A declaration that lands on one
   surface serving two pages satisfies both. Nothing else here moves: the
   size, the colours, the padding, the roll speed and the pause behaviour are
   what the category page has always had, because the desk's call is that the
   category page's bar was the correct one. */
.tickerbar{border-bottom:1px solid var(--line);background:var(--panel);
      overflow:hidden;white-space:nowrap;font-size:12.5px;
      font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
      font-variant-numeric:tabular-nums}
.tickerbar[hidden]{display:none}
.tk-track{display:flex;width:max-content;animation:tkroll 40s linear infinite}
.tickerbar:hover .tk-track,.tickerbar:focus-within .tk-track{
      animation-play-state:paused}
@keyframes tkroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
/* Padding on BOTH ends of a half, not just the trailing one. The track sits
   at translateX(0) until the marquee starts and a reader lands on it there,
   so a half with no leading padding puts the first symbol hard against the
   viewport edge, half of it under the phone's own screen curve — reported
   live on iOS Safari. Symmetric because the two halves have to stay
   identical: -50% is exactly one of them, and a leading pad on the first
   copy alone would make the loop jump. */
.tk-half{display:flex;align-items:center;gap:24px;padding:7px 24px}
.tk{display:inline-flex;align-items:baseline;gap:6px}
.tk b{font-weight:400;letter-spacing:.4px;color:var(--text)}
/* DECLARED RATHER THAN INHERITED (batch K2 §2). The weight budget names the
   bar's value and its change, and `.tk .c` already said 400 while `.tk .v`
   left it to the cascade — so the budget could read one of the pair and not
   the other. It computed to 400 either way; nothing about the bar moves. */
.tk .v{color:var(--muted);font-weight:400}
.tk .c{font-weight:400;color:var(--muted)}
.tk .c.up{color:var(--up)}
.tk .c.down{color:var(--down)}
.tk-note{color:var(--muted);opacity:.72;font-size:11px;letter-spacing:.3px}
/* The day's line beside the print (redesign 2026-09): dim by default,
   direction-coloured where the 24h change is, no fill, no axis — it is the
   tape, not a chart. */
.tk-spark{inline-size:44px;block-size:12px;display:inline-block;
      vertical-align:-2px;margin-inline-start:4px;overflow:visible}
.tk-spark polyline{fill:none;stroke:var(--dim);stroke-width:1.25;
      stroke-linejoin:round;stroke-linecap:round;
      vector-effect:non-scaling-stroke}
.tk-spark.up polyline{stroke:var(--up)}
.tk-spark.down polyline{stroke:var(--down)}
@media (max-width:640px){
  .tickerbar{font-size:12.5px}
  /* 16px, the same gutter the intel strip and the grid keep on a phone. */
  .tk-half{gap:16px;padding:5px 16px}
}
/* Reduced motion: the same bar, parked and swipeable, rather than nothing —
   the prices are the content, the movement is decoration. */
@media (prefers-reduced-motion:reduce){
  .tk-track{animation:none}
  .tickerbar{overflow-x:auto}
}

/* §5b: the measure holds at 62–75 characters at every width. It was 760px of
   16px prose — about 90 characters a line, which is a wall rather than a
   column, and the one number a newspaper has never got wrong. `ch` and not
   px, so it stays a MEASURE when a reader zooms or the face falls back to a
   platform serif in a non-Latin edition: 1ch is the width of "0" in whatever
   font actually rendered. Measured after the change at 68ch → 72 characters
   at every width from 768 up, and the width of the screen below that. */
main.article{max-width:760px}
main.article .body{max-inline-size:68ch}
main.article svg{display:block;width:100%;height:auto;
        margin:6px 0 18px}
main.article h1{font-size:29px;line-height:1.25;margin:14px 0 12px}
/* §6's dateline. It takes the KICKER's case and the LABEL's ink, which is the
   newspaper convention and also exactly what it is: a dateline is not the
   paper's editorial voice — it does not get --acc — and it is not a column
   head either. It says where the reporter was standing, and here that is a
   block height anybody can go and check. Set on the body's measure so it
   reads as the first line of the report rather than as a caption above it. */
main.article .dateline{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;
        letter-spacing:.12em;font-size:11px;font-weight:400;color:var(--dim);
        margin:16px 0 -4px;max-inline-size:68ch}
main.article .body{font-size:17px;line-height:1.62;color:var(--ink)}
main.article .body p{margin:14px 0}
main.article .body ul{margin:14px 0}
main.article .body ul{margin-inline-start:20px}
main.article .body a{color:var(--accent)}
/* …and the same four the flyout's own body carries, at this scale. See the
   comment there: these are the constructs a column's markdown can produce and
   a wire story never does, and the global margin reset is what leaves them
   flush against the paragraph above. A quotation is set apart by a rule in
   the accent rather than by italics — the markdown that makes it is `>`, and
   a column quoting a filing is quoting somebody else's words. */
main.article .body h2,main.article .body h3,main.article .body h4{
        margin:26px 0 8px;line-height:1.35;color:var(--text)}
main.article .body h2{font-size:20px}
main.article .body h3{font-size:17.5px}
main.article .body h4{font-size:15px;letter-spacing:.3px;color:var(--muted)}
main.article .body ol{margin:14px 0;margin-inline-start:20px}
main.article .body li{margin:6px 0}
main.article .body blockquote{margin:18px 0;padding-inline-start:16px;
        border-inline-start:3px solid var(--accent-bg);color:var(--text)}
main.article .disclaimer{color:var(--muted);font-size:13px}
/* ── The desk byline ────────────────────────────────────────────────────────
   Only a `desk` column carries one, and that is the point of it: a wire story
   is a summary of six outlets and has no person to name, so a byline over one
   would be this site claiming somebody wrote what a model did. The name is
   not marked up separately — the string is one sentence with a {name} in it,
   because the languages this site ships in do not agree on where in that
   sentence a name goes. */
.byline{display:flex;flex-wrap:wrap;align-items:baseline;gap:9px;
        margin:-2px 0 16px;font-size:13.5px;font-weight:400;color:var(--body)}
.byline time{font-weight:400;color:var(--muted);
        font-variant-numeric:tabular-nums}
/* "Translated from English", on the seven editions only. Same weight and
   colour as the date beside it: it is a fact about the page, not a warning,
   and it must not out-shout the name it qualifies. */
.byline .tr{font-weight:400;color:var(--muted)}
/* ── Provenance ribbon (batch 12.6 A3) ──────────────────────────────────────
   How the story was sourced and how fast, under the headline. One line that
   WRAPS rather than scrolls: on a phone it becomes two or three rows, which
   is the right degradation for a row of independent facts — a horizontal
   scroller hides the last of them behind an affordance nobody uses. */
.prov{display:flex;flex-wrap:wrap;gap:6px 14px;margin:-4px 0 18px;
      padding:9px 12px;border:1px solid var(--line);
      font-size:12.5px;color:var(--muted);line-height:1.5}
.prov b{color:var(--body);font-variant-numeric:tabular-nums;font-weight:500}
.prov time{font-variant-numeric:tabular-nums}
/* ── The canonical ID chip (batch 12.6 B1) ─────────────────────────────────
   `user-select:all` on the code: one click selects the whole ID, which is
   what somebody quoting it wants, and it is the half of the control that
   works with no JavaScript at all. */
.cite-id{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
         margin:22px 0 4px;font-size:12.5px;color:var(--muted)}
.cite-id .lab{text-transform:uppercase;letter-spacing:.6px;font-size:11px}
.cite-id code{user-select:all;background:var(--chip);color:var(--body);
              border:1px solid var(--line);
              padding:3px 8px;font-size:12.5px;letter-spacing:.3px}
.cite-id button{font:inherit;cursor:pointer;color:var(--muted);
                background:none;border:1px solid var(--line);
                padding:3px 9px}
.cite-id button:hover{color:var(--body);border-color:var(--muted)}
/* ── /accuracy/ and /status/ (batch 12.6 A1, C2) ────────────────────────────
   One figure block serves both — they are the same shape of claim (a number
   with a sentence under it) and two stylesheets for it would drift. */
.score{display:flex;flex-wrap:wrap;gap:10px;margin:22px 0 26px}
.score .fig{flex:1 1 130px;border:1px solid var(--line);
            padding:14px 16px;background:var(--chip)}
.score .fig b{display:block;font-size:30px;line-height:1.1;color:var(--body);
              font-variant-numeric:tabular-nums}
.score .fig span{display:block;margin-top:4px;font-size:12.5px;
                 color:var(--muted)}
/* The sentence the accuracy page exists for. Bordered, not shouted: it is a
   caveat about our own number, and styling it as a warning would make it read
   as somebody else's problem. */
.caveat{border-inline-start:3px solid var(--accent);padding:2px 0 2px 14px;
        color:var(--body)}
.acc{width:100%;border-collapse:collapse;margin:10px 0 18px}
.acc th,.acc td{text-align:start;padding:8px 10px;
                border-bottom:1px solid var(--line);font-size:14px}
.acc .num{text-align:end;font-variant-numeric:tabular-nums}
.months{display:flex;align-items:flex-end;gap:6px;height:110px;margin:14px 0}
.months .bar{flex:1 1 0;display:flex;flex-direction:column;
             justify-content:flex-end;align-items:center;height:100%}
.months .bar span{width:100%;min-height:2px;background:var(--accent);
}
.months .bar small{margin-top:5px;font-size:11px;color:var(--muted)}
/* The uptime strip. `flex-wrap` and a min-width rather than a scroller: 90
   cells become three rows on a phone, which stays readable — a horizontal
   scroller would hide the most recent days, which are the ones anybody
   arriving at a status page came to see. */
.strip{display:flex;flex-wrap:wrap;gap:3px;margin:18px 0 8px}
.strip .d{flex:1 0 6px;height:26px;background:var(--line)}
.strip .d.up{background:#2ea36b}
.strip .d.degraded{background:var(--live)}
.strip .d.down{background:#d4483b}
.incidents{list-style:none;padding:0;margin:14px 0}
.incidents li{border:1px solid var(--line);
              padding:14px 16px;margin:0 0 12px}
.incidents h3{margin:0 0 4px;font-size:16px}
.incidents .unexplained{color:var(--muted)}
.prose .corrected{border-left:3px solid var(--accent);padding:10px 14px;margin:14px 0;background:var(--card);font-size:14px}
.incidents .when{margin:0 0 8px;font-size:12px;color:var(--muted);
                 text-transform:uppercase;letter-spacing:.5px}
/* ── "What is not working right now" on /status/ (batch Q11 §1) ────────────
   Reuses `ul.iw` above, because it IS the same shape as a hole in the record
   on /coverage/ — a short list of things this site is short of — and a
   second list style for it would be the drift the integrity panel's own
   comment warns about. The stamp under it is the load-bearing part: an empty
   list and an uncounted one are the same shape on a page, so the time of the
   last look is printed whichever way the list came out. */
.working-when{margin:8px 0 0;font-size:12.5px;color:var(--muted)}
/* ── "Cite this" (batch 12.6 B2) ───────────────────────────────────────────
   A disclosure, so it costs one line until somebody wants it. The textareas
   are readonly and sized to their format — BibTeX is seven rows because it
   IS seven rows, and a two-row box with a scrollbar is a citation somebody
   copies half of. */
details.cite{margin:6px 0 2px;font-size:13px}
details.cite summary{cursor:pointer;color:var(--muted);
                     padding:4px 0;user-select:none}
details.cite summary:hover{color:var(--body)}
.cite-body{display:grid;gap:10px;margin:8px 0 4px}
.cite-body label{display:block;font-size:11px;text-transform:uppercase;
                 letter-spacing:.6px;color:var(--muted)}
.cite-body textarea{display:block;width:100%;margin-top:4px;resize:vertical;
                    font:12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;
                    color:var(--body);background:var(--chip);
                    border:1px solid var(--line);
                    padding:8px 10px;box-sizing:border-box}
.archived{margin:14px 0 0;font-size:12.5px;color:var(--muted)}
/* ── A figure and its evidence (batch 12.6 A2) ──────────────────────────────
   The button is styled as the number itself — a dotted underline, the body
   colour — because it IS the number: making it look like a link would put a
   second kind of link in prose that already links glossary terms.

   NO-SCRIPT IS THE DEFAULT STATE and that is deliberate. `.fig-src` hides
   only under `html.js`, a class the theme script sets on the first line it
   runs, so a reader with scripting off gets the quoted sentence, the outlet
   and the link laid out in the flow. The evidence is the point; the panel is
   a convenience over it. */
.fig{position:relative;white-space:nowrap}
.fig-btn{font:inherit;color:var(--body);background:none;border:0;padding:0;
         cursor:pointer;border-bottom:1px dotted var(--accent);
         text-underline-offset:2px}
.fig-btn:hover,.fig-btn:focus-visible{border-bottom-style:solid;
                                      outline-offset:3px}
.fig-src{white-space:normal;display:block;margin:6px 0 10px;padding:10px 12px;
         border-inline-start:3px solid var(--accent);background:var(--chip);
         font-size:13px;color:var(--body)}
/* THE QUOTES ARE THE CHARACTERS, NOT A CSS ESCAPE OF THEM (Q11 §4). This
   stylesheet is a plain Python string, so `"\201C"` was never CSS at all:
   Python reads `\201` as an OCTAL escape and hands the browser U+0081 — a C1
   control — followed by a literal `C`. Every figure card on the site opened
   with a tofu box and a capital C and closed with a box and a D, which read
   as a missing glyph and was not one: the vendored mono carries U+201C, and
   the CSS never asked for it. Written as the characters themselves there is
   nothing left to escape wrongly, and `check_stylesheet_tokens` fails on any
   control character reaching the shipped stylesheet. */
.fig-src q{quotes:"“" "”";display:block;margin-bottom:6px}
.fig-meta{display:block;font-size:11.5px;color:var(--muted)}
/* ── Visible edit history (batch 12.6 A4) ───────────────────────────────────
   A `<details>`, so the whole record is one click from the headline with or
   without scripting. `del` is struck and muted, `ins` is plain body text with
   no underline — an underline in prose reads as a link, and these are
   sentences rather than controls. */
.edits{margin:-6px 0 18px;font-size:13px}
.edits summary{cursor:pointer;color:var(--muted);padding:4px 0;
               user-select:none}
.edits summary:hover{color:var(--body)}
.edits summary .n{font-variant-numeric:tabular-nums}
.editlist,.edits .d{list-style:none;padding:0;margin:0}
.edits .edit{border-inline-start:2px solid var(--line);padding:2px 0 2px 12px;
             margin:10px 0}
.edits .when{margin:0 0 5px;font-size:11px;text-transform:uppercase;
             letter-spacing:.5px;color:var(--muted)}
.edits .d li{margin:3px 0;line-height:1.5}
.edits del{color:var(--muted);text-decoration-thickness:1px}
.edits ins{color:var(--body);text-decoration:none}
.edits .note{margin:10px 0 0;font-size:11.5px;color:var(--muted)}
/* ── Right of reply (batch 12.6 A5) ─────────────────────────────────────────
   Bordered and visually distinct from the story, because the one thing this
   block must never do is read as our prose. The accent stripe and the tag are
   doing the same job the `<figcaption>` does for a screen reader: saying
   whose words these are BEFORE them rather than after. */
.reply{margin:26px 0 6px;padding:14px 16px;border:1px solid var(--line);
       border-inline-start:4px solid var(--accent);
       background:var(--chip)}
.reply figcaption{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;
                  margin-bottom:9px;font-size:13px;color:var(--body)}
.reply .tag{text-transform:uppercase;letter-spacing:.6px;font-size:10.5px;
            font-weight:400;color:var(--accent)}
.reply .role,.reply time{font-weight:400;color:var(--muted);font-size:12.5px}
.reply blockquote{margin:0;padding:0;border:0;color:var(--body)}
.reply blockquote p:first-child{margin-top:0}
.reply blockquote p:last-child{margin-bottom:0}
.reply .note{margin:10px 0 0;font-size:11.5px;color:var(--muted)}
.declines{padding-inline-start:18px}
.declines li{margin:4px 0;font-size:13.5px}
/* ── /latency/ (batch 12.6 C1) ──────────────────────────────────────────────
   The reason a target is what it is sits UNDER the path name in the same
   cell, because splitting them across columns is how a reader ends up with
   the number and not the argument. */
.lat td small{display:block;margin-top:4px;color:var(--muted);font-size:12px;
              line-height:1.5;font-weight:400;max-width:52ch}
.lat td:first-child{min-width:20ch}
/* A day with no measurements is drawn as an empty slot rather than skipped —
   the emptiness is the fact. */
.lat-days{height:78px;gap:3px}
.lat-days .bar.none{opacity:.28}
.lat-days .bar.none span{min-height:2px;background:var(--line)}
.misses{padding-inline-start:18px}
.misses li{margin:5px 0;font-size:13.5px}
/* ── the integrity panel and /coverage/ (batch Q4) ──────────────────────────
   The panel sits under a download row and reuses `.score` above, because it
   IS the same shape of claim as /accuracy/ and /status/ — a number with a
   sentence under it — and a third stylesheet for it would drift the way the
   third row list would have. What is new here is the window list: a fixed
   column for the interval so the eye can scan for a long one, and the reason
   under it in muted type rather than beside it, because a `ConnectionError`
   carrying a URL is longer than any cell that would fit it. */
/* `--panel`, not `--chip`, and the two are now the same value. Q4 wrote here
   that eight rules on this stylesheet said `var(--chip)` with nothing
   defining it, that every one of them painted transparent, and that fixing it
   was not that batch's job — which was the right call and then sat in a
   comment for a fortnight, where a comment is a record and not a plan. Q11 §4
   declares it (see `_ALIASES`) and `check_stylesheet_tokens` is what stops
   the ninth. This rule stays on `--panel` because that is what it means. */
.integ{margin:26px 0 8px;border:1px solid var(--line);padding:20px 20px 6px;
       background:var(--panel)}
.integ h2{font-size:17px;margin:0 0 8px}
.integ h3{font-size:13px;letter-spacing:.08em;text-transform:uppercase;
          color:var(--muted);margin:22px 0 8px;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
.integ .lede{margin:0;font-size:15px;line-height:1.6}
.integ .scope{margin:6px 0 0;font-size:13.5px;color:var(--muted)}
.integ .score{margin:18px 0 4px}
.integ .fig b{font-size:24px}
.integ p{font-size:14px;line-height:1.6}
.integ .note{color:var(--muted);font-size:12.5px;margin:20px 0 14px}
ul.iw{list-style:none;padding:0;margin:0}
ul.iw li{padding:8px 0;border-bottom:1px solid var(--line);font-size:13.5px;
         line-height:1.55}
ul.iw li:last-child{border-bottom:0}
ul.iw time{display:inline-block;min-width:19ch;color:var(--muted);
           font-variant-numeric:tabular-nums;font-size:12.5px}
/* A correction is a paragraph, not a row. The 19ch date column above is right
   for a list of intervals and wrong for prose — it indents the first line by
   a third of the measure and leaves the rest flush, which reads as a broken
   hanging indent. */
ul.iw li.corr time{display:block;min-width:0;margin-bottom:4px}
ul.iw .who{color:var(--accent)}
ul.iw .rows{color:var(--muted);font-size:12px}
ul.iw small{display:block;color:var(--muted);font-size:12px;margin-top:3px;
            overflow-wrap:anywhere;max-width:76ch}
ul.iw .owed{color:var(--accent)}
/* The boundary page. One block per category, and the category's own sentence
   is the heading — "we are not watching this" is the finding, and a heading
   reading "off" would make the reader work out what off means. */
.bound{margin:26px 0}
.bound>li{list-style:none;border-inline-start:3px solid var(--line);
          padding:2px 0 2px 14px;margin:0 0 22px}
.bound>li.off{border-inline-start-color:var(--accent)}
.bound h3{font-size:15px;margin:0 0 4px}
.bound .says{font-size:12.5px;color:var(--muted);margin:0 0 10px;
             font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;letter-spacing:.05em}
.bound dl{margin:0}
.bound dt{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;color:var(--body);
          margin-top:10px}
.bound dd{margin:2px 0 0;font-size:13.5px;color:var(--muted);line-height:1.55;
          max-width:78ch}
html.js .fig-src{display:none;position:absolute;z-index:40;inset-inline-start:0;
                 top:calc(100% + 6px);width:min(340px,78vw);
                 border:1px solid var(--line);
                 }
html.js .fig-btn[aria-expanded="true"] + .fig-src{display:block}
@media (hover:hover){
  html.js .fig:hover .fig-src{display:block}
}
/* ── Story so far ───────────────────────────────────────────────────────────
   A rail with a dot per entry, oldest at the top. Rendered only when a story
   has actually moved (>= TIMELINE_MIN updates), so most articles carry none
   of this markup at all. */
.timeline{margin:30px 0 4px;border-top:1px solid var(--line);padding-top:20px}
.timeline h2{font-size:16px;letter-spacing:.3px;display:flex;
        align-items:baseline;gap:9px}
.timeline .tl-n{font-size:12px;font-weight:400;color:var(--muted)}
.timeline ol{list-style:none;padding:0;margin:16px 0 0;
        border-inline-start:2px solid var(--line)}
.timeline li{position:relative;padding:0 0 18px;padding-inline-start:20px;
        display:flex;flex-direction:column;gap:5px}
.timeline li:last-child{padding-bottom:0}
/* The dot sits ON the rail: -5px is half the dot minus the 2px border. */
.timeline li::before{content:"";position:absolute;inset-inline-start:-5px;
        top:5px;
        width:8px;height:8px;border-radius:50%;background:var(--line)}
.timeline li:last-child::before{background:var(--accent)}
.timeline time{font-size:12px;color:var(--muted);
        font-variant-numeric:tabular-nums}
.timeline .badge{align-self:flex-start}
.timeline .tl-title{font-size:14.5px;line-height:1.45;color:var(--body)}
/* WHAT KIND OF UPDATE, AND WHO STANDS BEHIND IT (batch L §7). Two small
   lines under the headline: `first reported · 3 sources`, `filing`, `market
   reaction`, then the outlets themselves. The kind carries the accent
   because it is the new information on this row — the reader is being told
   which entries moved the story — and the outlet list is dim because it is
   attribution. */
.timeline .tl-kind{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:10.5px;letter-spacing:.1em;
      text-transform:uppercase;color:var(--accent);display:block;margin-top:3px}
.timeline .tl-src{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:11.5px;color:var(--muted);
      display:block;margin-top:2px;overflow-wrap:anywhere}
/* ── Share kit ──────────────────────────────────────────────────────────────
   Kai's affordance, not the reader's: the story's three social assets, one
   tap each, hidden until `?admin=1` asks for them. `hidden` in the markup is
   what does the hiding, so a reader with JS off never sees it either — which
   is also why nothing here sets `display` on the paragraph: an author
   `display` would outrank the attribute's UA rule and un-hide the whole row
   on every article page. The links are 44px tall for the same reason the
   share control is. */
.kitlink{margin:16px 0 0;font-size:13px;color:var(--muted)}
.kitlink a{display:inline-flex;align-items:center;min-height:44px;
        margin-inline-start:8px;padding:0 12px;border:1px solid var(--line);
        color:var(--accent);text-decoration:none}
.kitlink a:hover{border-color:var(--accent)}
.back{display:inline-block;margin-top:26px;font-size:14px;text-decoration:none}
/* ── Live-blog ──────────────────────────────────────────────────────────────
   The running log, and the same markup again under "As it happened" once the
   story is closed. Entries are append-only, so the rail grows downward and
   nothing above ever moves. */
.live-head{display:flex;align-items:center;gap:10px;font-size:13px;
        color:var(--muted);letter-spacing:.3px;margin:0 0 4px}
ol.log{list-style:none;padding:0;margin:14px 0 0;
        border-inline-start:2px solid var(--line)}
ol.log>li{position:relative;padding:0 0 20px;padding-inline-start:20px}
ol.log>li:last-child{padding-bottom:0}
ol.log>li::before{content:"";position:absolute;inset-inline-start:-5px;
        top:6px;width:8px;
        height:8px;background:var(--line)}
/* Newest first while live, so the freshest entry is the one on the rail. */
.live ol.log>li:first-child::before{background:var(--flash)}
.log-closed ol.log>li:last-child::before{background:var(--accent)}
ol.log time{display:block;font-size:12px;color:var(--muted);
        font-variant-numeric:tabular-nums;margin-bottom:3px}
ol.log p{margin:0 0 8px;font-size:15.5px;line-height:1.65;color:var(--body)}
.log-closed{margin-top:34px;border-top:1px solid var(--line);padding-top:20px}
.log-closed h2{font-size:16px;letter-spacing:.3px}
/* ── Hack tracker table ─────────────────────────────────────────────────────
   Scrolls inside its own box rather than widening the page: five columns of
   incident data do not fit 375px on any arrangement, and a document that
   scrolls sideways is a worse answer than a table that does. */
.tablewrap{overflow-x:auto;margin-top:18px}
/* ── Raw data beside every table (batch 12.6 B4) ────────────────────────────
   The endpoint is SHOWN, not just linked: a download button gives somebody
   one file, a visible URL tells them the file has an address they can fetch
   on a schedule. `user-select:all` so one click selects the whole thing. */
.dl{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:12px 0 4px;
    font-size:12.5px;color:var(--muted)}
.dl .lab{text-transform:uppercase;letter-spacing:.6px;font-size:11px}
.dl .btn{padding:3px 10px;font-size:12px}
.dl .n{font-variant-numeric:tabular-nums}
.dl .endpoint{user-select:all;background:var(--chip);border:1px solid var(--line);
              padding:3px 8px;font-size:12px;
              overflow-wrap:anywhere}
.dl .apidoc{color:var(--muted);text-decoration:underline}
/* ── /embed/ (batch 12.6 B3) ────────────────────────────────────────────────
   The demo box is padded and bordered so the widget's OWN border is visible
   inside it — a preview flush against the page background hides the one thing
   somebody embedding it needs to see. */
.embed-demo{margin:12px 0;padding:14px;border:1px dashed var(--line);
            background:var(--chip)}
.snippet{margin:0 0 26px;padding:12px 14px;border:1px solid var(--line);
         background:var(--chip);overflow-x:auto}
.snippet code{font:12.5px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;
              color:var(--body);white-space:pre}
table.data{border-collapse:collapse;width:100%;min-width:620px;font-size:14px}
table.data th,table.data td{text-align:left;padding:10px 12px;
      border-bottom:1px solid var(--line);vertical-align:top}
table.data th{font-size:12px;cursor:pointer;white-space:nowrap;
      user-select:none}
table.data th:hover{color:var(--accent)}
table.data th[aria-sort]{color:var(--accent)}
table.data th[aria-sort="ascending"]::after{content:" \2191"}
table.data th[aria-sort="descending"]::after{content:" \2193"}
table.data td.num{text-align:right;font-variant-numeric:tabular-nums;
      white-space:nowrap}
table.data td.unknown{color:var(--muted)}
table.data a{color:var(--accent);text-decoration:none}
table.data a:hover{text-decoration:underline}
.totals{display:flex;gap:26px;flex-wrap:wrap;margin:4px 0 2px}
.totals div{display:flex;flex-direction:column;gap:3px}
.totals .n{font-size:26px;font-weight:500;font-variant-numeric:tabular-nums}
.totals .k{font-size:11px;letter-spacing:1.4px;color:var(--muted);
      font-weight:400;text-transform:uppercase}
.note{color:var(--muted);font-size:13px;line-height:1.6;margin-top:16px}
/* Directly under the three figures on `/calendar/`, because it is the
   sentence that says which zero the third one is (R14 §1). Tighter to the
   figures than `.note` is to the table: an explanation of a number that
   floats away from the number is an explanation a reader joins to the wrong
   thing. */
p.calwatch{color:var(--muted);font-size:12.5px;line-height:1.55;
      margin:10px 0 0;max-width:62ch}
/* ── /chains/ — the liveness board (batch I §6) ───────────────────────────
   The first cell is a mark, a name, a ticker and a reason, and it needs
   every one of those to be a block: an unsized `.mk` span puts an inline
   <svg> at the CSS default of 300x150, which is what the first render of
   this page did — a 300px-tall Bitcoin mark shoving "BitcoinBTC" sideways
   out of its own row. Sized here, once, for every table that carries one. */
td.chain{display:grid;grid-template-columns:auto 1fr;
      column-gap:9px;row-gap:2px;align-items:center}
td.chain .mk{width:17px;height:17px;grid-row:1/3;align-self:center;
      color:var(--ink);display:block}
td.chain .mk svg{width:100%;height:100%;display:block;fill:currentColor}
/* THE MARK NEVER OUTGROWS THE FIGURE BESIDE IT (§5): 17px against the 18px
   line box of a 14px table row is inside the cap height of the mono face. */
td.chain .nm{font-weight:400}
td.chain .sym{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;letter-spacing:.1em;
      color:var(--dim);margin-inline-start:7px}
td.chain small.why{grid-column:2;display:block;color:var(--dim);
      font-size:11.5px;line-height:1.4;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
/* A chain with no mark still lines its name up with the ones that have one. */
td.chain:not(:has(.mk)){grid-template-columns:17px 1fr}
td.chain:not(:has(.mk))>:first-child{grid-column:2}
/* …and so are the board dots (batch J §3c). `/chains/` and the exchange
   board each carried a five-colour dot beside a word that already said the
   same thing, pulsing at the same rate whether anybody had looked in the
   last minute or since Tuesday. Both cells carry an age now. */
table.data span.unknown{color:var(--dim)}
/* ── /calendar/ — four kinds of row, drawn four ways (batch Q6 §1) ────────
   §1's requirement is that a curated date and a contract-read date are not
   distinguishable only by a tooltip, so each class carries THREE independent
   channels: a glyph, a word, and the rule below keyed off the row's own
   class. A tint alone would fail the same test a tooltip does — it is one
   channel, it is invisible in print and it is invisible to anybody who has
   not read the legend. The glyph and the word are on the row itself. */
/* `callegend` and `td.how`, NOT `prov-legend` and `td.prov`. `.prov` is
   already taken by the ARTICLE provenance strip (a bordered flex box, ~370
   lines up), and the first cut of this page used it here — so every "how we
   know" cell rendered inside that strip's border and padding, in a table.
   Caught by looking at the page, which is the only thing that catches it: the
   markup was right, the new rules were right, and nothing was red. */
h2.calhead{font-size:16px;letter-spacing:.3px;margin:30px 0 2px}
ul.callegend{list-style:none;margin:16px 0 6px;padding:0;display:grid;
      gap:8px;grid-template-columns:repeat(auto-fit,minmax(232px,1fr))}
ul.callegend li{border-inline-start:3px solid var(--line);padding:6px 0 6px 11px;
      display:grid;grid-template-columns:auto 1fr;column-gap:8px;
      align-items:baseline}
ul.callegend .pmark{font-size:11px;line-height:1}
ul.callegend .pw{font-size:12.5px;letter-spacing:.4px}
ul.callegend .pd{grid-column:2;font-size:11.5px;line-height:1.45;
      color:var(--dim);font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif}
/* THREE HUES AND A BORDER STYLE, not four hues — and that is a palette rule
   rather than a shortage. This site has three colours with meaning already:
   amber is DEGRADED (batch J §3d) and a hand-typed date is not a fault, and
   green/red are bound to DIRECTION on a figure, which a provenance class is
   not. So `observed` — the one class that is not a prediction at all — is
   marked by a DOUBLED rule instead of a fourth hue. That is a fourth channel
   on top of the glyph and the word, and it survives greyscale, print and a
   reader who cannot tell oxblood from ink. */
.p-read{--pc:var(--accent); --pb:solid}
.p-published{--pc:var(--ink); --pb:solid}
.p-curated{--pc:var(--dim); --pb:dotted}
.p-observed{--pc:var(--accent); --pb:double}
ul.callegend li[class*="p-"]{border-inline-start-color:var(--pc);
      border-inline-start-style:var(--pb)}
ul.callegend .pmark,table.cal .pmark{color:var(--pc)}
/* THE ROW CARRIES ITS OWN CLASS, and the border is on the FIRST CELL rather
   than the <tr>: `border-collapse:collapse` on `table.data` drops a border
   set on a row, which is how the first cut of this page shipped four classes
   that were four identical rows with different invisible markup. */
table.cal tr.ev>td:first-child{border-inline-start:3px var(--pb) var(--pc);
      padding-inline-start:10px}
table.cal td.when time{white-space:nowrap;font-variant-numeric:tabular-nums}
table.cal td.what .nm{font-weight:400}
table.cal small.why{display:block;color:var(--dim);font-size:11.5px;
      line-height:1.4;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;margin-top:2px}
table.cal td.how{white-space:nowrap}
table.cal td.how .pmark{font-size:10px;margin-inline-end:6px}
table.cal td.how .pw{font-size:12.5px}
table.cal td.obs-cell .obs{font-variant-numeric:tabular-nums}
ul.calsrc{list-style:none;margin:10px 0 0;padding:0;font-size:12.5px;
      color:var(--muted)}
ul.calsrc li{padding:4px 0;border-bottom:1px solid var(--line)}
ul.calsrc li.bad{color:var(--dim)}
/* ── Asset hubs, glossary, API docs ─────────────────────────────────────── */
/* §7: the mark is the SUBJECT here, so it is large and in its own colours.
   Everything else on the page stays in the token palette — the mark is a
   quotation INSIDE a page, not a theme for it. The page's own text is
   contrast-checked against the page's own ground and never against the
   mark, which is not part of that assertion and was never meant to be. */
span.assetmark{width:64px;height:64px;flex:0 0 auto;display:block}
span.assetmark svg{width:100%;height:100%;display:block}
.assethead{display:flex;align-items:center;gap:18px;margin:6px 0 4px}
.assetmark{width:56px;height:56px;flex:0 0 auto;opacity:.9}
.assethead h1{font-size:30px;letter-spacing:.5px}
.assetprice{font-size:15px;margin-top:4px;font-variant-numeric:tabular-nums}
.assetprice[hidden]{display:none}
.assetprice .v{color:var(--text);font-weight:500}
.assetprice .c.up{color:var(--up)}
.assetprice .c.down{color:var(--down)}
/* Batch R8. A change figure with no number is neither up nor down. It had
   NO third state: `null >= 0` is true in JavaScript, so a missing figure
   rendered `<span class="c up">+</span>` — a green plus with nothing behind
   it, which is a direction claimed off no data. */
.assetprice .c.none{color:var(--muted)}
ul.terms{list-style:none;padding:0;display:grid;
      grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:2px}
ul.terms li{border-bottom:1px solid var(--line);padding:12px 2px}
ul.terms a{font-size:15.5px;text-decoration:none;font-weight:400;
      display:block}
ul.terms span{display:block;color:var(--muted);font-size:13px;
      line-height:1.5;margin-top:3px}
ul.mentions{margin:10px 0 0 18px}
ul.mentions li{margin:7px 0;font-size:15px;line-height:1.5}
.prose .lede{font-size:17.5px;line-height:1.6;color:var(--text)}
.prose h2{font-size:17px;margin:26px 0 8px}
/* A jargon link is a hint, not a call to action: underlined, in the body
   colour, so a paragraph with three of them still reads as a paragraph. */
a.jargon{color:inherit;text-decoration:underline dotted;
      text-underline-offset:3px;text-decoration-color:var(--accent)}
a.jargon:hover{color:var(--accent)}
pre{background:var(--panel);border:1px solid var(--line);
      padding:14px 16px;overflow-x:auto;font-size:13px;line-height:1.6;
      margin:12px 0}
code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
p code,li code{background:var(--panel);border:1px solid var(--line);
      padding:1px 5px;font-size:.92em}
/* ── Mood page ──────────────────────────────────────────────────────────── */
.dial{display:flex;align-items:center;gap:26px;flex-wrap:wrap;margin:10px 0 6px}
.dial svg{width:210px;height:auto;flex:0 0 auto}
.dial .score{font-size:38px;font-weight:500;line-height:1.1}
.dial .band{font-size:14px;color:var(--muted);letter-spacing:.4px}
.dial .why{font-size:15px;color:var(--body);line-height:1.6;max-width:34ch;
      margin-top:8px}
.chart{margin-top:8px}
.chart svg{width:100%;height:auto;display:block}
.daylist{list-style:none;padding:0;display:flex;flex-direction:column;gap:10px}
.daylist a{font-size:15px;text-decoration:none}
.daylist span{color:var(--muted);font-size:13px}
.prose{max-width:720px}
.prose h1{font-size:26px;margin:6px 0 16px}
.prose p{margin:14px 0;font-size:15.5px;line-height:1.75;color:var(--body)}
.daytitle{font-size:22px;margin:0 0 18px}
.daytitle small{font-size:14px;color:var(--muted);font-weight:400;
        letter-spacing:.2px}

.whales-filters{display:flex;flex-wrap:wrap;gap:.5rem;margin:1rem 0}
.whales-filters input,.whales-filters select{font:inherit;padding:.45rem .6rem;
  border:1px solid var(--rule);background:transparent;
  color:var(--ink);min-height:44px}
/* §5c: the ledger's own controls are targets too. They were 38px, and the
   "stories only" checkbox 13x38 — the sweep measured every one of them. */
.whales-filters .w-only{min-block-size:44px}
.whales-filters #w-carded{inline-size:22px;block-size:22px;min-block-size:22px}
.apidoc{display:inline-flex;align-items:center;min-block-size:44px}
.whales-filters #w-q{flex:1 1 16rem}
.whales-filters .w-only{display:flex;align-items:center;gap:.35rem;
  font-size:14px;color:var(--muted)}
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
table.whales{border-collapse:collapse;width:100%;font-size:14px;
  min-width:44rem}
table.whales th,table.whales td{border-bottom:1px solid var(--line);
  padding:.45rem .6rem;text-align:start;white-space:nowrap}
table.whales th{color:var(--dim)}
table.whales td.num{text-align:end;font-variant-numeric:tabular-nums}
/* A carded row is one the wire told. Marked rather than filtered out: the
   page's claim is completeness, and hiding the difference would make the
   curated half invisible instead of visible. */
table.whales tr.carded td:first-child{border-inline-start:3px solid var(--acc);
  padding-inline-start:calc(.6rem - 3px)}

/* ── A cited row, marked ──────────────────────────────────────────────────
   §3f. 11,270 canonical ids resolve to `/whales/#<key>` and the page carried
   eleven `id=` attributes, all of them nav chrome — so every one of those
   citations landed at the top of the page. Every row now carries its event's
   own key, server-side, and :target marks the one a reader was sent to.

   `:target` and not a script: a citation has to work with scripting off, and
   a JS-only anchor would satisfy a test and fail the reader who arrives with
   a blocked script. `.found` is the same mark applied by the seek
   enhancement for a row further down the ledger. */
table.whales tr:target,table.whales tr.found{background:var(--tint)}
table.whales tr:target td,table.whales tr.found td{
  border-block:1px solid var(--acc)}
table.whales tr:target td:first-child,table.whales tr.found td:first-child{
  border-inline-start:3px solid var(--acc);
  padding-inline-start:calc(.6rem - 3px)}
/* the sticky masthead must not sit on top of the row a citation just landed
   on — this is the whole difference between "it works" and "it looks like it
   did nothing" */
table.whales tr{scroll-margin-block-start:96px}

/* ── The ledger at a narrow width ─────────────────────────────────────────
   §5b. The table STAYS a table: a reader compares down a column, and turning
   each row into a stack of label/value pairs destroys the one thing a ledger
   is for. It scrolls inside its own region instead, and columns drop in a
   documented priority as width falls.

   PRIORITY, least important first: To (6) → From (5) → Type (2). What
   survives to 320px is exactly the four things §3f says a citation has to be
   able to show — the TIME (1), the ASSET or venue (3), the AMOUNT (4) and the
   SOURCE (7, the explorer link). Chain was dropped third in the first draft
   of this rule and measured at 390px: the cited row showed a time, a type, a
   figure and a link, and no way to tell WHAT $10.7M of. That is the column a
   citation cannot lose, so Type goes instead — "Whale move" is recoverable
   from the amount and the explorer, and "bitcoin" is not recoverable from
   anything.

   A dropped column is HIDDEN, not deleted: it stays in the markup, the §0b
   inventory runs at every tested width, and a control that exists at 1440 and
   is absent from the DOM at 390 is a §0b failure like any other. */
@media (max-width:900px){
  table.whales th:nth-child(6),table.whales td:nth-child(6){display:none}
}
@media (max-width:720px){
  table.whales th:nth-child(5),table.whales td:nth-child(5){display:none}
}
@media (max-width:560px){
  table.whales th:nth-child(2),table.whales td:nth-child(2){display:none}
  table.whales{min-width:0}
}
/* The time column is sticky so a row stays identifiable while the rest of it
   scrolls sideways. */
@media (max-width:900px){
  table.whales td:first-child,table.whales th:first-child{
    position:sticky;inset-inline-start:0;background:var(--pap);z-index:1}
  table.whales tr:target td:first-child,
  table.whales tr.found td:first-child{background:var(--tint)}
}
/* A gap is OURS, not a chain's: marked as a different kind of row rather
   than styled like a quiet event. Every rule in this block is served on
   every page of the site (`_chrome` inlines one stylesheet), so it is kept
   short on purpose. */
table.whales tr.gap td{background:color-mix(in srgb, var(--muted) 12%,
  transparent);color:var(--muted);white-space:normal}
table.whales tr.gap .why{opacity:.75}
/* §5: the asset column's mark. THE TABLE STAYS A TABLE — no card-ification
   at any width, because a reader compares down a column and that is what a
   ledger is for — so the mark is an inline prefix inside the existing cell
   and adds no column of its own. 13px against the 14px row is inside the
   cap height of the mono face beside it. */
td.wasset .mk{width:13px;height:13px;display:inline-block;
      vertical-align:-2px;margin-inline-end:6px;color:var(--ink)}
td.wasset .mk svg{width:100%;height:100%;display:block;fill:currentColor}
/* An entity is a LINK to its page (12.5), with a small caret beside it that
   filters the ledger instead — the two things a reader might mean by tapping
   a name, kept apart. An entity with no page written falls back to the
   button, which is what this column was before the pages existed. */
table.whales .ent{font:inherit;color:inherit;background:none;border:0;
  padding:0;cursor:pointer;text-align:start;
  border-bottom:1px dotted var(--line)}
table.whales a.ent{text-decoration:none}
table.whales .ent:hover{color:var(--accent)}
table.whales .entf{font:inherit;color:var(--muted);background:none;border:0;
  cursor:pointer;padding:0 .25rem;line-height:1}
table.whales .entf:hover{color:var(--accent)}
/* What we can compute about a wallet we cannot name. Muted and wrapping,
   because it is context under the row's own answer rather than a second
   column — and `white-space:normal` because the table's default is nowrap
   and a dormancy phrase would otherwise widen every row on the page. */
table.whales .attr{display:block;color:var(--muted);font-size:12px;
  white-space:normal;max-width:22rem}
.small{color:var(--muted);font-size:13px}

.entityhead{display:flex;gap:.75rem;align-items:baseline;flex-wrap:wrap}
.entityhead h1{margin:0}
.etype{font-size:12px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);border:1px solid var(--line);
  padding:.15rem .5rem}
.ejuris{color:var(--muted);font-size:14px}
.eaddr{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;
  word-break:break-all}
/* A block inside the cell, not the cell: `max-width` on a `td` is ignored
   under automatic table layout, so the note widened the table to 3,000px
   rather than wrapping — which looked fine to every check and wrong to
   anybody who opened the page. */
.esig{color:var(--muted);font-size:13px;white-space:normal;
  max-width:34rem;min-width:16rem}
.epending{color:var(--flash);font-size:11px;letter-spacing:.04em;
  text-transform:uppercase;white-space:normal;margin-top:.2rem}
.enote{border-inline-start:3px solid var(--line);padding-inline-start:.75rem;
  color:var(--muted);font-size:14px;margin:1rem 0}
ul.esources{list-style:none;padding:0;margin:.5rem 0;font-size:13px}
ul.esources li{display:inline;margin-inline-end:.75rem}
/* The role a story gave an entity is DATA, not decoration: it is on the
   entity page beside the headline, and never on a card. */
.erole{color:var(--muted);font-size:12px;margin-inline-start:.4rem}
/* Story pages carry their entities as small links, under the same 3-link
   discipline the glossary follows. */
.entrow{margin:.75rem 0 0;font-size:14px;color:var(--muted)}
.entrow a{margin-inline-end:.5rem}

/* ── AN AGE THAT COUNTS, WHERE A DOT USED TO PULSE (batch J §3c) ──────────
   A pulsing dot pulses whether or not the feed is alive. It is decoration
   wearing the costume of a status, and it is the most overused element in a
   machine-built dashboard after the gradient card this design already threw
   out. Every status dot on this site is now a number:

       3s   since last row                      healthy, quiet or busy
       41m  Bitcoin — not watching since 17:32  dark, and unmistakable

   The number only moves when the data moves. If a feed dies the number
   climbs and a reader watches it climb, which is a thing a CSS animation
   cannot fake — that is the whole point.

   IT DELETES A CLASS OF BUG. There is no separate "broken" state to remember
   to render, so a quiet feed and a broken feed cannot look the same. That is
   the 2026-08-17 rule expressed as a graphic instead of as a paragraph.

   §3d: A HEALTHY STATE CARRIES NO ACCENT. The figure sits in --ink and its
   caption in --dim, like every other figure on the site. Amber is reserved
   for the degraded state alone, so amber can mean LOOK HERE. */
.age{display:inline-flex;align-items:baseline;gap:6px;min-width:0}
.age b{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:400;font-size:12px;
      font-variant-numeric:tabular-nums lining-nums;color:var(--ink);
      /* A RESERVED BOX. The value goes 3s → 41m → 2h and a box that resizes
         once a second is the bug the strip's countdown rule exists for. */
      min-width:4ch;text-align:end;font-feature-settings:"tnum"}
.age .what{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:12px;color:var(--dim);
      white-space:nowrap}
/* A CAPTION IS DROPPED, NEVER CUT. Measured at 390: "5h since last row"
   next to LIVE and the threshold line leaves the caption 40px, and an
   ellipsis on "since last r…" is the truncation §2 bans. Same rule a dropped
   table column follows — still in the DOM, still read aloud, just not drawn.
   The figure never goes: it is the reading. */
@media (max-width:520px){.band-head .age .what{
      position:absolute;width:1px;height:1px;overflow:hidden;
      clip-path:inset(50%);white-space:nowrap}}
/* THE DEGRADED STATE, AND ONLY THE DEGRADED STATE, IS AMBER. --live-ink is
   the 4.52:1 value; --live is the 3.33:1 one and may only carry a non-text
   indicator, which after §3c there are none of. */
.age.dark b,.age.dark .what{color:var(--live-ink)}
td .age{gap:5px}
td .age b{font-size:12px}

.crumb{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;letter-spacing:.08em;
      text-transform:uppercase;color:var(--dim);margin:0 0 10px}
.crumb a{color:inherit}
.mkhead{display:flex;gap:16px;align-items:flex-start;margin:0 0 6px}
.mkhead .assetmark{width:44px;height:44px;flex:0 0 auto}
.mkhead h1{margin:0;line-height:1.15}
.mkhead .sym{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.5em;letter-spacing:.08em;
      color:var(--dim);vertical-align:.5em;margin-inline-start:6px}
.mkline{margin:4px 0 2px;font-size:17px}
.mkline .up{color:var(--up)}
.mkline .down{color:var(--down)}
.mkstale{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;letter-spacing:.08em;
      text-transform:uppercase;color:var(--down);margin-inline-start:6px}
.mksec{margin:26px 0 0;padding-top:16px;border-top:1px solid var(--rule2)}
.mksec h2{margin:0 0 4px;font-size:19px}
.mksec .note{margin:0 0 10px}
/* THE CHART. A box that scales with the column and never sets its own
   height in pixels: the viewBox is the proportion and the page is the size. */
.mkchart{display:block;width:100%;height:auto;margin:6px 0 8px;
      font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.mkchart .hl line{stroke:var(--rule2);stroke-width:5;stroke-linecap:round}
.mkchart .cl{fill:none;stroke:var(--ink);stroke-width:1.75;
      stroke-linejoin:round;stroke-linecap:round}
.mkchart .cl.up{stroke:var(--up)}
.mkchart .cl.down{stroke:var(--down)}
.mkchart .dot.up{fill:var(--up)}
.mkchart .dot.down{fill:var(--down)}
.mkchart .rds circle{fill:var(--dim)}
.mkchart .ax{fill:var(--dim);font-size:11px}
.mkchart .ax.now{fill:var(--ink);font-weight:600}
.mkchart .ax.now.up{fill:var(--up)}
.mkchart .ax.now.down{fill:var(--down)}
.mkchart .ax.end{text-anchor:end}
.mkrows{list-style:none;padding:0;margin:0;border-top:1px solid var(--rule2)}
/* THE PLAIN STORY LIST, and it is deliberately NOT `.card` (batch L3 §0).
   The front-page card treatment — the graphic, the tier badge, the source
   count, the standfirst — is `/asset/<sym>/`'s job, and two pages doing one
   job is the thing the boundary exists to stop. A headline and a date is a
   list of stories; it is not a second front page for one instrument. */
.mklist{list-style:none;padding:0;margin:0;border-top:1px solid var(--rule2)}
.mklist li{padding:9px 0;border-bottom:1px solid var(--rule2);
      display:flex;gap:14px;align-items:baseline}
.mklist time{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;letter-spacing:.06em;
      color:var(--dim);flex:0 0 auto;white-space:nowrap}
/* THE CROSS-LINK. One line, one direction, on each of the two page types. */
.mkcross{margin:10px 0 0}
.mkgrid{display:grid;gap:0 18px;grid-template-columns:1fr}
table.markets td.n,table.markets th.n{text-align:end;font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
      font-variant-numeric:tabular-nums}
table.markets .sym{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;
      letter-spacing:.06em;color:var(--dim)}
table.markets td.up{color:var(--up)}
table.markets td.down{color:var(--down)}
@media (max-width:560px){
  .mkhead .assetmark{width:34px;height:34px}
  .mkchart{margin-inline:-4px}
}

/* ── The price rail (§2) and the live band (§3) ────────────────────────────
   Two full-bleed surfaces added around the widget strip, which stays exactly
   where it was:

       masthead + nav
       market rail          §2
       instrument strip     already built, unmoved
       live band            §3
       front-page grid

   A RAIL MAY SCROLL SIDEWAYS AND A STRIP MAY NOT, and the distinction is not
   a preference. A rail is a SEQUENCE — six instruments in a fixed order, and
   a reader who sees BTC, ETH and SOL has read the beginning of it and knows
   there is a rest. A strip is a SET — eight unrelated readings, and one
   hidden behind a swipe is one that has been dropped for every reader who
   does not think to swipe. §5b forbids the carousel for the strip and this
   comment exists so nobody "fixes" the rail into it, or the strip into this. */

/* ── the rail: RETIRED (batch K2 §2) ──────────────────────────────────────
   The front page rendered a section carrying the `rail` class — a server-
   rendered lane of 24 instruments at 17px with a coin mark against each,
   rolling at 60s — and `/c/crypto/` rendered the `tickerbar` element, the
   client-side marquee at 12.5px with no marks, rolling at about 55px a
   second. (Neither element is SPELLED as markup here on purpose: this
   comment ships inside every page of the tree, and more than one check in
   this repo greps the served body for the first occurrence of a tag. The
   trap has fired twice already — see `check_intel`.) Two
   surfaces, two stylesheets, two speeds, both called the rail. The desk saw
   them side by side and ruled that the category page's is the correct one;
   the front page now emits exactly that markup, from the same `_ticker()`
   call, so the two pages are byte-identical rather than merely similar.

   Everything below this comment used to be `.rail .rail-lane .rl-track
   .rl-half .rl .rl .mk .rl b .rl .v .rl .c .rl.stale .rl .stalemark` and
   `@keyframes rlroll`. It is gone with `_rail`, `_rail_cell` and `RAIL_JS`.
   `tools/ui_inventory.py` -> RETIRED carries the record, `tools/rail_probe.py`
   drove a control that no longer exists, and `band.rail`/`band.rail_lane`
   stay in `newsagent/band.py` as the data helpers that build
   `data/ticker.json`, which the bar that replaced this one reads.

   WHAT IT COST, STATED RATHER THAN BURIED: the front page's prices were REAL
   MARKUP ON LOAD and are now fetched by script. A reader with scripting
   blocked had a rail and has a bar that never unhides. That is a real loss
   and it is the desk's call, not an oversight. */

/* ── the band ──────────────────────────────────────────────
   THE BAND HAS NO HEIGHT BUDGET (batch K §0). It had one from I1 §2 until
   this batch, and the budget is retired — with `--band-above`,
   `--band-gap`, `--card-floor`, the donor arithmetic that fed them, and the
   two phone overrides that paid for them.

   WHAT BOUGHT THE CAP WAS REAL AND WHAT IT GREW INTO WAS NOT. I1 §2
   measured a first `<article>` showing 46 of its 388 pixels at 1366x768, and
   capping the band fixed that. What followed was not asked for: a `max-height`
   built from three measured constants, the band head deleted on phones (J4),
   the lead figure deleted on phones (J4), the widget strip cut from eight
   cells to five (J3 §5), and §8's threshold line left with nowhere to live.
   Five surfaces removed from the page to keep one arithmetic true. The desk:
   "if you put Last update: 5 minutes ago, maybe the news bit go down a little
   but so what? when did i restrict that this cant happen." Nobody did. The
   rule was invented here.

   THE CONSTRAINT IS A REPORT NOW, and it makes a weaker promise on purpose:
   the band is ENTERED above the fold at every width — the head and at least
   one whole row are on screen before a reader scrolls — and the lead card's
   top and its headline's bottom are MEASURED and PRINTED at five viewports
   rather than forbidden from moving. A number that moves is then visible in
   a diff instead of being illegal. `selfcheck.check_band_fold` is that
   report; `tools/fold_probe.py` is the instrument that feeds it.

   `min-height:0` STAYS, and it is the only part of I1 §2 that does. A flex
   item's default `min-height:auto` is its content, so a `.band-list` without
   it refuses to shrink and any cap above it is a no-op — which makes it the
   best negative control in this file, and it is still driven as one. The list
   may still give way if something above it ever needs the room. It is simply
   no longer the mechanism that decides what ships. */
.band{border-bottom:1px solid var(--rule);margin:0;
      display:flex;flex-direction:column}
.band-head,.band-down,.band-quiet{flex:0 0 auto}
.band-head{display:flex;align-items:center;gap:12px;
      padding:9px max(20px,env(safe-area-inset-right),
                      calc((100% - var(--wrap)) / 2))
              9px max(20px,env(safe-area-inset-left),
                      calc((100% - var(--wrap)) / 2));
      border-bottom:1px solid var(--rule2)}
/* NOT AMBER (batch J §3d, correcting G §2b, which is mine and was wrong).
   G gave `--live` "the live band, tick indicators, watching / not-watching
   state", which put HEALTHY and DEGRADED in the same colour: the LIVE marker
   and the WATCHERS UNKNOWN notice were both amber, on a site whose standing
   rule is that a quiet feed and a broken feed must look different. It also
   put four accents on one screen — oxblood, amber, green, red.

   Corrected: a healthy state carries NO accent. The word LIVE is furniture
   and sits in --dim; the age beside it is the signal, and the fact that it
   MOVES is what says the wire is alive. A page where nothing is wrong should
   have nothing shouting on it, and amber used that sparingly becomes
   legible — amber means look here. */
.band-head h2{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:12.5px;font-weight:400;
      letter-spacing:.13em;text-transform:uppercase;color:var(--dim);
      display:inline-flex;align-items:center;gap:7px}
/* The wire's last two days as 48 bars (redesign 2026-09). Dim, the current
   hour in the accent; drawn from the rows, bumped by the poll. */
.band-hist{inline-size:96px;block-size:12px;color:var(--dim);
      margin-inline-start:14px;align-self:center;flex:0 0 auto}
.band-hist rect{fill:currentColor}
.band-hist .base{stroke:var(--rule);stroke-width:.7;vector-effect:non-scaling-stroke}
.band-hist .tick{fill:var(--dim);opacity:.7}
.band-hist rect:last-child{fill:var(--acc)}
/* THE THRESHOLD LINE IS RETIRED (batch K §3), not rehomed. `.thr` carried
   "transfers ≥ $25M · liquidations ≥ $1M · moves ≥ 2%" — §8's "the page
   STATES the threshold it used". The desk's call is that the front page does
   not need it, and J4's open item asking where it should live on a phone is
   retired with it. The bars are still one function (`band.thresholds`), still
   the only reader of `band.thresholds` in config, and still substituted into
   BAND_JS, so the filter and the client cannot disagree about what is on the
   page — what is gone is the sentence, not the single source.
   `tools/ui_inventory.py` → RETIRED carries the record.
   THE LAST-UPDATE LINE IS WHAT THE HEAD SAYS INSTEAD (§3): "last update 5m
   ago", one translated template with the counting age substituted into it,
   so the word order is the edition's and not English's. */
.band-head .upd{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:13px;color:var(--dim);
      margin-inline-start:auto;text-align:end;min-width:0;
      display:inline-flex;align-items:baseline;gap:6px;white-space:nowrap}
/* ── THE BAND IS A TAPE (batch L §8) ──────────────────────────────────────
   Five fixed rows became twenty-four in the DOM and four on the screen. The
   fold arithmetic batch K §0 retired does not come back with it and cannot:
   four rows in a box of a fixed, small height is a constant, and the twenty
   behind them cost nothing above it.

   `--bd-row` is the row's own height and `--bd-shown` the count PLUS the
   peek. §8's rule is that a scrolling region must show there is more, and
   half of the fifth row sitting on the bottom edge is the cheapest possible
   way to say so — the same rule §7b set for the strip horizontally, turned
   ninety degrees. `[data-more]` fades that edge with it, and BAND_JS takes
   the fade off at the end of the list, because a permanent one is a lie
   about there being more.

   MEASURED, NOT ASSERTED. `check_band_tape` counts the rows whose boxes are
   fully inside the scroll viewport at 1440 and at 390 — a band row wraps to
   two lines inside 390px often enough that a number derived on paper would
   be wrong there. */
.band-list{list-style:none;padding:0;margin:0;
      --bd-row:42px;--bd-shown:4.5;
      max-height:calc(var(--bd-row) * var(--bd-shown));
      /* Scrolling INSIDE the band is itself a pause (see BAND_JS), which is
         the gesture a reader reaches for before they find a button. */
      overflow-y:auto;overscroll-behavior-y:contain;
      /* `overflow-anchor:none`, and it is REQUIRED rather than tidy. Chrome
         anchors a scrolled box to the content in it, so a row inserted at
         the top pushes `scrollTop` down to keep what you were looking at
         still — which for a reader AT THE TOP means the new row is scrolled
         out of sight the instant it arrives. §8 asks for the opposite: at
         the top, a new row pushes in and STAYS VISIBLE. Off. The
         scrolled-down case does not need it, because a held row is not
         inserted at all. */
      overflow-anchor:none;
      /* The region scrolls from the keyboard, and does so with no script:
         the markup carries `tabindex` and `role="feed"`, and Firefox needs
         the tabindex before a scrollable box will take focus at all. */
      scroll-behavior:smooth;
      /* THE LIST IS STILL WHAT WOULD GIVE WAY, and `min-height:0` is still
         the load-bearing half — a flex item's default `min-height:auto` is
         its content, so without it the list refuses to shrink and any cap
         above it does nothing at all except overflow. There is no cap above
         it as of batch K §0 and there is no second ceiling here either: the
         band holds five rows (§2) and draws all five. This declaration is
         kept because it is the file's best negative control and because the
         day something above the list does need the room, this is what pays
         — not because anything is currently shrinking. */
      flex:1 1 auto;min-height:0}
@media (prefers-reduced-motion:reduce){.band-list{scroll-behavior:auto}}
/* AN ARRIVAL MOVES (redesign 2026-09). A row the poll inserts after the page
   is up slides in from the head and its ground cools from the tint to the
   paper over a second; the rows the render wrote get nothing, because they
   did not arrive — the list is marked `live` only once the poll is running
   and `@starting-style` only fires for a node inserted after that. Off with
   the rest of the motion under `prefers-reduced-motion` (no `.anim`). */
.anim #band-list.live>.bd{transition:opacity .45s var(--ease),
      transform .45s var(--ease),background-color 1.2s var(--ease)}
@starting-style{
  .anim #band-list.live>.bd{opacity:0;transform:translateY(-8px);
      background-color:color-mix(in srgb,var(--live) 18%,var(--pap))}
}
.band-list:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.band-list[data-more="1"]{
      -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 22px),
                                         transparent);
      mask-image:linear-gradient(to bottom,#000 calc(100% - 22px),transparent)}
/* ── "N NEW", AND WHY IT EXISTS (batch L §8) ─────────────────────────────
   A feed that yanks the page while somebody is reading row eleven is worse
   than a feed that does not update. At the top, a new row pushes in and
   stays visible; scrolled down, the reader's position is PRESERVED to the
   pixel and this appears instead. Pressing it returns to the top and
   flushes what was held. A real <button>: keyboard-reachable, announced,
   and it says how many. */
.band-wrap{position:relative}
.band-new{position:absolute;inset-block-start:6px;inset-inline-start:50%;
      transform:translateX(-50%);z-index:3;
      font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:11.5px;letter-spacing:.09em;
      text-transform:uppercase;padding:5px 12px;
      border:1px solid var(--rule);background:var(--tint);color:var(--ink);
      cursor:pointer}
.band-new[hidden]{display:none}
.band-new:hover{border-color:var(--accent);color:var(--accent)}
/* §9: THE TWO STATES ARE VISIBLY DIFFERENT. A row that goes somewhere is
   underlined and takes the accent on hover; a row that does not is plain text
   with no affordance at all, so a reader learns which rows go somewhere by
   the second kind never pretending to.

   KEYED ON `data-dest` AS OF BATCH L1 §2, not on `data-story`. A story is no
   longer the only destination a row can have — an on-chain row goes to its
   row on the ledger — and `data-story` still means exactly what it meant, so
   a row with a story carries both. What must stay true is that this selector
   matches EVERY unlinked row and no linked one, which is what
   `check_band_reads` asserts against the rendered band. */
.bd:not([data-dest]) .tx{cursor:default}
/* ── A BAND ROW IS 17px AND 42px TALL (batch K §1) ────────────────────────
   It was 13.5px on a page whose lead card ran a 126px figure — two type
   systems on one screen, one shouting and one whispering. 17 is the ladder's
   INLINE rung, which is also the rung a figure inside this row takes, so the
   sentence and the number it carries are one size and read as one line.

   42px OF ROW, and the padding is derived rather than chosen: the row is a
   1px rule plus its own line box plus two paddings, and the line box is
   17 x 1.5 = 25.5 inherited from `body`. (42 - 1 - 25.5) / 2 = 7.75. The
   phone's is 46 by the same arithmetic — (46 - 1 - 25.5) / 2 = 9.75 — and
   the FONT does not change with the width: a phone is not a shrunken
   desktop, only its column count and its sentence length differ. Both are
   affordable only because §0 took the cap off. */
.bd{display:grid;grid-template-columns:auto 1fr auto;position:relative;
      align-items:baseline;gap:0 10px;
      padding:7.75px max(20px,env(safe-area-inset-right),
                      calc((100% - var(--wrap)) / 2))
              7.75px max(20px,env(safe-area-inset-left),
                      calc((100% - var(--wrap)) / 2));
      border-bottom:1px solid var(--rule2);font-size:17px;
      /* §2: a band row is a list of readings, not a list of claims. */
      font-weight:400}
.bd:last-child{border-bottom:0}
.bd time{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;color:var(--dim);
      font-variant-numeric:tabular-nums lining-nums;white-space:nowrap}
/* THE FIGURE INSIDE THE SENTENCE IS THE INLINE RUNG, STATED (batch K §1 and
   §7). It is 17 because the row is 17 and a figure set apart from the words
   around it is the 126px-over-13.5px mistake in miniature. Declared rather
   than inherited so `check_type_ladder` has a rule to read: a number-bearing
   selector must name its rung. */
.bd .fig{font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:16px;font-weight:400;
      color:var(--ink);font-variant-numeric:tabular-nums lining-nums;
      white-space:nowrap}
/* THE SPEED CHIP IS GONE (batch J §2d). It was meant to let a reader tell a
   five-second-old liquidation from a forty-minute-old story without reading
   the timestamp — but every row on the live band said SECONDS, so it
   distinguished nothing, and what it actually named was which stream
   produced the row. The time column answers the question it was built for,
   and answers it exactly. */
.bd .tx{min-width:0;overflow-wrap:anywhere;color:var(--ink)}
/* THE REFERENCE CLAUSE (batch L §2). "— the largest hour since Tuesday" is
   the half of the row that makes it news rather than a reading, and it is
   set DOWN rather than up: upright, dimmed, the same weight as the sentence
   it qualifies. `font-style:normal` because `<em>` is italic by default and
   the mono figures beside it have no italic to match. */
.bd .tx em{font-style:normal;color:var(--dim)}
.bd .tx a{color:inherit;text-decoration:none;
      border-bottom:1px solid var(--rule)}
.bd .tx a:hover{color:var(--acc);border-bottom-color:var(--acc)}
/* THE WHOLE ROW IS THE TARGET (batch J §2d). One anchor stretched over the
   row, not a second link and not an onclick: the a11y tree still holds
   exactly one link per row, keyboard focus still lands on the text, and a
   reader with no CSS still gets an ordinary inline link. */
.bd .tx a::after{content:"";position:absolute;inset:0}
.bd .sr{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:13px;color:var(--dim);
      white-space:nowrap;justify-self:end;
      /* above the row overlay, so the attribution stays its own link */
      position:relative;z-index:1}
.bd .sr a{color:var(--dim)}
/* `display:inline-block`, and it is not cosmetic. A <span> is `display:inline`
   by default, and an inline box IGNORES width and height — so the <svg>
   inside fell back to its intrinsic 300x150 and rendered a Bitcoin mark the
   full width of a phone, between the notice and the first row. It works in
   `.rl` only because that is a flex container, which blockifies its children.
   Caught by looking at the 390px screenshot; every gate was green over it,
   because a 300px mark is present, correctly coloured and correctly
   referenced — it is just the wrong size, which nothing was measuring. */
.bd .mk{display:inline-block;width:15px;height:15px;color:var(--ink);
      vertical-align:-2px;margin-inline-end:6px}
.bd .mk svg{width:100%;height:100%;display:block;fill:currentColor}
/* The mark never outgrows the figure beside it: 15px against a 17px line is
   under the cap height of the mono face, which is the §5 rule and applies
   wherever a mark is a label. It tracked the row when the row was 13.5 and
   it tracks it now that batch K §1 made the row 17. */
/* A QUIET BAND AND A BROKEN BAND MUST LOOK DIFFERENT. This is the broken
   one: a named watcher, and since when. On 2026-08-17 the Bitcoin socket was
   dead for hours while Binance liquidations kept arriving, and a tape with no
   row like this looked perfectly alive. */
.band-down{display:block;padding:9px max(20px,calc((100% - var(--wrap)) / 2));
      border-bottom:1px solid var(--rule2);
      background:var(--tint);font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:13px;
      color:var(--live-ink);border-inline-start:3px solid var(--live)}
.band-down b{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-weight:400;letter-spacing:.06em}
.band-quiet{padding:14px max(20px,calc((100% - var(--wrap)) / 2));
      font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:13px;color:var(--dim)}
@media (max-width:620px){
  /* The source column is the least load-bearing end of a band row and it is
     recoverable — every row's text links to the thing itself. Hidden, never
     deleted: the §0b inventory runs at 390 to prove it is still in the DOM.
     UNCHANGED BY BATCH K: §1 makes the row's own type the same size on a
     phone as on a laptop, and §7 puts the source on the right of a row; it
     does not ask for a third column back inside 360px, and reversing J's
     call here is not this batch's. */
  .bd{grid-template-columns:auto 1fr;
      /* 46px of row, derived: (46 - 1 rule - 17 x 1.5 line) / 2 = 9.75.
         THE FONT DOES NOT CHANGE — the `font-size:13px` that used to sit
         here is gone (batch K §1). */
      padding-block:9.75px;
      /* ── AND THE PHONE ROW IS A FIXED TWO LINES (batch L §8) ─────────
         Derived, not chosen: 2 x 25.5 line + 2 x 9.75 padding + 1 rule =
         71.5. Every row on a phone is exactly this tall — a one-line row is
         padded out to it and a longer one is clamped to two lines below —
         so `--bd-shown` is a row COUNT rather than an estimate.

         WHY IT HAD TO BE FIXED. Measured at 390x844 with the batch's own
         rows, three were visible; measured again after §4 made the
         sentences longer, TWO were. The count was tracking the length of
         whatever the market had just done, which is not a layout. A
         one-line row set to 46px would have been deterministic too and it
         is the desk's drawing — `white-space:nowrap;text-overflow:ellipsis`
         at every width — but it clips a headline on the surface that has
         the least room for one, and this repo's standing rule is that a
         line written to be clipped is not a line. Paying 25.5px of height
         to keep the words is the trade. */
      min-height:71.5px;align-content:center}
  .bd .sr{display:none}
  /* THREE ROWS AND HALF OF A FOURTH (batch L §8). 46px is the phone row
     derived seven lines above; the half-row is the cue that the tape
     continues, and it is what absorbs a sentence that wraps.

     MEASURED AT 390x844 ON A REAL RENDER: 3 rows fully inside the box, a
     4th partly in view, 24 in the DOM, and the page does not scroll
     sideways. It is 3 rather than 4 because the phone row is taller and the
     box is shorter, both deliberately.

     AND THE COUNT HAS A FLOOR. A band row wraps here — the source column is
     gone but the sentence is not shortened — so a row is 46px on one line
     and 71.5px on two, and three long rows in a row would show TWO. The
     clamp below is what stops it going lower than that: no row may take
     three lines, whatever arrives in it. A clamped row is the one place on
     this site where text is cut, and it is cut with an ellipsis on a row
     whose full text is one tap away on the story it links to — which is the
     trade the desk's own drawing makes (`.x{white-space:nowrap;overflow:
     hidden;text-overflow:ellipsis}`, at every width) and this takes half of. */
  .band-list{--bd-row:71.5px;--bd-shown:3.5}
  .bd .tx{display:-webkit-box;-webkit-box-orient:vertical;
      -webkit-line-clamp:2;line-clamp:2;overflow:hidden}
  /* WHAT USED TO BE HERE, AND WHY IT IS NOT (batch K §0).
     `.band-list{max-height:210px}` — a second cap under `.band`'s, and the
     one that actually bound at 390x844.
     `.band-head{display:none}` — J4's first donor. The head is back at
     every width; §3 is the whole reason.
     `.band{--band-above:224px;--band-gap:21px;--card-floor:242px;
      max-height:clamp(...)}` — the phone's re-derived budget.
     Its other half, `.card.lead>.top{display:none}` in THEME_CSS, is gone
     too, so a phone gets the lead's picture back. All five were bought by
     one arithmetic and the arithmetic is retired: the band is entered above
     the fold at every width, the lead card's position is measured and
     printed rather than forbidden to move, and `check_band_fold` is that
     report. */
}
@media (prefers-reduced-motion:reduce){
  .bd{animation:none !important}
}

/* ── REDESIGN 2026-09 · SECOND PASS ─────────────────────────────────────
   Kai, on the first pass: "not good enough … most things look like the old
   site … I don't like the coral and blueish dark … more smooth, soothing for
   the eye, nice buttons and icons, news cards look appealing." So every
   surface is re-drawn here, LAST in the cascade on every page: warm paper,
   raised rounded surfaces with a soft tinted shadow, pill controls, one
   teal-green accent, and motion on a single slow curve. The reading order
   of the page is untouched. */
:root{--ease:cubic-bezier(.32,.72,0,1);--r:16px;--r2:11px;
      --shadow:0 1px 2px rgb(0 0 0/.03),0 14px 34px -18px rgb(28 31 29/.22);
      --shadow-up:0 2px 4px rgb(0 0 0/.03),0 26px 52px -22px rgb(28 31 29/.34)}
::selection{background:color-mix(in srgb,var(--acc) 24%,transparent)}
body{font-variant-numeric:lining-nums}
/* header: a frosted bar, the wordmark heavy, links as pills */
header{background:color-mix(in srgb,var(--pap) 84%,transparent);
      -webkit-backdrop-filter:blur(18px) saturate(1.3);backdrop-filter:blur(18px) saturate(1.3);
      border-bottom:1px solid color-mix(in srgb,var(--rule) 70%,transparent)}
@supports not (backdrop-filter:blur(1px)){header{background:var(--pap)}}
header h1{font-size:22px;font-weight:800;letter-spacing:-.045em}
/* No `color` rule for the mark here any more: it had one because the
   four-cell mark was `currentColor`, and the cut b names `--acc` and
   `--pap` itself (MARK_CSS). A `color` here now would set nothing and
   read as the thing that paints the masthead's mark. */
.navlink{font-size:13.5px;font-weight:500;padding:6px 11px;border-radius:999px;line-height:1.2;
      transition:background-color .35s var(--ease),color .35s var(--ease)}
.navlink:hover{background:var(--tint);color:var(--ink)}
.cats a.navlink[aria-current]{background:var(--ink);color:var(--pap)}
.cats a.navlink[aria-current]::after{display:none}
.cats .grp{gap:2px}
.cats .grp+.grp{margin-inline-start:8px;padding-inline-start:8px;border-inline-start:1px solid var(--rule)}
.themebtn,.search .mag,.langpick>summary,.search.open .clear{
      width:36px;height:36px;padding:0;border-radius:999px;background:var(--tint);color:var(--ink);
      border:0;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
      transition:background-color .35s var(--ease),transform .45s var(--ease),color .35s var(--ease)}
.menubtn{border:0;border-radius:999px;background:var(--tint);color:var(--ink)}
.langpick>summary{width:auto;padding:0 12px 0 10px;gap:6px;font-weight:500;font-size:12.5px}
.themebtn:hover,.search .mag:hover,.langpick>summary:hover,.langpick[open]>summary,.search .clear:hover{
      background:var(--rule);color:var(--ink);transform:translateY(-1px)}
.themebtn:active,.search .mag:active{transform:scale(.94)}
.themebtn svg,.shareico{stroke-width:1.5}
.search input{background:var(--tint);border:0;border-radius:999px;height:36px;padding:0 14px;font-size:14px}
.search input:focus{border:0;box-shadow:0 0 0 2px color-mix(in srgb,var(--acc) 40%,transparent)}
.langmenu{border-radius:var(--r2);box-shadow:var(--shadow-up);border:1px solid var(--rule2);
      background:var(--surf);padding:6px}
.langmenu a{border-radius:8px}
.menubtn i{height:1.5px;border-radius:2px}
.navpanel{border-radius:0 0 var(--r) var(--r);box-shadow:var(--shadow-up);background:var(--surf)}
.navpanel a{border-radius:var(--r2)}
/* the grid and the cards: raised, rounded, lifting on hover */
.grid{gap:18px;border-top:0;padding-top:6px}
.card{background:var(--surf);border:1px solid var(--rule2);border-radius:var(--r);
      margin:0;padding:0;min-block-size:0;overflow:hidden;box-shadow:var(--shadow)}
.js .card.in{transition:transform .55s var(--ease),box-shadow .55s var(--ease),
      opacity .45s var(--ease),background-color .3s var(--ease)}
@media (hover:hover){.card:hover{transform:translateY(-3px);box-shadow:var(--shadow-up)}}
.card .top{margin:0;border-radius:0}
.card .pad{padding:14px 16px 18px;gap:8px}
.hd{font-size:18px;font-weight:700;letter-spacing:-.02em;line-height:1.22}
.card p{font-size:13.5px;line-height:1.55}
.meta{font-size:12px;gap:8px}
.badge{font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:10.5px;font-weight:700;letter-spacing:.1em;
      padding:3px 9px;border-radius:999px;background:var(--tint);color:var(--dim);line-height:1.3}
/* THREE TIERS, THREE COLOURS (the desk): flash red and blinking, story in
   the editorial teal, digest in the gold. The red is its own token so the
   two palettes each carry a red that clears 4.5:1 under paper-coloured type. */
:root{--flash:#F06A5F}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){--flash:#C8322A}}
:root[data-theme="light"]{--flash:#C8322A}
.badge.story{background:var(--acc);color:var(--pap)}
.badge.flash{background:var(--flash);color:var(--pap);padding:3px 9px}
.badge.digest{background:var(--live-ink);color:var(--pap)}
.anim .badge.flash{animation:bnblink 1.8s var(--ease) infinite}
@keyframes bnblink{50%{opacity:.45}}
.badge.desk{border:1px solid var(--live);color:var(--live-ink);background:transparent;padding:2px 8px}
.chip-tl,.chip-live{border-radius:999px;background:var(--tint)}
.sharebtn{border-radius:999px}
.sharebtn:hover{background:var(--tint)}
#pill{border-radius:999px;box-shadow:var(--shadow-up);font-weight:600;background:var(--ink);color:var(--pap);
      transition:transform .35s var(--ease),background-color .3s var(--ease)}
#pill:hover{background:var(--acc);transform:translateX(-50%) translateY(-1px)}
.card.fresh{animation:bnfresh 2.6s var(--ease)}
@keyframes bnfresh{from{background-color:color-mix(in srgb,var(--acc) 18%,var(--surf))}}
.more button{background:var(--ink);color:var(--pap);border:0;border-radius:999px;
      padding:12px 24px;font-weight:600;font-size:14px;
      transition:transform .45s var(--ease),background-color .35s var(--ease)}
.more button:hover{background:var(--acc);color:var(--pap);transform:translateY(-1px)}
.more button:active{transform:scale(.97)}
/* the strip: eight raised cells */
/* AND THE THIRD PADDING RULE (batch R49). This one is later than the base
   rule and won above 720, so the strip measured 14px in at 1366 with the
   base rule already set to full bleed — the same three-layer trap R44 §2
   hit on this element's flex-basis, on a different property. Measured, not
   assumed: `padding-left` read 14px at 1366 after the first pass. */
.intel-row{border:0;--pkg:12px;gap:var(--pkg);
      padding-inline:env(safe-area-inset-left,0px)
                     env(safe-area-inset-right,0px)}
.wcell,.wcell:last-child,.wcell:nth-child(4n),.wcell:nth-child(n+5){
      border:1px solid var(--rule2);border-top:1px solid var(--rule2)}
.wcell{background:var(--surf);border-radius:var(--r2);box-shadow:var(--shadow);
      padding:14px 12px;transition:transform .5s var(--ease),box-shadow .5s var(--ease)}
@media (hover:hover){a.wcell:hover{background:var(--surf);transform:translateY(-2px);box-shadow:var(--shadow-up)}}
.wlab{font-size:10.5px;letter-spacing:.14em;font-weight:600;margin-bottom:8px}
.wnum{font-size:24px;font-weight:600;letter-spacing:-.02em}
.wnum s{font-size:12px}
.wsub{font-size:10.5px;letter-spacing:0;margin-top:3px}
.wbar,.wsplit{height:5px;border-radius:999px;overflow:hidden;margin-top:10px;background:var(--rule2)}
.wbar i,.wsplit i{border-radius:999px}
.wspark{block-size:22px;margin-top:8px}
.wspark polyline{stroke-linejoin:round;stroke-linecap:round}
.wdot{width:7px;height:7px}
/* THREE LAYERS SET THIS PROPERTY AND THE LAST ONE WINS — R44 §2 found that
   the hard way, changing only the upper rule and measuring exactly the same
   page. The two rules that used to live here and in the phone block are
   gone: the cell's width is `--w-cell` on the base rule and nothing
   re-declares it, so there is one declaration to read rather than three to
   put in agreement. `check_strip_peek` asserts that. */
/* the live band: a raised list, the time as a pill coloured by stream */
.band{border:0}
.band-head{flex-wrap:wrap;border-bottom:0;padding-block:12px 4px;gap:8px 12px}
.band-head h2{font-weight:700;letter-spacing:.14em;font-size:11px;color:var(--acc)}
.band-head h2::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--acc);
      box-shadow:0 0 0 3px color-mix(in srgb,var(--acc) 22%,transparent)}
.anim .band-head h2::before{animation:bnpulse 2.4s var(--ease) infinite}
@keyframes bnpulse{50%{transform:scale(1.4);opacity:.55}}
.band-list{background:var(--surf);border:1px solid var(--rule2);border-radius:var(--r);
      box-shadow:var(--shadow);overflow-x:hidden;overflow-y:auto;
      margin:0 max(20px,env(safe-area-inset-right),calc((100% - var(--wrap)) / 2))
             0 max(20px,env(safe-area-inset-left),calc((100% - var(--wrap)) / 2))}
/* THE BAND ROW'S SIZE IS A VARIABLE (batch R48). It is declared here rather
   than on `.bd` because the slab's live line is the SAME sentence off the
   same tape and Kai asked for the same size, and it is not inside `.band` —
   so a value only `.bd` could read would have to be copied, and a copy is
   what drifts. `.bd` and `.ls-live-t` both read this and nothing else does.
   Note the `.bd{font-size:17px}` in BAND_CSS is DEAD: this sheet is later,
   so 16/15 is what a reader has always been served. Measured, not read. */
:root{--bd-fs:16px}
.bd{padding:11px 18px;border-bottom:1px solid var(--rule2);font-size:var(--bd-fs);align-items:center;
      transition:background-color .3s var(--ease)}
.bd:hover{background:var(--tint)}
.bd time{font-size:12px;background:var(--tint);border-radius:999px;padding:2px 8px;color:var(--dim)}
.bd[data-stream="seconds"] time{color:var(--live-ink);background:color-mix(in srgb,var(--live) 18%,var(--tint))}
.bd[data-stream="minutes"] time{color:var(--acc);background:color-mix(in srgb,var(--acc) 14%,var(--tint))}
.bd .fig{font-size:16px;font-weight:600}
.band-down,.band-quiet{border-inline-start:0;margin:8px max(20px,env(safe-area-inset-right),calc((100% - var(--wrap)) / 2))
             8px max(20px,env(safe-area-inset-left),calc((100% - var(--wrap)) / 2));
      border-radius:var(--r2);background:color-mix(in srgb,var(--live) 12%,var(--tint));padding:10px 16px}
.band-new{border-radius:999px;background:var(--acc);color:var(--pap);border:0;box-shadow:var(--shadow-up)}
.band-new:hover{color:var(--pap);background:var(--ink)}
/* the price bar on every page that is not the front: a raised pill row
   inside the wrap, its cells reading as they did */
.tickerbar{margin:10px auto 0;max-width:var(--wrap);border:1px solid var(--rule2);
      border-radius:var(--r2);background:var(--surf);box-shadow:var(--shadow)}
/* the article page's share row, citation chip and integrity panel */
.share input{border:0;border-radius:999px;background:var(--tint);padding:10px 14px}
.share button,.share a.btn{border-radius:999px;background:var(--ink);color:var(--pap);
      transition:background-color .35s var(--ease),transform .45s var(--ease)}
.share button:hover,.share a.btn:hover{background:var(--acc);color:var(--pap);transform:translateY(-1px)}
.share a.alt{border-radius:999px;border:1px solid var(--rule2);background:var(--surf)}
.share a.alt:hover{border-color:var(--acc)}
.cite-id code{border-radius:8px;background:var(--tint)}
.cite-id button{border-radius:999px;border:1px solid var(--rule2);background:var(--surf)}
.integ{border:1px solid var(--rule2);border-radius:var(--r);background:var(--surf);box-shadow:var(--shadow)}
.kitlink a{border-radius:999px}

/* the flyout and the footer */
#flyout .inner{border-radius:var(--r) 0 0 var(--r)}
#flyout .close{border-radius:999px;background:var(--tint)}
footer{background:var(--tint);border-top:0;border-radius:var(--r) var(--r) 0 0;
      margin:44px auto 0;max-width:calc(var(--wrap) + 40px);padding:36px 24px 44px;
      font-size:12.5px;line-height:1.8}
footer a{text-decoration:none;border-bottom:1px solid var(--rule);padding-bottom:1px}
footer a:hover{color:var(--acc);border-color:var(--acc)}
/* …except the brand marks, which are not words and take no underline. The
   rule above is written for text links and would draw a line under a 15px
   glyph. */
footer .socs a.soc{border-bottom:0;padding-bottom:0}
/* A PHONE IS NOT A SHRUNKEN DESKTOP AND NOT A LOUDER ONE EITHER (Kai: "the
   fonts are too big on mobile"). Every register steps down one notch. */
@media (max-width:720px){
  header h1{font-size:19px}
  .navpanel a{font-size:15px;min-height:44px}
  .hd{font-size:17px}
  .card p{font-size:13px}
  .card .pad{padding:12px 14px 16px}
  .meta{font-size:11px}
  .grid{gap:14px}
  /* the row steps down one notch on a phone, and the live line steps with
     it because both read `--bd-fs` (batch R48). */
  :root{--bd-fs:15px}
  .bd{padding:10px 14px}
  .bd .tx{-webkit-line-clamp:3;line-clamp:3}
  .bd .fig{font-size:15px}
  .band-head .upd{font-size:12px}
  .wnum{font-size:21px}
  .wlab{font-size:10px}
  .more button{font-size:13px;padding:10px 20px}
  footer{margin-top:32px;font-size:12px}
}
/* ── the menu (the desk: "fix the boring menu/nav bar") ──────────────────
   Desktop: a quiet editorial masthead — the wordmark larger, the links as
   plain words with room between them, the two groups parted by a dot, the
   current page underlined in the accent, the controls round and light.
   Phone: the drawer is a two-column grid of pill chips rather than a column
   of 48px rows, so the whole menu fits above the fold. */
header{padding:10px 24px}
header h1{font-size:24px}
header h1 .mk-byte{inline-size:1.05em;block-size:1.05em;margin-inline-end:.35em}
.cats{background:transparent;border-radius:0;padding:0;gap:0}
.cats .grp{gap:18px}
.cats .grp+.grp{margin-inline-start:18px;padding-inline-start:18px;border-inline-start:0;position:relative}
.cats .grp+.grp::before{content:"";position:absolute;inset-inline-start:-2px;top:50%;width:4px;height:4px;
      margin-top:-2px;border-radius:50%;background:var(--rule)}
.navlink{padding:4px 0;border-radius:0;font-size:13.5px;font-weight:500;letter-spacing:.005em;
      color:var(--dim);position:relative;background:transparent}
.navlink:hover,.cats .navlink:hover,header>a.navlink:hover{background:transparent;color:var(--ink)}
.cats a.navlink[aria-current]{background:transparent;color:var(--ink);box-shadow:none}
.cats a.navlink::after{bottom:-6px;height:2px;background:var(--acc);border-radius:2px}
.cats a.navlink[aria-current]::after{display:block;transform:scaleX(1)}
header>a.navlink{background:transparent;padding:4px 0;margin-inline:10px 6px}
/* DYNAMIC (the desk): a tinted pill GLIDES under whichever link the pointer
   is on, the underline grows in on hover, a press dips the word. */
.cats{overflow:visible;position:relative;isolation:isolate;margin-inline-start:28px}
.nav-glide{position:absolute;top:50%;inset-inline-start:0;width:100px;height:30px;margin-top:-15px;
      border-radius:999px;background:var(--tint);opacity:0;transform-origin:left center;
      transition:transform .4s var(--ease),opacity .25s var(--ease);pointer-events:none;z-index:0}
[dir="rtl"] .nav-glide{transform-origin:right center}
.nav-glide.on{opacity:1}
.cats .navlink,.cats .navmenu{position:relative;z-index:1}
.cats a.navlink::after{transform-origin:center;transition:transform .3s var(--ease)}
.cats a.navlink:hover::after{transform:scaleX(1);background:var(--rule)}
.cats a.navlink[aria-current]:hover::after{background:var(--acc)}
.navlink{transition:color .25s var(--ease),transform .2s var(--ease)}
.navlink:active,.navmenu>summary:active{transform:translateY(1px) scale(.96)}
.navmenu{position:relative;display:inline-block}
.navmenu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:3px}
.navmenu>summary::-webkit-details-marker{display:none}
.navmenu .i-chev{width:12px;height:12px;transition:transform .3s var(--ease);opacity:.7}
.navmenu[open] .i-chev{transform:rotate(180deg)}
.navmenu[open]>summary,.navmenu.here>summary{color:var(--ink)}
.navmenu.here>summary::after{content:"";position:absolute;left:0;right:14px;bottom:-6px;height:2px;
      border-radius:2px;background:var(--acc)}
.navmenu-panel{position:absolute;top:calc(100% + 12px);inset-inline-start:-10px;z-index:12;
      background:var(--surf);border:1px solid var(--rule2);border-radius:var(--r2);
      box-shadow:var(--shadow-up);padding:8px;display:grid;grid-template-columns:1fr 1fr;gap:2px;
      min-width:320px}
.navmenu-panel a.navlink{padding:9px 12px;border-radius:8px;font-size:13.5px;font-weight:500;color:var(--ink);
      text-decoration:none;white-space:nowrap;display:block}
.navmenu-panel a.navlink::after{display:none}
.navmenu-panel a:hover{background:var(--tint);color:var(--acc)}
.navmenu-panel a[aria-current]{background:var(--tint);color:var(--acc)}
.anim .navmenu[open] .navmenu-panel{animation:bnrise .3s var(--ease)}
.navpanel .grp-lab{grid-column:1/-1;font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;letter-spacing:.14em;
      text-transform:uppercase;color:var(--dim);padding:8px 4px 0}
.langpick>summary{background:transparent;border:1px solid var(--rule);height:34px;
      font-size:11px;letter-spacing:.1em;padding:0 12px 0 10px}
.langpick>summary:hover,.langpick[open]>summary{background:var(--tint);transform:none}
.themebtn,.search .mag{width:34px;height:34px;background:transparent;border:1px solid var(--rule)}
.themebtn:hover,.search .mag:hover{background:var(--tint);transform:none}
.navpanel:not([hidden]){display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:12px 14px 14px}
.navpanel .grp{display:contents}
.navpanel .grp+.grp{border:0;margin:0;padding:0}
.navpanel a{min-height:40px;padding:6px 12px;border-radius:999px;background:var(--tint);
      font-size:14px;justify-content:center;text-align:center;color:var(--ink)}
.navpanel a[aria-current]{background:var(--ink);color:var(--pap)}
.navpanel a[aria-current]::before{display:none}
.navpanel .langrow{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:6px;justify-content:center;padding-top:4px}
.navpanel .langrow a{min-height:32px;font-size:12.5px;padding:4px 10px}
/* One account, one row — batch R64 §B2. Full width because the label is a
   sentence ("Follow on Telegram") rather than the one word the category
   pills carry, and a sentence in a half-width pill wraps at 360. */
.navpanel .socrow{grid-column:1/-1;display:flex;align-items:center;
      justify-content:center;gap:8px;min-height:40px;padding:6px 12px;
      border-radius:999px;background:var(--tint);color:var(--ink);
      font-size:14px;text-decoration:none}
.navpanel .socrow:hover{background:var(--ink);color:var(--pap)}
.navpanel .socrow .socg{inline-size:1.1em;block-size:1.1em}

/* Three named states, one row — batch R64 §B1. `grid-column:1/-1` because
   the panel is a two-column grid and this is one control, not two rows. */
.navpanel .themerow{grid-column:1/-1;display:grid;
      grid-template-columns:repeat(3,1fr);gap:6px;padding-top:4px}
.navpanel .themerow button{min-height:40px;padding:6px 8px;border-radius:999px;
      background:var(--tint);border:1px solid transparent;color:var(--ink);
      font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Source Han Sans SC",system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;font-size:12.5px;cursor:pointer}
/* THE ACTIVE ONE, OFF THE ATTRIBUTE AND NOT OFF `aria-pressed`: the panel is
   in the DOM from first paint and `bnThemeSync` cannot run until the body's
   end, so during that gap the only thing that knows the reader's stored
   choice is the attribute THEME_INIT already set. Auto is the ABSENCE of it,
   which is why it needs `:not()` and not a value. */
:root:not([data-theme]) .navpanel .themerow button[data-v="auto"],
:root[data-theme="light"] .navpanel .themerow button[data-v="light"],
:root[data-theme="dark"] .navpanel .themerow button[data-v="dark"]{
      background:var(--ink);color:var(--pap)}
/* ── the price bar: a raised pill row, always moving ─────────────────── */
.tickerbar{margin:0;max-width:none;border:0;border-block:1px solid var(--rule2);
      border-radius:0;background:var(--surf);box-shadow:none;font-size:12px}
.tk-half{gap:8px;padding:6px 10px}
.tk{background:var(--tint);border-radius:999px;padding:4px 10px;gap:6px;align-items:center}
.tk b{font-weight:600;letter-spacing:.06em;font-size:11px}
.tk .c{border-radius:999px;padding:1px 6px;background:color-mix(in srgb,var(--dim) 12%,transparent);font-size:11px}
.tk .c.up{background:color-mix(in srgb,var(--up) 14%,transparent)}
.tk .c.down{background:color-mix(in srgb,var(--down) 14%,transparent)}
.tk-spark{inline-size:40px;block-size:14px}
/* ── scroll to top ───────────────────────────────────────────────────── */
#totop{position:fixed;inset-inline-end:16px;inset-block-end:16px;width:44px;height:44px;
      border-radius:999px;background:var(--ink);color:var(--pap);border:0;cursor:pointer;
      display:inline-flex;align-items:center;justify-content:center;z-index:6;
      box-shadow:var(--shadow-up);opacity:0;transform:translateY(8px);pointer-events:none;
      transition:opacity .35s var(--ease),transform .45s var(--ease),background-color .35s var(--ease)}
#totop svg{width:20px;height:20px}
#totop.on{opacity:1;transform:none;pointer-events:auto}
#totop:hover{background:var(--acc)}
/* ── the phone: no wasted height between the masthead and the bar, the
   band and the strip; cells as tall as their content, not as their row ── */
@media (max-width:720px){
  header{padding-block:6px}
  main{padding-inline:10px}
  .band-list,.band-down,.band-quiet{margin-inline:10px}
  .band-head{padding-inline:12px}
  /* full bleed here too (R49): it was 8px, which is a page edge the strip
     does not share with anything else on it. */
  .intel-row{padding-inline:env(safe-area-inset-left,0px)
                            env(safe-area-inset-right,0px)}
  .grid{gap:12px}
  .tickerbar{margin:0}
  .band-head{padding-block:8px 2px}
  .intel{margin-bottom:10px}
  /* EVERY WIDGET THE SAME HEIGHT (batch R49). `align-items:start` is what
     made a phone's tiles ragged — measured on the live page, nine tiles at
     390 came back 88.8, 130.8 and 140.8px tall, because a tile with no bar
     and no sparkline is a shorter box and `start` lets it be one. The
     desktop grid was uniform only because a grid row stretches its cells;
     the default `stretch` on this flex row is the same rule, said once and
     applying at every width. */
  .wcell{padding:12px 12px}
  .wart svg{height:34px}
  .grid{padding-top:2px}
  .ig-chart{height:96px}
}
/* THE SET A RANKING OPENS ON: the BYTE 100, with the wider set one tap
   away. State is the URL fragment and CSS :target — no script, no store;
   the anchors precede the control and the blocks because ~ looks forward */
.setw{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}
.setw>i,.prose>i[id^="set-"]{display:block;height:0;margin:0;overflow:hidden}
.setpick{display:flex;gap:6px;margin:0 0 8px;padding:0;list-style:none}
.prose .setpick{margin:12px 0 4px}
.setpick a{display:inline-block;font-family:'BN Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;font-weight:500;line-height:1;
      padding:6px 10px;border-radius:999px;border:1px solid var(--rule);color:var(--dim);
      text-decoration:none;transition:background .35s var(--ease),color .35s var(--ease),border-color .35s var(--ease)}
.setpick a:hover{color:var(--ink);border-color:var(--dim)}
.setpick a.s100,#set-500:target~.setpick a.s500,#set-all:target~.setpick a.sall{
      background:var(--acc);border-color:var(--acc);color:var(--pap)}
#set-500:target~.setpick a.s100,#set-all:target~.setpick a.s100{
      background:transparent;border-color:var(--rule);color:var(--dim)}
.setw .mv[data-set="500"]{display:none}
#set-500:target~.mv[data-set="500"]{display:grid}
#set-500:target~.mv[data-set="100"]{display:none}
.prose tr.o,.prose .note.sall{display:none}
#set-all:target~.tw tr.o{display:table-row}
#set-all:target~.note.sall{display:block}
#set-all:target~.note.s100{display:none}
