/* system.css: THE SHARED PRIMITIVES. Five objects, one prefix, no keyframes.
 * ===========================================================================
 * This sheet exists because four sections are being rebuilt at once and the
 * last time that happened each one invented its own stat, its own table head
 * and its own way of saying "we did not measure this". Measured on the live
 * page 2026-09-05, before any of this: Islands renders FOURTEEN font sizes,
 * Premium seven, Today nine, and the two <table>s on the site disagree on
 * every property of a column header (10.5px/500/0.945em tracking against
 * 13px/400/normal). That is not four styles of one thing, it is four claims.
 *
 * PAYOFFS IS THE BASELINE, NOT A BLANK PAGE. Everything below is generalised
 * from payoff.css, which renders SIX sizes and spends TWO hues, and which the
 * owner called the good one. Where a rule here restates one of its decisions,
 * the decision is cited rather than re-derived.
 *
 * ── WHAT THIS SHEET IS FOR, IN ONE SENTENCE ───────────────────────────────
 * A figure on this site carries its population, its window and what it
 * excludes. Every reference dashboard the owner named is a TRADER dashboard
 * where a KPI is a big number and nothing else; this audience is the eleven
 * venues and their investors, and to them a number with no population is not
 * impressive, it is unsourced. So provenance is not a caveat bolted under a
 * card here. It is a REQUIRED SLOT of the card, and .ds-stat draws a loud
 * failure when it is missing, because a rule four agents can forget is a rule
 * that will be forgotten.
 *
 * ══ WHAT THIS SHEET IMPORTS: NOTHING. ═════════════════════════════════════
 * Same reason payoff.css imports nothing. A second design system scoped under
 * a class is not contained: instrument.css:343 pins six properties with
 * !important on `body *:not(svg):not(svg *)`, an important declaration beats a
 * normal one at ANY specificity, and @keyframes names are document-global so a
 * class prefix cannot scope them. Everything below is built from the app's own
 * tokens and THIS FILE DECLARES NO @keyframes AT ALL.
 *
 * ══ THE TWO BLANKETS, BOTH MEASURED IN A BROWSER ══════════════════════════
 *   instrument.css:343   font-size --t-sm / font-weight 400 / letter-spacing
 *                        normal / border-radius 0 / box-shadow none, all
 *                        !important, on `body *:not(svg):not(svg *)`.
 *   instrument.css:1883  font-family: var(--sans) !important, SAME selector,
 *                        LATER in the file.
 *
 * THE SECOND ONE IS THE TRAP AND IT IS NOT WRITTEN DOWN ANYWHERE ELSE.
 * payoff.css's header says 343 pins --mono. That is true at 343 and false by
 * 1883: the resolved default face on the live page is Inter, not the mono
 * stack. Measured, not read. So a figure that wants tabular digits must say
 * `font-family: var(--mono) !important` for itself. Every figure rule below
 * does.
 *
 * MEASURED, AND IT CHANGES WHAT THE BLANKET MEANS: an author !important beats
 * both blankets on every one of the six properties, border-radius and
 * box-shadow included. `border-radius: 8px !important` renders 8px.
 * SO THE BLANKET IS A DEFAULT, NOT A WALL, and "no radius, no shadow" is a
 * HOUSE RULE that four agents each have to keep on purpose. The one thing
 * !important is licensed for below is claiming a TYPE ROLE: a size that is
 * already on the scale, a face, a weight, a tracking. It is never used here to
 * put a radius or a shadow back.
 *
 * ══ THE SCALE. FIVE STEPS, AND ONE OF THEM IS A DUPLICATE ═════════════════
 * Measured with an !important probe, because a normal probe measures the
 * blanket and reports 15px for everything:
 *
 *   --t-xs      11px   every label, every unit, every provenance line
 *   --t-dense   13px   table bodies, and nothing else
 *   --t-sm      15px   body and prose. THE DEFAULT.
 *   --t-md      15px   IDENTICAL TO --t-sm. controls.css:664 re-declares it
 *                      over instrument.css:3092's 16px, on the argument that
 *                      two sizes a reader cannot tell apart are noise. Both
 *                      names are live; they are ONE step. Do not build a
 *                      hierarchy on the difference, there is none.
 *   --t-lg      30px   a stat figure
 *   --t-hero    76px   at 1400. The ONE figure a section leads with.
 *
 * THERE IS NO STEP BETWEEN 15 AND 30, and that is load bearing rather than a
 * gap to fill. A stat has exactly two sizes here. Wanting a third is wanting a
 * fourth, and thirteen is where this app was in July. If a grid of small KPI
 * tiles needs 20px, the answer is fewer tiles, not a new size.
 *
 * ══ COLOUR. ONE ACCENT, TWO DIRECTIONS, THREE INKS ════════════════════════
 * Measured on the live page (data-theme="dark" is the only theme index.html
 * sets, inline at index.html:100):
 *
 *   --accent  #ff7a1a  THE LIVE THING YOU ARE MOVING, and nothing else: a
 *                      slider thumb, the selected step, the row you are on.
 *                      Never a badge, never a heading, never a "good" number.
 *   --neg     #e06e76  EXPOSURE and direction down.
 *   --pos     #5aae87  direction up. A gain is NOT automatically --pos: the
 *                      app's own heroes (.vp-num-n, .mn-hero-n, .pf-live-n)
 *                      all draw the display figure in --ink and reserve hue
 *                      for STATE. A hero that turns green when it is up is a
 *                      section's invention.
 *   --ink / --ink2 / --ink3   everything else, in three steps.
 *
 * FOUR TOKENS THAT LOOK USABLE AND ARE NOT, each measured rather than assumed:
 *   --accent-ink  resolves to #ff7a1a, THE SAME VALUE AS --accent. Setting it
 *                 as the colour on an accent fill prints the accent on the
 *                 accent and the element renders as a blank blob. Anything
 *                 sitting ON an accent fill takes --bg.
 *   --fill-1      `transparent`.
 *   --fill-2      `transparent`. Painting a hover with either means there is
 *                 no hover. --hover-wash is the token that carries one.
 *   --gold-wash   rgba(240,185,11,...). Binance gold, still resolving, and it
 *   --gold-line   belongs to the palette §63 removed for being borrowed.
 *                 Nothing in this sheet or downstream of it may spend them.
 *
 * ══ WHERE THIS FILE LOADS, AND WHY THAT SLOT ══════════════════════════════
 * index.html, after learn.css and BEFORE islands/premium/vaults/payoff.
 *   AFTER  instrument.css and controls.css, so it wins their order ties.
 *   BEFORE the section sheets, so a section can still overrule its own corner.
 *   BEFORE gap.css, which is last on purpose and MUST stay last.
 *
 * ══ WHAT THIS FILE DOES NOT OWN, AND MUST NEVER REDECLARE ═════════════════
 * gap.css owns the INLINE absence vocabulary for the whole app and loads after
 * everything: .mn-gap (theirs, solid rule), .mn-gap-ours (ours, dashed rule),
 * .mn-note (a method note), .mn-i (the tap mark that opens it), .mn-once,
 * .mn-zero (a MEASURED zero, which is not an absence), .mn-key, .mn-kick.
 * .ds-zero below is the BLOCK form and it composes with those; it does not
 * restyle a single one of them. Put an .mn-gap-ours inside a .ds-zero and
 * gap.css wins the span, which is the correct outcome.
 *
 * NO ADDRESS TREATMENT IS PROVIDED. Identity on this product is opt-in and
 * signed (claims.py). There is no .ds- class for rendering an address and one
 * must not be added here, because a shared primitive is an invitation.
 * ═══════════════════════════════════════════════════════════════════════════ */


