/* gap.css — THE FOUR OBJECTS THIS PRODUCT IS MADE OF.
 * ===========================================================================
 * Moneyness draws the same four things on every one of its eight sections:
 *
 *   A VENUE        a mark and a name
 *   A MEASUREMENT  a figure in full ink with its unit in muted ink
 *   AN ABSENCE     a word, and a reason for it
 *   A METHOD NOTE  the provenance of a figure that is not what it looks like
 *
 * Before this file, each section had invented its own. Measured across the
 * rendered surfaces on 2026-08-28, an ABSENCE alone was drawn six ways:
 *
 *   Venues    "—", "n/a" (italic), "no feed", "not public", "not published"
 *   Today     "venue not published" (italic)
 *   Lattice   "—" at --ink3, and "·" at --ink3 and 38% opacity
 *   Compare   "no offer", "No feed" (+ hatched swatch), "no feed", "$0" (dim)
 *   Trades    "—", with the reason only in a title attribute
 *   Islands   a word, at one ink step below the measurement beside it
 *   Flow      the tile omitted, or drawn with no text in it at all
 *
 * Three of those are bare punctuation and one of them is a zero. That is not
 * six styles of one thing, it is six different claims, and four of them make
 * the claim this product exists to refuse: THAT NOTHING HAPPENED. Eight of the
 * eleven venues publish no trade feed. A dash in their row says they were
 * quiet. They are unknown, not quiet, and the difference is the product.
 *
 * ── THE RULE THIS FILE ENFORCES ────────────────────────────────────────────
 * AN ABSENCE IS AS VISUALLY PRESENT AS A MEASUREMENT, AND IT IS NOT THE SAME
 * SHAPE AS ONE. Not fainter, which reads as "less"; not identical, which reads
 * as a value. It is the same size and the same ink weight as the measurement
 * beside it, and it hangs off a short rule that turns under the word. The rule
 * is what says "stated gap"; the dotted turn is what says "a reason is
 * attached, and it is in the title".
 *
 * Run down a card or a column and the rules line up into a visible left edge
 * exactly as long as the coverage gap. That is the finding, drawn.
 *
 * ── WHY IT LOADS LAST ──────────────────────────────────────────────────────
 * instrument.css:343 pins font-size, font-family, font-weight, letter-spacing,
 * border-radius and box-shadow with !important on `body *:not(svg)`. It does
 * NOT pin colour, border-left, border-bottom or padding, which is the whole
 * surface this file uses, so nothing here needs !important. It does not reset
 * font-style either, which is why every rule below says so by hand.
 * The `html body` prefix is the house idiom and is load-bearing: the per-
 * section rules this file supersedes are written at (0,1,3) themselves.
 */

/* ═══ AN ABSENCE ═════════════════════════════════════════════════════════ */
html body .mn-gap {
  color: var(--ink2);
  font-style: normal;
  cursor: help;
  /* THE RULE IS THE MARK. 2px, in the muted ink rather than in the accent:
     eight of eleven venues land here, and eight accents on one screen is not
     an emphasis, it is a background. */
  border-left: 2px solid var(--ink3);
  border-bottom: 1px dotted var(--ink3);
  padding-left: 7px;
  /* The word keeps the baseline of the measurements above it; only the rule
     hangs, into the padding the card already has. */
  margin-left: -9px;
}
html body .mn-gap:hover { color: var(--ink); border-left-color: var(--ink2); }