/* ══ 0. THE GRID THE CARDS SIT IN ══════════════════════════════════════════
 * 1px gaps over a --line ground, which is the app's own way of dividing
 * without a second border: .pf-b3 and .pf-card both already do it, and it is
 * the only card division on this site that survives box-shadow being pinned
 * to none. The column count is a custom property so a section sets it in one
 * place and the media query below does not have to know about it.
 * ─────────────────────────────────────────────────────────────────────────── */
.ds-grid {
  display: grid;
  grid-template-columns: repeat(var(--ds-cols, 3), minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
/* A grid that is one row of the page rather than a panel inside one: hairlines
   top and bottom, none at the sides, exactly as .pf-b3 is drawn. */
.ds-grid[data-edge="strip"] { border-left: 0; border-right: 0; }
.ds-grid > * { background: var(--bg); min-width: 0; }


/* ══ 1. THE STAT CARD. A FIGURE CANNOT BE PLACED WITHOUT ITS POPULATION ════
 *
 * <article class="ds-stat">
 *   <p class="ds-stat-pop">3 of 11 venues <b>24h to 09:12 UTC</b></p>
 *   <p class="ds-stat-n">$269.1K</p>
 *   <p class="ds-stat-u">of premium we observed</p>
 * </article>
 *
 * THE ORDER IS THE ARGUMENT AND IT IS NOT THE REFERENCE DASHBOARDS' ORDER.
 * Every trader dashboard puts the label above and the figure below, and the
 * label is a NAME: "Volume", "PnL". Here the top line is not a name, it is the
 * POPULATION AND THE WINDOW, and it is read first because on this product the
 * question "of what, over what" comes before the quantity. .pf-live already
 * does this: "AT A SPOT OF $84.61" sits above $1.47 and "PER CONTRACT, AT
 * EXPIRY" sits under it. This is that shape, with the top slot made mandatory.
 *
 * ── THE ENFORCEMENT IS REAL, NOT A COMMENT ────────────────────────────────
 * CSS cannot validate markup, but :has() can notice a missing child, and a
 * card that is missing its population renders with a red rule and the words
 * POPULATION AND WINDOW MISSING across it. That is deliberately impossible to
 * ship past a screenshot, which is how this codebase verifies UI. A rule that
 * only lives in a document is a rule four agents will each forget once.
 * ─────────────────────────────────────────────────────────────────────────── */
.ds-stat {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 14px 16px 16px;
  min-width: 0;
}

/* THE POPULATION AND THE WINDOW. One line, two ranks: the population in --ink2
   because it is the subject, the window in --ink3 inside the <b> because it is
   the qualifier. font-weight is restated at 400 because the <b> is a semantic
   tag here and not a bold one, and because the blanket's 400 loses to the
   user-agent's bold on a <b> at equal importance. */
html body .ds-stat-pop {
  margin: 0;
  color: var(--ink2);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
  line-height: 1.35;
}
html body .ds-stat-pop b {
  color: var(--ink3);
  font-weight: 400 !important;
  font-variant-numeric: tabular-nums;
}
/* The window separated from the population by a middot rather than a comma:
   they are two different facts and a comma reads them as one list. */
html body .ds-stat-pop b::before { content: "\00b7\00a0"; color: var(--line2); }

/* THE FIGURE. Two sizes and no third, per the scale note in the header.
   --t-lg is the default because most stats are not the one a section leads
   with, and a page of heroes has no hierarchy at all.
   font-family IS SET EXPLICITLY AND WITH !important. instrument.css:1883 puts
   --sans on every element; without this line the digits are proportional and
   a column of figures does not align. */
html body .ds-stat-n {
  margin: 0;
  color: var(--ink);
  font-family: var(--mono) !important;
  font-size: var(--t-lg) !important;
  font-weight: 600 !important;
  line-height: 1;
  letter-spacing: -0.02em !important;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* THE ONE FIGURE A SECTION LEADS WITH. Drawn exactly as detail.css draws
   .vp-num-n and payoff.css draws .pf-live-n: 0.92 leading so a 76px number
   does not reserve 100px of line box, and -0.045em so the digits sit as one
   shape. ONE PER SECTION. Two heroes is no hero. */
html body .ds-stat[data-rank="hero"] .ds-stat-n {
  font-size: var(--t-hero, clamp(48px, 6.6vw, 76px)) !important;
  line-height: 0.92;
  letter-spacing: -0.045em !important;
}
/* STATE, NEVER SIGN. --neg is for exposure and direction-down, --pos for
   direction-up, and neither is applied automatically: a gain is ink here, the
   same way every other hero in this app draws one. These two classes exist so
   a section can say direction WHEN DIRECTION IS THE READING, and direction on
   this product comes from side_of() in app/backend/settled.py, never from a
   raw `side` column. If your figure's sign came from anywhere else, do not
   colour it. */
html body .ds-stat-n.ds-dn { color: var(--neg); }
html body .ds-stat-n.ds-up { color: var(--pos); }
/* A WORD AT FIGURE SIZE IS A WORD. "UNBOUNDED", "NOT PUBLISHED", "NO CEILING":
   lighter and tracked open so it can never be scanned as a quantity that
   happens to be spelled out. Lifted verbatim from .pf-b-none. */
html body .ds-stat-n.ds-word {
  font-weight: 400 !important;
  letter-spacing: 0.04em !important;
}

/* THE UNIT, AND WHAT IT IS AND IS NOT. It says what the figure counts and on
   what basis. It is NOT the place for the population, which is the slot above
   and is required. "of premium we observed" belongs here. "3 of 11 venues"
   does not. */
html body .ds-stat-u {
  margin: 0;
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
  line-height: 1.4;
}
/* One sentence of context under the unit, at body size, for the stat that
   needs a clause rather than a label. Premium's "$37.0M of it changed hands."
   is this. */
html body .ds-stat-say {
  margin: 2px 0 0;
  color: var(--ink2);
  line-height: 1.5;
  font-size: var(--t-sm) !important;
  max-width: 48ch;
}

/* ── THE MISSING-POPULATION FAILURE ────────────────────────────────────────
   Loud on purpose and in --neg, which is the app's exposure hue: shipping an
   unsourced figure IS the exposure on this product. It is drawn with an
   outline rather than a box-shadow because instrument.css pins box-shadow to
   none, and the offset is negative so the 1px grid gaps do not shift.
   The pseudo-element sets its own font-size: a ::before inherits the
   originating element's computed size, which the blanket has already pinned
   to 15px, and it needs to be a label rather than a sentence. */
html body .ds-stat:not(:has(.ds-stat-pop)) {
  outline: 2px solid var(--neg);
  outline-offset: -2px;
}
html body .ds-stat:not(:has(.ds-stat-pop))::before {
  content: "POPULATION AND WINDOW MISSING";
  display: block;
  color: var(--neg);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  line-height: 1.35;
}
html body .ds-stat:not(:has(.ds-stat-pop)) .ds-stat-n { color: var(--neg); }


/* ══ 2. THE SECTION HEADER RHYTHM ══════════════════════════════════════════
 * TWO OF THE THREE LINES ALREADY EXIST AND ARE NOT REDECLARED HERE.
 * instrument.css:4651 draws `html body .tl-sec .tl-sec-h` as the eyebrow
 * (--t-xs, 600, uppercase, --tr-label, --ink, margin 0 0 10px) and
 * instrument.css:4682 draws `.tl-sec-s` as the sub (--t-sm, --ink2, 1.55,
 * margin 0 0 20px, 78ch). Seventeen call sites already use the first and
 * twelve the second. Restyling either from here would fork a rhythm that is
 * already consistent, so this sheet leaves both alone and adds ONLY the line
 * that does not exist.
 *
 * <header class="ds-head">
 *   <h3 class="tl-sec-h">Premium</h3>
 *   <p class="tl-sec-s">How much premium traded, and who wrote it.</p>
 *   <p class="ds-prov">
 *     <span>Population<b>3 of 11 venues that publish fills</b></span>
 *     <span>Window<b>36 days to 5 Sep</b></span>
 *     <span>Excludes<b>8 venues we do not read</b></span>
 *   </p>
 * </header>
 *
 * ── WHY THE THIRD LINE IS A PRIMITIVE AND NOT PROSE ───────────────────────
 * Today states its coverage as a sentence 780px BELOW the 76px figure it
 * qualifies, at 11px, at the bottom of the card. Measured on the render: the
 * hero and its population are the two ends of the section. Islands states it
 * at the top and in the right place. Premium states it in three different
 * shapes on three adjacent stats. Nobody was wrong on purpose; there was no
 * object to put it in. This is the object. Three attributes, one line, always
 * the same place, always directly under the sub.
 *
 * ANY OF THE THREE MAY BE OMITTED WHEN IT GENUINELY DOES NOT APPLY, and
 * "Excludes" is the one that usually does apply and usually gets dropped.
 * ─────────────────────────────────────────────────────────────────────────── */
.ds-head { margin: 0 0 4px; }

html body .ds-prov {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 24px;
  margin: -8px 0 20px;          /* pulls up under .tl-sec-s's 20px bottom */
  padding: 9px 0 0;
  border-top: 1px solid var(--line);
  font-size: var(--t-xs) !important;
  line-height: 1.4;
}
html body .ds-prov > span {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  min-width: 0;
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
}
/* The VALUE is the thing that is read; the key is furniture. So the value is a
   step up in ink and drops the uppercase, because a population is a phrase and
   uppercase phrases are scanned as labels and skipped. */
html body .ds-prov > span > b {
  color: var(--ink2);
  font-weight: 400 !important;
  text-transform: none;
  letter-spacing: normal !important;
  font-variant-numeric: tabular-nums;
}
/* THE ONE THAT MUST NOT BE QUIET. What a figure excludes is the fact a reader
   would not guess, so it is the one key drawn in full ink. It is still not
   the accent: the accent means "the live thing you are moving" and a static
   caveat is not that. */
html body .ds-prov > span[data-k="excludes"] { color: var(--ink2); }
html body .ds-prov > span[data-k="excludes"] > b { color: var(--ink); }


/* ══ 3. THE TABLE TREATMENT ════════════════════════════════════════════════
 * MEASURED DISAGREEMENT, 2026-09-05, the two <table>s the site renders:
 *   .isl-x         th 10.5px / 500 / uppercase / 0.945px track / pad 0 12 7 0
 *   .pm-sr-table   th 13px   / 400 / uppercase / normal track  / pad 9 10 8
 * Same job, no property in common. Both resolved to the line below.
 *
 * <table class="ds-t">
 *   <thead><tr>
 *     <th>Venue</th>
 *     <th class="ds-n">Premium<i>usd, 24h</i></th>
 *   </tr></thead>
 *   <tbody><tr>
 *     <td>Derive</td><td class="ds-n">$779.8K</td>
 *   </tr></tbody>
 * </table>
 *
 * ── THE ONE RULE THAT IS NOT COSMETIC: <i> IN THE HEADER ──────────────────
 * A column of figures has a unit and a window, and in every table on this site
 * today they live in a footnote or nowhere. .ds-t th > i is the column's own
 * population slot, in the header, where it is read with the column rather than
 * after it. It is the .ds-stat-pop rule applied to a column instead of a card,
 * and it is the reason a table is allowed to carry figures here at all.
 * ─────────────────────────────────────────────────────────────────────────── */
/* ── FOUR THINGS style.css DOES TO EVERY <table> ON THIS SITE ──────────────
 * MEASURED, and every one of them arrived in this table uninvited before these
 * rules existed. style.css declares them on BARE ELEMENT SELECTORS, so they
 * reach any markup a section writes without that section importing anything:
 *
 *   style.css:685  th { text-align: right; position: sticky; top: -1px;
 *                       background: linear-gradient(180deg,--s3,--s2);
 *                       font-weight: 700; padding: 8px 13px }
 *   style.css:692  th:first-child, td:first-child { text-align: left }
 *   style.css:693  td { text-align: right; padding: 6px 13px }
 *   style.css:695  tbody tr:hover { background: var(--glass) }
 *
 * SO A BARE <td> IN THIS APP IS RIGHT ALIGNED AND THE FIRST ONE IS NOT, WHICH
 * IS THE OPPOSITE OF WHAT ANYONE WRITING A TABLE WILL ASSUME. islands.css:458
 * found the same thing and fixed it for one table; every property is
 * neutralised here instead, in both directions, so a section never meets it.
 * Alignment is stated for left AND right rather than only the one that is
 * currently wrong, so the intent survives someone changing the default.
 * ─────────────────────────────────────────────────────────────────────────── */
html body .ds-t {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
html body .ds-t th {
  text-align: left;
  vertical-align: bottom;
  /* NOT sticky by default and NO gradient: style.css:685 makes every th on the
     site sticky with an --s3/--s2 wash, which rendered as a pale band behind
     this header row on a --bg panel. Sticky is opt-in below. */
  position: static;
  background: none;
  padding: 0 14px 8px 0;
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
  font-weight: 600 !important;
  border-bottom: 1px solid var(--line2);
  white-space: nowrap;
}
/* The unit and window under the column name. Its own line, lower ink, no
   uppercase: it is a phrase, not a second label. */
html body .ds-t th > i {
  display: block;
  margin-top: 3px;
  color: var(--ink3);
  font-style: normal;
  font-weight: 400 !important;
  text-transform: none;
  letter-spacing: normal !important;
  font-size: var(--t-xs) !important;
  opacity: 0.8;
}
/* WORDS LEFT. Stated because style.css:693 says right and style.css:692 makes
   the first cell the exception, so without this line a five column table has
   one left column and four right ones and nobody wrote that. */
html body .ds-t td,
html body .ds-t td:first-child,
html body .ds-t th:first-child { text-align: left; }
html body .ds-t td {
  padding: 8px 14px 8px 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink2);
  /* --t-dense, and it is the RIGHT size rather than a compromise: controls.css
     already exempts every dense surface on this site to 13px, and a table is
     the dense surface that exemption was written for. */
  font-size: var(--t-dense) !important;
  line-height: 1.45;
}
/* Words left, FIGURES RIGHT AND IN MONO. This is the column rule the app does
   not have: .pm-sr-table renders its fourteen numeric columns left aligned in
   Inter, so no two rows line up and the eye cannot run a column. The face has
   to be claimed with !important because of the 1883 blanket. */
html body .ds-t th.ds-n,
html body .ds-t td.ds-n,
html body .ds-t th.ds-n:first-child,
html body .ds-t td.ds-n:first-child {
  text-align: right;
  font-family: var(--mono) !important;
  font-variant-numeric: tabular-nums;
}
/* THE HEADER AND ITS COLUMN MUST CARRY .ds-n TOGETHER OR THEY DISAGREE, and a
   header sitting over a column it is not aligned with is the single most
   common table bug in this app. There is no way for CSS to infer it, so it is
   the one thing DESIGN-SYSTEM.md asks you to check on the screenshot. */
html body .ds-t td.ds-n { color: var(--ink); }
html body .ds-t tbody tr:last-child td { border-bottom: 0; }
/* --fill-1 and --fill-2 are BOTH `transparent` on this theme, so a hover
   painted with either does not exist. --hover-wash is the one that carries.
   The row's own background is cleared first: style.css:695 hovers every
   `tbody tr` on the site with var(--glass), which is a token from the lit
   glass pass and washes this row a different colour underneath ours. */
html body .ds-t tbody tr:hover { background: none; }
html body .ds-t tbody tr:hover td { background: var(--hover-wash); }
/* THE ROW YOU ARE ON takes the accent, because that is the accent's one job on
   this site: the live thing you are moving. An outline and not a shadow, and
   inset so it cannot shift the row. */
html body .ds-t tbody tr[data-on="1"] td {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--ink);
}
html body .ds-t tbody tr[data-on="1"] td:first-child {
  box-shadow: none;
  border-left: 2px solid var(--accent);
  padding-left: 8px;
}
/* A HEAD THAT SURVIVES SCROLLING. --bg and not transparent: a sticky head over
   a transparent ground paints the rows through itself. */
html body .ds-t[data-stick="1"] thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--bg);
}
/* A table wider than its panel scrolls INSIDE ITSELF and never pushes the page
   sideways. The rule is on the wrapper because a scrolling <table> loses its
   sticky head. */