/* ═══ AN ABSENCE HAS TWO KINDS, AND THE PRODUCT LIVES ON THE DIFFERENCE ═══
 * .mn-gap        THEIRS. We looked and the value is not published.
 * .mn-gap-ours   OURS.  We have not read it. It may well be published.
 *
 * ── WHY THIS SPLIT EXISTS, AND IT IS NOT A REFINEMENT ─────────────────────
 * The surface asserted, at display size on two sections, that EIGHT OF THE
 * ELEVEN INDEXED VENUES PUBLISH NO TRADE FEED. That is false, and it was
 * re-verified false on 2026-08-28 by fetching the venues directly, without
 * credentials:
 *
 *   Deribit  public/get_last_trades_by_currency?currency=BTC&kind=option
 *            returns real option fills (BTC-25DEC26-65000-C at 0.209, and
 *            BTC-29AUG26-77000-P). Confirmed in this session.
 *   OKX      /api/v5/market/trades on a live instrument id returns fills.
 *   Binance  /eapi/v1/trades on a live symbol returns trades.
 *   Paradex  GET /v1/trades?market=... returns 200 with side, size, price and
 *            timestamp per fill (tape.js:1124, audited 2026-08-05).
 *   Rysk, CallPut  settle on HyperEVM and Base. Their fills are on a public
 *            chain and readable from its logs.
 *
 * What the cell actually meant, in the code, was only this: NO ADAPTER OF OURS
 * DECLARES A fills_mode. Three do. That is a statement about our adapters and
 * about nothing else, and the surface published it as a statement about eight
 * named venues, to an audience that IS those venues.
 *
 * It is also the exact inverse of house rule 1. "Unknown is never zero" says a
 * venue we cannot see is a gap in OUR coverage and never evidence about the
 * venue. The old copy converted a missing adapter into a published assertion
 * that eight venues are more opaque than they are. There is no worse bug this
 * product can ship, because it is this product's own thesis, run backwards.
 *
 * ── THE DRAWING ───────────────────────────────────────────────────────────
 * Same word weight, same ink, same size, same dotted turn: both are absences
 * and neither is a lesser thing than a measurement. The rule tells them apart.
 * SOLID means we looked and it is not there. DASHED means we have not looked,
 * and dashed already means "not ground in the sense the rest of the map means
 * it" one file over (islands.css .isl-band-off), so the vocabulary is the
 * app's own rather than a new one. A reader can run a column and see, without
 * a single hover, which holes are the market's and which are ours.
 *
 * WHEN IN DOUBT IT IS OURS. Claiming a venue withholds something is a claim
 * about a third party; claiming we have not read it costs us nothing but
 * accuracy. The default in every render path below is the dashed one. */
/* THE RULE IS PAINTED, NOT BORDERED, AND THAT IS FOR LEGIBILITY AT 1x. A
   `border-left: 2px dashed` on a 17px line box is rendered by the engine as
   two or three segments whose length it chooses; read back off the PNG at 1x
   it was very nearly a solid rule, which would have collapsed the one
   distinction this whole file exists to draw. A repeating gradient states the
   pattern instead of asking for one: 3 on, 3 off, at the same 2px width and
   the same ink as the solid rule, so the two differ in exactly one property
   and a reader can tell them apart down a column without hovering anything. */
html body .mn-gap-ours {
  color: var(--ink2);
  font-style: normal;
  cursor: help;
  border-bottom: 1px dotted var(--ink3);
  /* 9px, not 7px. The solid variant spends 2px of its 9px offset on a border
     box; this one paints its rule inside the padding instead, so the padding
     has to absorb the same 2px or the two words sit two pixels apart in a
     column where they alternate. Measured off the PNG, not reasoned. */
  padding-left: 9px;
  margin-left: -9px;
  background-image: repeating-linear-gradient(
    to bottom, var(--ink3) 0 3px, transparent 3px 6px);
  background-size: 2px 100%;
  background-repeat: no-repeat;
  background-position: left top;
}
html body .mn-gap-ours:hover {
  color: var(--ink);
  background-image: repeating-linear-gradient(
    to bottom, var(--ink2) 0 3px, transparent 3px 6px);
}
html body td .mn-gap-ours,
html body th .mn-gap-ours { margin-left: 0; }

/* IN A TABLE CELL THE RULE DOES NOT HANG. A negative margin inside a td pulls
   the word out of its column and off the header above it; here the 7px of
   padding is the indent, and it is wanted: in a column where ten rows are gaps
   and one is a value, the one value is the only thing flush left. */
html body td .mn-gap,
html body th .mn-gap { margin-left: 0; }

/* ═══ A METHOD NOTE ══════════════════════════════════════════════════════ */
/* A figure that IS measured but is not what it looks like at a glance: read
   once on a date, or read off a venue's own documentation rather than its
   feed. It wears the dotted turn, because the same promise applies (the reason
   is in the title), and never the rule, because nothing is missing. */
html body .mn-note {
  border-bottom: 1px dotted var(--ink3);
  cursor: help;
  font-style: normal;
}
html body .mn-note:hover { border-bottom-color: var(--ink2); }