.ds-tw { overflow-x: auto; max-width: 100%; }


/* ══ 4. THE RANGE, WHICH IS A WINDOW CONTROL AND NOT A SETTING ═════════════
 * Generalised from .pf-rail, the only range input on the site today, and every
 * geometry number below was settled on that section's rendered PNGs.
 *
 * <div class="ds-range">
 *   <div class="ds-range-hd">
 *     <span class="ds-range-l">Window</span>
 *     <output class="ds-range-v">7 days to 5 Sep &middot; 412 fills</output>
 *   </div>
 *   <input type="range" class="ds-range-i" style="--ds-fill:62%">
 *   <div class="ds-range-ax"><span>1 Aug</span><span>5 Sep</span></div>
 * </div>
 *
 * ── .ds-range-v IS NOT A READOUT, IT IS THE POPULATION ────────────────────
 * THIS IS THE WHOLE EPISTEMIC PROBLEM WITH SLIDERS ON THIS PRODUCT AND IT HAS
 * ONE ANSWER. Dragging a window changes what every linked figure covers. A
 * trader dashboard can leave that implicit because its reader chose the window
 * a second ago and remembers. A screenshot of this page does not, and a
 * screenshot is how this site travels. So the control STATES THE WINDOW IT HAS
 * PRODUCED, in words, in the same object, and the linked .ds-stat-pop lines
 * must be rewritten by the same handler that moves the thumb. A slider that
 * silently changes a denominator is the concentration bug with a handle on it.
 *
 * IF YOUR SLIDER CANNOT NAME ITS POPULATION, IT IS NOT READY TO SHIP.
 *
 * ── THREE THINGS MEASURED ON payoff.css's PNGs AND CARRIED OVER ───────────
 * 1. THE THUMB CAN BE ROUND. instrument.css pins border-radius: 0 on
 *    `body *`, and `body *` matches ELEMENTS, never ::-webkit-slider-thumb.
 *    Verified there on a render and again on this sheet's own.
 * 2. THE TRACK IS TWO GREYS, NOT THE ACCENT. Painted accent, the filled half
 *    is a second full-width orange horizontal line under whatever the section
 *    already drew in orange, and two identical marks (one measured, one
 *    furniture) is the confusion this product cannot afford. Accent goes on
 *    the thumb and nothing else.
 * 3. --ds-fill IS SET ON THE INPUT BY SCRIPT. Custom properties cascade into
 *    pseudo-elements from the element they are set on, so one declaration
 *    drives both halves of the track.
 * ─────────────────────────────────────────────────────────────────────────── */
.ds-range { margin: 16px 0 0; }
/* THE LABEL AND THE WINDOW IT PRODUCED READ AS ONE LINE, LEFT ALIGNED.
   space-between was tried first and rendered at 1400 with 900px of empty
   track between "WINDOW" and "22 days to 5 Sep", which is a form layout: the
   readout ends up over the far end of the rail instead of beside the thing it
   names. Left aligned with a gap is what .pf-set already does with the
   position, the token and the two editable figures, and it is why that row
   reads as one line rather than as four. */
.ds-range-hd {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin: 0 0 9px;
}
html body .ds-range-l {
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
}
/* Full ink and mono: it is the population, so it is read, not glanced. */
html body .ds-range-v {
  color: var(--ink);
  font-family: var(--mono) !important;
  font-size: var(--t-sm) !important;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
html body .ds-range-i {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 26px;
  padding: 0;
  margin: 0;
  background: transparent;
  cursor: ew-resize;
  /* touch-action BELONGS ON THE ELEMENT THAT RECEIVES THE POINTER. On the
     wrapper instead, the browser claims the gesture for a page scroll and
     pointerdown fires ZERO times on a phone; the control looks broken and is
     really never receiving an event. Measured on an iPhone 13 profile for
     .pf-svg and the same applies here. */
  touch-action: none;
}
.ds-range-i::-webkit-slider-runnable-track {
  height: 2px;
  border: 0;
  background: linear-gradient(
    to right,
    var(--ink2) 0%,
    var(--ink2) var(--ds-fill, 50%),
    var(--line) var(--ds-fill, 50%),
    var(--line) 100%
  );
}
.ds-range-i::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  margin-top: -9px;
  cursor: grab;
}
.ds-range-i::-moz-range-track { height: 2px; background: var(--line); border: 0; }
.ds-range-i::-moz-range-progress { height: 2px; background: var(--ink2); }
.ds-range-i::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg); cursor: grab;
}
.ds-range-i:focus { outline: none; }
.ds-range-i:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.ds-range-ax {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
}
html body .ds-range-ax > span {
  color: var(--ink3);
  font-size: var(--t-xs) !important;
  letter-spacing: var(--tr-label) !important;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── THE BAND: TWO THUMBS ON ONE TRACK, AND NO LIBRARY ─────────────────────
 * <div class="ds-range" data-band>
 *   ... hd ...
 *   <div class="ds-band" style="--ds-lo:20%;--ds-hi:70%">
 *     <input type="range" class="ds-range-i">
 *     <input type="range" class="ds-range-i">
 *   </div>
 *   ... ax ...
 * </div>
 *
 * Two stacked native inputs. The INPUTS take pointer-events: none and the
 * THUMBS take it back, which is what lets a pointer reach the lower thumb
 * through the upper input's box. The track is drawn once, by .ds-band itself,
 * so the two inputs contribute no track at all and cannot draw two.
 * The section's script is responsible for one thing this CSS cannot do: the
 * thumbs must not cross. Clamp lo <= hi on every input event, or the band
 * inverts and --ds-lo > --ds-hi paints nothing, which reads as an empty
 * result, which on this product is a claim we did not make.
 * ─────────────────────────────────────────────────────────────────────────── */
.ds-band { position: relative; height: 26px; }
.ds-band::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 12px;
  height: 2px;
  background: linear-gradient(
    to right,
    var(--line) 0%,
    var(--line) var(--ds-lo, 0%),
    var(--ink2) var(--ds-lo, 0%),
    var(--ink2) var(--ds-hi, 100%),
    var(--line) var(--ds-hi, 100%),
    var(--line) 100%
  );
}
.ds-band .ds-range-i {
  position: absolute;
  inset: 0;
  width: 100%;
  pointer-events: none;
}
.ds-band .ds-range-i::-webkit-slider-runnable-track { background: transparent; }
.ds-band .ds-range-i::-webkit-slider-thumb { pointer-events: auto; }
.ds-band .ds-range-i::-moz-range-track { background: transparent; }
.ds-band .ds-range-i::-moz-range-progress { background: transparent; }
.ds-band .ds-range-i::-moz-range-thumb { pointer-events: auto; }