/* ── THE MARK YOU TAP ──────────────────────────────────────────────────────
 * .mn-note is a dotted underline whose reason lives in a title attribute, and
 * a title attribute IS NOT A DISCLOSURE. It does not exist on a phone. The
 * audit that produced this pass measured 67.6% of one section's prose sitting
 * behind hover, every tooltip hover-only: on touch that is not prose behind a
 * mark, it is prose deleted.
 *
 * So the method note that carries real sentences gets a real control. .mn-i is
 * a <button>, which means Enter and Space open it for free, and glossary.js
 * toggles aria-expanded on it so a screen reader is told it is a disclosure
 * and whether it is open. It renders the SAME popover as a glossary term,
 * because a reader should not have to learn two ways to ask "what is this".
 *
 * WHY A CIRCLE AND NOT A WORD. Every one of these sits beside a figure, and a
 * word beside a figure competes with it: the surface is supposed to answer,
 * and the mark is supposed to be the thing you reach for only if the answer
 * raised a question. A 15px ring is legible, is not a value, and never wraps.
 *
 * font-size AND border-radius NEED !important. instrument.css:343 pins both
 * with !important on `body *:not(svg)`; without them the ring renders as a
 * 13px square. Measured, not reasoned. */
/* THE HIT AREA IS BIGGER THAN THE RING, AND IT IS A REAL BOX, NOT A PSEUDO.
   First draft drew a 15px button and stretched its touch target with an
   absolutely positioned ::before at inset:-6px. It worked, and it also made
   every mark on the page report scrollWidth 19 against clientWidth 13: a
   negative-inset child is scrollable overflow, and this pass is verified
   against a zero-overflow assertion. So the BUTTON is the 27px target and a
   span inside it is the 15px ring. Same drawing, no overflow, and the ring
   still cannot be tabbed to separately because a span is not focusable. */
html body .mn-i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 27px;
  height: 27px;
  padding: 0;
  /* The box is 27px and the ring is 15px, so it already carries 6px of
     transparent gutter: the visual gap to the figure on its left is that
     gutter, and the block margins give the line box its 15px back. */
  margin: -6px 0;
  vertical-align: middle;
  background: none;
  border: 0;
  color: var(--ink3);
  cursor: pointer;
  flex: none;
}
html body .mn-i > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border: 1px solid var(--line2);
  border-radius: 50% !important;
  font-size: 9.5px !important;
  line-height: 1 !important;
  font-style: normal;
  letter-spacing: 0 !important;
}
html body .mn-i:hover,
html body .mn-i[aria-expanded="true"] { color: var(--ink); }
html body .mn-i:hover > span,
html body .mn-i[aria-expanded="true"] > span { border-color: var(--ink2); }
html body .mn-i:focus-visible { outline: 0; color: var(--ink); }
html body .mn-i:focus-visible > span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Inside a heading or a kicker the mark takes the surrounding ink, not the
   heading's weight: it is an aside on the line, never a second word in it. */
html body .td-kick .mn-i,
html body .mn-kick .mn-i { margin-left: 7px; }

/* THE STRONGER FORM, for a figure that is not live at all. A word rather than
   a colour: colour would rank it against the live figures beside it, and it is
   not worse than them, it is a different kind of thing. */
html body .mn-note i,
html body .mn-once {
  font-style: normal;
  font-size: 9.5px !important;
  letter-spacing: .06em !important;
  text-transform: uppercase;
  color: var(--ink3);
  border: 1px solid var(--line2);
  padding: 1px 4px;
  margin-left: 6px;
  white-space: nowrap;
}

/* ═══ A MEASURED ZERO IS NOT AN ABSENCE ══════════════════════════════════ */
/* The one case that must never wear either mark. "No underlying in common"
   when both venues publish their full asset lists is a fact, not a gap. It is
   muted because it is a null result, and it carries no promise of a reason
   because there is no missing reason to promise. */
html body .mn-zero { color: var(--ink3); font-style: normal; }

/* ═══ THE SECTIONS THAT HAD THEIR OWN ═════════════════════════════════════
 * Aliased rather than rewritten where the markup already carries a class with
 * exactly this meaning, so one drawing reaches every section without eight
 * render paths having to agree on a string. Where a section rendered
 * PUNCTUATION instead of a word, the alias is not enough and the render path
 * itself was changed; those are noted at the call site.
 */