/* ══ 5. ZERO WITH A REASON ═════════════════════════════════════════════════
 * THE SHARPEST THING ON THIS SITE AND THE ONE NO VENUE CAN COPY, because it is
 * an admission and admissions do not get built by marketing teams. gap.css
 * already draws it INLINE, for a word inside a row or a cell. This is the
 * BLOCK form: what a whole card, a whole column or a whole section shows when
 * the answer is empty.
 *
 * <div class="ds-zero" data-why="ours">
 *   <p class="ds-zero-w">Not read</p>
 *   <p class="ds-zero-r">We index Aevo but no adapter of ours declares a
 *      fills mode for it. Aevo may well publish this.</p>
 * </div>
 *
 * ── THREE KINDS, AND THE DISTINCTION IS THE ENTIRE PRODUCT ────────────────
 *   data-why="measured"  A REAL ZERO. The tape is complete for this population
 *                        and the count is nought. Full ink, no rule, because
 *                        it is a measurement and not a gap.
 *   data-why="theirs"    We read the venue and the value is not published.
 *                        SOLID rule, matching gap.css's .mn-gap.
 *   data-why="ours"      We have not read it. It may well be published.
 *                        DASHED rule, matching .mn-gap-ours. THE DEFAULT, and
 *                        the default when in doubt: claiming a venue withholds
 *                        something is a claim about a third party, claiming we
 *                        have not read it costs us nothing but accuracy.
 *
 * data-why IS REQUIRED AND ITS ABSENCE IS DRAWN, the same way .ds-stat draws a
 * missing population. A .ds-zero with no reason is a dash, and a dash says
 * "nothing happened", which is the one claim this product exists to refuse.
 *
 * ── THE TWO DRAWING RULES, BOTH INHERITED FROM gap.css AND BOTH MEASURED ──
 * 1. AN ABSENCE IS AS PRESENT AS A MEASUREMENT. Same size, same ink weight as
 *    the figure it replaces. Not fainter, which reads as "less"; not
 *    identical, which reads as a value. So .ds-zero-w takes --t-lg, the stat
 *    figure's own size, and .ds-zero keeps the card's height so a grid does
 *    not reflow around a hole.
 * 2. THE DASHED RULE IS PAINTED, NOT BORDERED. A `border-left: 2px dashed` on
 *    a short line box is rendered by the engine as two or three segments of
 *    its own choosing, and read off a PNG at 1x it is very nearly solid, which
 *    collapses the one distinction this whole thing exists to draw. A
 *    repeating gradient states the pattern instead of asking for one: 3 on,
 *    3 off, same 2px width, same ink.
 * ─────────────────────────────────────────────────────────────────────────── */