/* EVERY SELECTOR BELOW WAS CHECKED TO EXIST IN THE MARKUP BEFORE IT WAS
   ALIASED. Two invented names (.td-nov, .sc-gap) and one wrong target
   (.chv-na) were in an earlier draft of this list: .td-nov and .sc-gap matched
   nothing at all, and .chv-na matched a LATTICE GRID CELL, which put a 2px
   rule and a dotted underline on every empty cell of a 300-cell matrix and
   even on the legend swatch under it. An alias list is a promise that the
   class means what this file says it means, and a class that does not exist
   cannot keep it. Today's own absence is .td-c-vn--none and is handled below
   with the rest of that section's italics. */
html body .vn-na,          /* Venues: was italic "n/a"                      */
html body .vn-off,         /* Venues: was a dim "no feed"                   */
html body .td-c-vn--none,  /* Today:  was an italic "venue not published"   */
html body .tw-none {       /* Trades: was a bare em dash                    */
  color: var(--ink2);
  font-style: normal;
  cursor: help;
  border-left: 2px solid var(--ink3);
  border-bottom: 1px dotted var(--ink3);
  padding-left: 7px;
}
html body .vn-na:hover,
html body .vn-off:hover,
html body .td-c-vn--none:hover,
html body .tw-none:hover { color: var(--ink); border-left-color: var(--ink2); }

/* TRADES' ABSENCE IS OURS-SHAPED WHERE IT IS ABOUT A COUNTERPARTY. The venue
   published the fill and did not name the side; that is the venue's
   disclosure, so it keeps the solid rule. Nothing on the tape is a coverage
   gap of ours, because a fill we cannot read never reaches the tape at all. */

/* ── THE ONE PLACE THE RULE IS WRONG, AND WHY ──────────────────────────────
   Lattice is a dense matrix: every strike that a venue does not list is an
   absence, so a rule on each would draw several hundred ticks and say nothing.
   There the absence keeps a glyph, but the glyph is made legible (it was
   --ink3 at 38% opacity, which is below the app's own contrast floor) and the
   legend under the grid carries the words. Density is the reason; it is not a
   licence for the other seven sections. */
html body .chv-void {
  color: var(--ink3);
  opacity: .72;
  font-style: normal;
  cursor: help;
}

/* ── THE SHELL EDGE ────────────────────────────────────────────────────────
   main is the scroller and <footer> is its sibling in the shell, so content
   always terminates at a hard 1px rule with the footer's own figures sitting
   directly under it. Measured at 1280 and 1920 that cut lands MID-GLYPH
   through a venue name ("Aevo" and "Paradex" both sliced horizontally), and
   the footer's ground is rgba(28,22,17,.55), i.e. translucent, so the sliced
   text also shows THROUGH the bar. Nothing overlaps in the box model, but
   every reader sees a bar cutting a card, and three of them told us so.

   THE FIX IS THE SCROLLPORT, NOT THE BAR. A depth cue on the footer treats the
   symptom; the cut is still a cut. Masking the last 30px of the scroller means
   content never terminates at an edge at all: it fades, which is the honest
   signal, because there IS more below. Trades already solved its own last row
   this way (controls.css:1282); this lifts the same answer to the shell so
   every section gets it. The bar keeps a soft cast so the fade reads as depth
   rather than as a rendering fault.

   THE BOTTOM PADDING IS WHY THIS IS FREE AT THE END OF A SCROLL. main carries
   padding-bottom:40px (style.css:623), so when a section is scrolled to its
   end the masked band falls on padding and nothing legible fades. */
html body main {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 30px), rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 30px), rgba(0, 0, 0, 0) 100%);
}
html body > footer {
  position: relative;
  z-index: 3;
  box-shadow: 0 -14px 20px -14px rgba(0, 0, 0, .7) !important;
}

/* ── THE FOOTER BAND FITS ITS OWN BAR ──────────────────────────────────────
   footer carries max-height:44px (style.css:1095) so the collapsed colophon is
   exactly its .foot-bar. But .foot-bar is `flex-wrap: wrap`, and measured at
   1280 its six items do not fit one line: "How this is measured", the only
   CONTROL in the bar, wraps to a second line and is then cut in half by the
   44px ceiling. A clipped button is worse than a tall footer.

   max-height:none is safe here and not a loosening of the original rule. What
   the 44px was guarding against is the recording block, and `footer .rec {
   display: none }` (style.css:1099) already removes that from flow entirely
   while the disclosure is shut. The open state keeps its own ceiling, declared
   below so this rule cannot swallow it. */