/* THE RULE HANGS INTO THE PADDING THE CARD ALREADY HAS, AND THE WORDS KEEP
   THE STAT CARD'S LEFT EDGE. First draft indented the text to 25px to clear
   the rule and, rendered in a row beside a .ds-stat, "No book" sat 9px right
   of the "0" next to it: three cards in one row, three different left edges,
   and the eye reads that as three different kinds of object rather than as
   three answers to one question. gap.css solves the same problem inline with
   `margin-left: -9px` and states the goal exactly: run down a card or a
   column and the rules line up into a visible left edge as long as the
   coverage gap. So the rule goes at 7px, in the gutter, and the words stay at
   16px where every figure on the row is. */
.ds-zero {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 14px 16px 16px;
  position: relative;
  min-width: 0;
}
.ds-zero::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 15px;
  bottom: 17px;
  width: 2px;
  background: var(--ink3);
}
.ds-zero[data-why="ours"]::before {
  background: repeating-linear-gradient(
    to bottom,
    var(--ink3) 0, var(--ink3) 3px,
    transparent 3px, transparent 6px
  );
}
/* A MEASURED ZERO WEARS NEITHER MARK. gap.css says this in one line for the
   inline case (.mn-zero) and it is the same fact here: nothing is missing, so
   there is no rule and no promise of a reason. */
.ds-zero[data-why="measured"]::before { display: none; }

/* THE WORD, AT THE SIZE THE FIGURE WOULD HAVE BEEN. Tracked open and at 400,
   the .pf-b-none treatment, so it can never be scanned as a quantity. */
html body .ds-zero-w {
  margin: 0;
  color: var(--ink);
  font-family: var(--mono) !important;
  font-size: var(--t-lg) !important;
  font-weight: 400 !important;
  letter-spacing: 0.04em !important;
  line-height: 1;
}
/* A real zero IS a figure, so it is drawn as one: mono, 600, tight. */
html body .ds-zero[data-why="measured"] .ds-zero-w {
  font-weight: 600 !important;
  letter-spacing: -0.02em !important;
}
/* THE REASON, AND IT IS PROSE RATHER THAN A LABEL. This is the sentence a
   reader came for. It is at body size in --ink2, which is one step LOUDER than
   a caption: a reason kept at caption size is a reason nobody reads, and then
   the empty card is a dash again. */
html body .ds-zero-r {
  margin: 0;
  color: var(--ink2);
  line-height: 1.5;
  font-size: var(--t-sm) !important;
  max-width: 52ch;
}
/* WHOSE GAP IT IS, named in words. The hue does no work on its own here and is
   not asked to: the WORD is the carrier, the rule is the second signal, and
   both survive greyscale and a screenshot. */