html body > footer { max-height: none; }
/* AND THE BAR SHEDS WEIGHT RATHER THAN GROWING FOREVER. Letting it size to its
   own content fixes the clipped control at 1280, where it wraps to two lines.
   At 390 the same six items wrap to FOUR, and a colophon spending 170px of a
   900px viewport is its own defect: the fix for a clipped footer must not be a
   footer that eats the page. Below 560px the two items whose information is
   reachable elsewhere step out (the counter's caption, which the recording
   panel states in full, and the start date, which it states beside it) and the
   two that are not reachable elsewhere stay: the live count, and the control
   that opens the panel holding both of the others. */
@media (max-width: 560px) {
  html body > footer .foot-bar { gap: 6px 14px; }
  html body > footer .foot-bar .tr-count i,
  html body > footer .foot-bar .tr-since { display: none; }
  html body > footer .foot-bar .byline { margin-left: 0; }
}
html body > footer:has(#method:not([hidden])) {
  max-height: min(70dvh, 540px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ── THE VENUE TABLE'S HEADERS WRAP, SO THE DATA SETS THE COLUMN WIDTHS ────
   controls.css records two failed re-cuts of this track list. §11 sized the
   columns from their max-content DATA and clipped two LABELS ("ASSE…",
   "CONTRAC…"); §19 sized them from the LABELS and pushed the mins to 1,122px
   against a content box of ~1,072px at 1440, which amputated the ninth column
   outright: the header rendered "COUNTERPAR" and the cells "both sides" and
   "not publisl", sliced mid-glyph. .vn-table does carry overflow-x:auto, but
   with overlay scrollbars there is nothing on screen to say a column is out
   there, so it simply disappeared.

   Both re-cuts were solving the wrong problem. A ten-character header on a
   five-character column is only a floor because the header refuses to wrap.
   Let it wrap and the constraint is gone: every track can be sized on its
   data, which is what §11 measured correctly in the first place, and the
   ninth column comes back on screen at every width.

   The header row keeps its baseline by aligning to the bottom, so one-line and
   two-line labels sit on the same rule as each other and as the data. */
html body .vn-head .vn-c {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.25;
  align-self: end;
  hyphens: none;
}

/* ── THE VENUE TABLE IS EIGHT COLUMNS NOW, AND THE TRACKS ARE MEASURED ─────
   Measured with a Range over every cell's text node plus its own padding, on
   the rendered table, at 1440 / 1280 / 900:

     venue 121 | how you trade 180 | network 202 | assets 70 | contracts 90
     prices 109 | firm 136 | trades 134 | counterparties 136   = 1,178px

   against a content box of 1,070px at 1440 and 920px at 1280. Nine columns of
   worded absences do not fit any width this app can produce (the widest is
   1,138px at 1920), so shaving the track list a third time could not have
   worked, and the two shaves already in controls.css (§11, §19) are why the
   ninth column was amputated in the first place.

   TWO CHANGES BOUGHT THE ROOM, and both are better information design rather
   than compression. PRICES became a sub-line under HOW YOU TRADE, because
   they are one fact about access and the distinction that column existed for
   (CallPut quotes on request AND publishes prices; Rysk quotes on request and
   publishes none) now reads on one line instead of across five columns. FIRM's
   absence became "no book" rather than "not published", naming the missing
   object instead of the missing act. 1,178 - 109 - 39 = 1,030.

   1,030 fits 1,070 at 1440 and 1,138 at 1920. It does NOT fit 920 at 1280 or
   570 at 900, and .vn-table already scrolls (instrument.css:2150). What was
   missing was any sign that it does: with overlay scrollbars the ninth column
   simply vanished. The fade below is that sign.

   These override controls.css §19 by loading later at equal specificity. */
html body .vn-table {
  grid-template-columns:
    minmax(min(100%, 121px), 1.20fr)   /* venue                             */
    minmax(min(100%, 184px), 1.70fr)   /* how you trade, + prices sub-line  */
    minmax(min(100%, 202px), 1.90fr)   /* network: "Derive Chain (OP Stack)"
                                          measures 202 with its padding     */
    minmax(min(100%,  70px), 0.66fr)   /* assets                            */
    minmax(min(100%,  90px), 0.85fr)   /* contracts                         */
    minmax(min(100%,  97px), 0.92fr)   /* firm, floored on "no book"        */
    minmax(min(100%, 140px), 1.26fr)   /* trades, 24h                       */
    minmax(min(100%, 136px), 1.28fr)   /* counterparties                    */
    /* !important because controls.css:1236 declares its own list important.
       An !important anywhere in a value makes the whole DECLARATION important,
       so the only way to win is in kind. */
    !important;
  position: relative;
}

/* THE SIGN THAT THERE IS MORE TO THE RIGHT. A fade on the last 28px of the
   scrollport, removed once the scroller has reached its end so it can never
   dim a column that is fully on screen. Pure CSS: `animation-timeline: scroll()`
   drives it off the scroll position with no listener to leak. Where that is
   unsupported the fade simply stays on, which is the safe failure: it says
   "there is more" on a table where there usually is. */
@supports (animation-timeline: scroll(self inline)) {
  html body .vn-table {
    animation: mn-fade-end linear both;
    animation-timeline: scroll(self inline);
    animation-range: contain;
  }
}
html body .vn-table {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), rgba(0, 0, 0, .18) 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), rgba(0, 0, 0, .18) 100%);
}
@keyframes mn-fade-end {
  to {
    -webkit-mask-image: linear-gradient(to right, #000 100%, #000 100%);
    mask-image: linear-gradient(to right, #000 100%, #000 100%);
  }
}

/* ── NETWORKS: THE TWO CHIP GROUPS ARE TWO DIFFERENT CLAIMS ────────────────
   "7 networks we read live" and "14 networks holding no venue we can index"
   are the whole of this block's argument, and they were drawn apart by one
   border tint and one ink step (learn.css:596). The second group is a group of
   ABSENCES, so it wears the mark every other absence in the app wears, and the
   two groups separate at a glance instead of on inspection. */
html body .nw-i:not(.nw-on) { border-left: 2px solid var(--ink3); }

/* ══════════════════════════════════════════════════════════════════════════
 * COMPARE: THE FIRM / INDICATIVE FIREWALL, PUT BACK
 * ══════════════════════════════════════════════════════════════════════════
 * House rule 2 of this product: a resting two-sided quote you could hit and a
 * price quoted on request are DIFFERENT OBJECTS and are never shown as the
 * same one. On the Compare board they were.
 *
 * grid.js gets this exactly right. floor's val() sets `c: firm ? "n" : "n ind"`
 * and `s: firm ? "of price" : "indicative"`, and its comment states the reason
 * in full: CallPut is an RFQ venue whose indicative price can come in UNDER a
 * venue whose price is actually resting on a book, so "printing the two at the
 * same weight would let the cheaper one win a comparison it has not earned".
 *
 * instrument.css:1808 then hid every sub-line on the board:
 *     body .sc-c > i, body .sc-c .sc-b > i { display: none !important; }
 * The strings it removed are, verbatim, "indicative", "not published",
 * "unknowable", "feed is live", "no live offer" and "nothing resting" — the
 * qualifiers, and nothing but the qualifiers, that say what KIND of thing each
 * figure is. Rendered, CallPut's indicative 1.39% and Binance's firm 1.77%
 * became the same object separated by one ink step at 12px.
 *
 * §30'S DENSITY ARGUMENT IS NOT BEING UNDONE. "One value per cell" is a good
 * rule and it stays for the captions that are decoration ("12 exp ·
 * 800-16,000", "1,183 fills", the maker counts). What comes back is the six
 * cell states where the sub-line is not a caption at all: it is the difference
 * between a price you can hit and a price someone might honour, and between a
 * venue that traded nothing and a venue we cannot see. Those are selected by
 * the state class the render path already puts on the cell, so nothing here
 * has to guess from text.
 */
html body .sc-c.ind > i,
html body .sc-c.ind .sc-b > i,          /* "indicative"     — an RFQ price   */
html body .sc-c.unk > i,
html body .sc-c.unk .sc-b > i,          /* "unknowable"     — no feed        */
html body .sc-c.st-unknown > i,
html body .sc-c.st-unknown .sc-b > i,   /* "not published"  — no feed        */
html body .sc-c.st-listed > i,
html body .sc-c.st-listed .sc-b > i,    /* "feed is live"   — a real zero    */
html body .sc-c.pr-req > i,
html body .sc-c.pr-req .sc-b > i,       /* "quoted on demand"                */
html body .sc-c.pr-marks > i,
html body .sc-c.pr-marks .sc-b > i,     /* "no live offer"                   */
html body .sc-c.pr-empty > i,
html body .sc-c.pr-empty .sc-b > i {    /* "nothing resting"                 */
  display: block !important;
  font-style: normal;
  /* WRAPS, AS INSURANCE. These qualifiers are the firewall; a qualifier that
     silently overflows its cell because someone lengthens a string is the
     failure this whole pass exists to remove, and a right-aligned cell
     overflows LEFT where text-overflow gives no ellipsis and nothing in the
     DOM reports it. Wrapping fails visibly instead. */
  white-space: normal;
  overflow-wrap: anywhere;
  font-size: 10px !important;
  letter-spacing: .05em !important;
  line-height: 1.3 !important;
  color: var(--ink3);
  margin-top: 2px;
}
/* .sc-b is the flex box holding value and qualifier; it has to stack for the
   qualifier to sit UNDER the figure rather than beside it, and it has to keep
   the cell's own alignment so a right-aligned column stays right-aligned. */
html body .sc-c.ind .sc-b,
html body .sc-c.unk .sc-b,
html body .sc-c.st-unknown .sc-b,
html body .sc-c.st-listed .sc-b,
html body .sc-c.pr-req .sc-b,
html body .sc-c.pr-marks .sc-b,
html body .sc-c.pr-empty .sc-b {
  flex-direction: column;
  align-items: inherit;
}
html body .sc-c.a-r.ind .sc-b,
html body .sc-c.a-r.unk .sc-b { align-items: flex-end; }

/* THE INDICATIVE FIGURE IS NOT DRAWN AS A FIRM ONE. Same figure, and it is a
   real figure, so it keeps full ink and never wears the absence rule: nothing
   is missing. It wears the method mark instead, which is what it is: a price
   whose provenance is not what the column implies. */
html body .sc-c.ind .sc-b > b {
  border-bottom: 1px dotted var(--ink3);
  color: var(--ink2);
}

/* THE THREE ABSENCE DIALECTS ON THIS BOARD BECOME ONE. "no offer", "No feed"
   and "no feed" sat in one row in three casings and two type registers. The
   words are grid.js's and are correct; only the drawing is unified here, on
   the state class the cell already carries, so no render path had to agree on
   a string for the board to stop speaking three languages. */
/* .dim IS THEIRS: "no offer" means we read the venue's book and there is no
   two-sided put near that strike and tenor. Solid rule.
   .unk AND .st-unknown ARE OURS: both are the fills we do not read, which is
   the claim this whole pass corrected. Dashed, and painted rather than
   bordered for the reason recorded on .mn-gap-ours. Three columns of this
   board carry an absence on eight of eleven rows, so getting the kind right
   here is most of the board's honesty. */
html body .sc-c.dim .sc-b {
  border-left: 2px solid var(--ink3);
  padding-left: 7px;
  color: var(--ink2);
}
html body .sc-c.unk .sc-b,
html body .sc-c.st-unknown .sc-b {
  padding-left: 9px;
  color: var(--ink2);
  background-image: repeating-linear-gradient(
    to bottom, var(--ink3) 0 3px, transparent 3px 6px);
  background-size: 2px 100%;
  background-repeat: no-repeat;
  background-position: left top;
}
/* IN A RIGHT-ALIGNED COLUMN THE RULE STILL SITS LEFT OF THE WORD, AND THE BOX
   SHRINKS TO THE WORD TO PUT IT THERE. Hanging it on the cell's right edge was
   tried first and read wrong: it landed a pixel from the next column's border
   and the pair drew as one double rule, so the mark stopped looking like a
   mark. width:max-content plus margin-left:auto keeps the cell right-aligned
   while making the rule touch the text it belongs to. */
html body .sc-c.a-r.unk .sc-b,
html body .sc-c.a-r.dim .sc-b {
  width: max-content;
  max-width: 100%;
  margin-left: auto;
}

/* ── THE KEY. THE SYSTEM HAS TO TEACH ITSELF ───────────────────────────────
   Three marks carry the whole reading of this product and none of them is
   self-evident on first sight: a solid rule is a gap the venue left, a dashed
   rule is a gap we left, and a dotted underline with neither is a measured
   value whose provenance is on the mark. A reader who does not know that reads
   eleven cards as eleven venues of varying quality, which is the opposite of
   what the map says. It is one line, it sits under the map where the marks
   are, and it is drawn with the real classes so it can never describe a style
   the page is not using. */
html body .mn-key {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
  align-items: baseline;
  margin: 12px 0 0;
  padding: 10px 0 0;
  border-top: 1px solid var(--line);
  list-style: none;
}
/* THE KEY ALSO HAS TO WORK INSIDE A <p>. Compare's key lands in
   #where-legend, which index.html declares as a paragraph, and a <ul> inside a
   <p> is closed by the parser before it is opened: the browser silently splits
   the paragraph and the key lands outside it. Same drawing, span children. */
html body .mn-key li,
html body .mn-key > span {
  font-size: 11px !important;
  line-height: 1.5 !important;
  color: var(--ink3);
  min-width: 0;
}
html body p.mn-key { display: flex; }
html body .mn-key li > span:first-child,
html body .mn-key > span > span:first-child { margin-right: 5px; }
html body .mn-key .mn-gap,
html body .mn-key .mn-gap-ours { margin-left: 0; color: var(--ink2); }

/* ── LATTICE: THE TWO FACTS THE GRID IS ABOUT WERE ITS TWO FAINTEST MARKS ──
   chain.css:271 drew "listed, nothing resting" at --ink3 and "not listed at
   all" at --ink3 with opacity .38. On the live dark palette that second one
   computes to roughly #3a4746 against a #0b1416 ground, which is under 2:1 and
   below this app's own contrast floor for text. So the densest surface in the
   product rendered its core claim as something a reader could easily not see
   at all, with the meaning available only in a title attribute.

   Both marks are legible now, and they still differ from each other and from a
   price: a price is --ink, "nothing resting" is a hollow ring at --ink2, "not
   listed" is a middot at --ink3. Three weights of three different facts, none
   of them a numeral and none of them a minus sign. The words are in the legend
   under the grid; see chain.js footnote(). */
html body .chv .chv-t td.chv-na {
  color: var(--ink2);
  border-left: 0;
  padding-left: 0;
}
html body .chv-lg b.chv-na,
html body .chv-lg b.chv-void {
  border-left: 0;
  border-bottom: 0;
  padding-left: 0;
}
html body .chv .chv-t td.chv-void { color: var(--ink3); opacity: .85; }
html body .chv-lg b.chv-na { color: var(--ink2); }
html body .chv-lg b.chv-void { color: var(--ink3); }

/* ── TODAY'S SIX CARDS WRAPPED 5 + 1 ───────────────────────────────────────
   today.js:297 slices a FIXED six runners-up; today.css:454 lays them out with
   `repeat(auto-fill, minmax(190px, 1fr))`, which resolves to five tracks at
   the measured content width and leaves the sixth card alone on a row of its
   own. A count that is fixed in the render path has no business being laid out
   by a track algorithm that does not know it: auto-fill will land on 5 for the
   same reason it lands on 4 for a five-item strip.

   Six divides by 3, 2 and 1, so those are the counts declared, and none of
   them can orphan. The breakpoints are measured against the content box, not
   guessed against the viewport, for the 900px trap (nav.css:129): content runs
   390->300, 620->520, 768->657, 885->764, 899->777, 900->570, 1280->920,
   1920->1138, which is not monotonic across 899/900. Three columns need
   3*190 + 2*10 = 590px of content, first cleared at 700 (594) and again at 900
   (570)... which it does NOT clear, so the floor is set at 1000 (662) where
   every wider viewport also clears it. Two columns need 390px, cleared from
   480 up. Re-measure before moving either number. */
html body .td-cs {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 479px) {
  html body .td-cs { grid-template-columns: 1fr; }
}
@media (min-width: 1000px) {
  html body .td-cs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The assets whose treemap tiles are too small to carry a ticker, named in
   words under the chart. Set at the app's provenance size, because that is
   what this line is: a statement about what the drawing could not show. */
html body .fl-tiny {
  margin: 6px 0 0;
  font-size: 11px !important;
  line-height: 1.55 !important;
  color: var(--ink3);
  max-width: 92ch;
}
html body .fl-tiny b { color: var(--ink2); font-weight: 500 !important; }

/* ── FLOW'S DAILY RIBBON: A MEASURED DAY WAS DRAWN IN THE BORDER COLOUR ────
   instrument.css:5348 gives .dy-b `background: var(--line2)` and a 2px floor,
   so 29 of 31 measured days render as hairlines in the exact token used for
   table rules, and the ribbon reads as an empty axis with one spike on it.
   A day we measured is a value; it takes value ink. */
html body .dy-b { background: var(--ink3); }