html body .ds-zero-k {
  margin: 0;
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: var(--tr-label) !important;
  font-size: var(--t-xs) !important;
}
/* MISSING REASON. Same failure treatment as a missing population, and for the
   same reason: it must not survive a screenshot. */
html body .ds-zero:not([data-why]) {
  outline: 2px solid var(--neg);
  outline-offset: -2px;
}
html body .ds-zero:not([data-why])::before {
  content: "REASON MISSING: measured / theirs / ours";
  position: static;
  display: block;
  width: auto;
  background: none;
  color: var(--neg);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
}


/* ══ 6. THE ONE SHARED PHONE BREAK ═════════════════════════════════════════
 * 620px, which is the width payoff.css already breaks at. A second breakpoint
 * on a shared sheet is a second layout for four agents to disagree inside of.
 * ─────────────────────────────────────────────────────────────────────────── */
@media (max-width: 620px) {
  /* CARDS STACK RATHER THAN SHRINK. Three columns at 320px of section width is
     about 100px each, which breaks a figure like $206.4K over two lines and
     puts its population over four. */
  .ds-grid { grid-template-columns: 1fr; }
  /* THE THREE FACTS STACK, AND EACH VALUE TAKES ITS OWN LINE FLUSH LEFT.
     Left as one inline-flex row per fact, the value wrapped INSIDE its <b> and
     the second line landed indented under "venues that publish", which reads
     as a hanging fragment rather than as the population. `flex: 1 0 100%`
     pushes the whole value onto its own full-width line instead. */
  .ds-prov { gap: 9px 0; flex-direction: column; }
  html body .ds-prov > span { flex-wrap: wrap; }
  html body .ds-prov > span > b { flex: 1 0 100%; }
  /* The thumb grows because a finger is 44px wide and a 20px target is a miss
     waiting to happen. The rail's height follows it or the thumb clips. */
  html body .ds-range-i { height: 32px; }
  .ds-range-i::-webkit-slider-thumb { width: 26px; height: 26px; margin-top: -12px; }
  .ds-range-i::-moz-range-thumb { width: 22px; height: 22px; }
  .ds-band { height: 32px; }
  .ds-band::before { top: 15px; }
  .ds-range-hd { flex-direction: column; align-items: flex-start; gap: 4px; }
  html body .ds-range-v { text-align: left; }
}
