:root {
  /* THE SHARE LANE. A fixed trailing track appended to every --tape-cols
     variant, so the SHARE control has a place of its own to sit. It used to be
     positioned over the row's last column, where elementsFromPoint confirmed it
     was covering the maker column's own button. Rows carry no extra child, so
     this track simply stays empty and the button is placed into it. */
  --tape-share: 76px;
}

/* Moneyness — Tools & offerings.
 *
 * Every value below is an existing token from style.css (--bg, --ink, --s1..3,
 * --line, --line2, --q1..--q5, --empty, --mono, --sans, --r). This file
 * defines NO new colour. The reason is the one already written into marks.js:
 * the blue ramp means recency, --empty means "no measurement", and a second
 * palette invented here would give a reader two colour languages with no way
 * to tell which one is data.
 *
 * Four-state cell vocabulary is deliberately IDENTICAL to the coverage board
 * (td.cov-c in style.css): shaded = traded, dashed = listed-no-trades,
 * hatched = unknown, blank = not listed. A reader learns it once. The rules
 * are duplicated rather than shared because this file must not depend on
 * selectors owned by another agent's stylesheet.
 *
 * Everything is scoped under .tl / #tools-root so nothing here can reach a
 * component it does not own.
 */

#tools-root .tl-body {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 0;
  align-items: start;
}

/* ── left rail: sections, one per question ──────────────────────────── */
.tl-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 10px 16px;
  border-right: 1px solid var(--line);
  position: sticky;
  top: 56px;
  background: linear-gradient(180deg, var(--s2), transparent 70%);
  min-height: 100%;
}
.tl-rail-h {
  font: 700 10px/1 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ink3);
  padding: 4px 9px 9px;
}
.tl-tab {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 8px 10px;
  cursor: pointer;
  color: var(--ink2);
  font: inherit;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.tl-tab:hover {
  background: var(--glass);
  color: var(--ink);
}
.tl-tab b {
  display: block;
  font: 650 12.5px/1.3 var(--sans);
  letter-spacing: -0.015em;
}
.tl-tab i {
  display: block;
  font-style: normal;
  font-size: 10.5px;
  color: var(--ink3);
  margin-top: 2px;
  line-height: 1.35;
}
.tl-tab[aria-selected="true"] {
  background: var(--bg);
  border-color: var(--line2);
  box-shadow: inset 2px 0 0 var(--mint);
}
.tl-tab[aria-selected="true"] b {
  color: var(--mint);
}

/* ── right pane ─────────────────────────────────────────────────────── */
.tl-main {
  padding: 14px 15px 18px;
  min-width: 0;
}
.tl-sec[hidden] {
  display: none;
}
.tl-sec-h {
  margin: 0 0 3px;
  font: 650 14px/1.25 var(--sans);
  letter-spacing: -0.02em;
}
.tl-sec-s {
  margin: 0 0 14px;
  font-size: 11.5px;
  color: var(--ink3);
  max-width: 92ch;
  line-height: 1.6;
}

/* ── the card grid (the "tools" surface) ────────────────────────────── */
.tl-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 10px;
}
.tl-card {
  background: var(--s1);
  border: 0;
  border-radius: var(--r-card);
  padding: 12px 13px 11px;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.tl-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, var(--mint-dim), transparent 65%);
}
.tl-card h4 {
  margin: 0;
  font: 650 13px/1.3 var(--sans);
  letter-spacing: -0.015em;
  color: var(--ink);
}
.tl-card .tl-struct {
  font: 600 10px/1.5 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink2);
  background: var(--s3);
  border: 1px solid var(--line2);
  border-radius: 4px;
  padding: 1px 6px;
  align-self: flex-start;
}
.tl-card p {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--ink2);
}
.tl-recipe {
  font: 10.5px/1.45 var(--mono);
  color: var(--ink);
  background: var(--s1);
  border: 1px solid var(--line);
  border-left: 2px solid var(--mint2);
  border-radius: 4px;
  padding: 6px 8px;
}
.tl-kv {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: auto;
  padding-top: 7px;
  border-top: 1px solid var(--line);
}
.tl-kv div {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font: 10.5px/1.5 var(--mono);
  color: var(--ink3);
}
.tl-kv div b {
  color: var(--ink);
  font-weight: 700;
}
.tl-kv div.tl-unk b {
  color: var(--warn);
}

/* ── venue x product matrix ─────────────────────────────────────────── */
.tl-matrix-wrap {
  margin-top: 18px;
  overflow-x: auto;
}
table.tl-m {
  border-collapse: separate;
  border-spacing: 0;
  font: 11px/1.3 var(--mono);
  min-width: 640px;
}
table.tl-m th,
table.tl-m td {
  padding: 0;
}
.tl-m thead th.tl-p {
  vertical-align: bottom;
  padding: 0 5px 7px;
  text-align: left;
  width: 116px;
}
.tl-m thead th.tl-p b {
  display: block;
  font: 650 11px/1.3 var(--sans);
  color: var(--ink);
  letter-spacing: -0.01em;
  white-space: normal;
}
.tl-m thead th.tl-p i {
  display: block;
  font-style: normal;
  font: 600 8.5px/1.4 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink3);
  margin-top: 2px;
}
.tl-m th.tl-v {
  text-align: left;
  vertical-align: middle;
  padding-right: 12px;
  white-space: nowrap;
  font-weight: 600;
  color: var(--ink);
  height: 56px;
}
.tl-m th.tl-v span.vn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.tl-m th.tl-v em {
  font-style: normal;
  display: block;
  font: 10px/1.4 var(--mono);
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding-left: 24px;
}

.tl-c {
  width: 112px;
  height: 62px;
  vertical-align: middle;
  text-align: center;
  border: 1px solid transparent;
  border-radius: 4px;
  position: relative;
  padding: 0 3px;
}
.tl-c b {
  display: block;
  font: 650 14px/1.1 var(--mono);
  letter-spacing: -0.02em;
}
/* Two sub-lines: how many strikes qualify, then whether the side you need has
   a price. Both are counts of published rows; neither is a score. */
.tl-c i {
  display: block;
  font-style: normal;
  font: 10px/1.35 var(--mono);
  margin-top: 2px;
  opacity: 0.9;
}
.tl-c i + i {
  margin-top: 1px;
  opacity: 0.72;
}
/* traded — shaded by RECENCY, exactly as the coverage board shades. Colour on
   this page never means "good"; it only ever means "recently". */
.tl-c.traded {
  color: var(--bg);
  border-color: var(--ink);
}
.tl-c.traded b,
.tl-c.traded i {
  mix-blend-mode: luminosity;
  opacity: 1;
}
/* listed, feed present, nothing traded — a real absence, so a real outline. */
.tl-c.no_trades {
  border: 1px dashed var(--empty);
  color: var(--ink3);
}
/* the venue publishes no fill feed. Hatched, never blank, never zero. */
.tl-c.unknown {
  border: 1px solid var(--empty);
  color: var(--ink3);
  background: repeating-linear-gradient(
    45deg,
    var(--s2),
    var(--s2) 2px,
    transparent 2px,
    transparent 6px
  );
}
.tl-c.none {
  border-color: transparent;
  color: var(--ink3);
  background: none;
}
.tl-c.none i {
  font-size:10px;
  opacity: 0.65;
}
.tl-c:not(.none) {
  cursor: pointer;
}
.tl-c:not(.none):hover,
.tl-c:not(.none):focus-visible {
  outline: 2px solid var(--mint);
  outline-offset: -1px;
  z-index: 1;
}

/* ── hover card ─────────────────────────────────────────────────────── */
#tools-card {
  position: fixed;
  z-index: 60;
  width: 340px;
  max-width: calc(100vw - 16px);
  background: var(--bg);
  border: 1px solid var(--line2);
  border-radius: var(--r);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
  padding: 12px 13px;
  pointer-events: none;
  font: 11.5px/1.45 var(--sans);
  color: var(--ink);
}
#tools-card[hidden] {
  display: none;
}
.tc-h {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 12.5px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.tc-x {
  color: var(--ink3);
  font-weight: 400;
}
.tc-v {
  margin: 0 0 8px;
  font-size: 11.5px;
  line-height: 1.5;
}
.tc-v.live,
.tc-v.traded {
  color: var(--pos);
}
.tc-v.no_resting_side,
.tc-v.no_trades {
  color: var(--ink2);
}
.tc-v.unknown {
  color: var(--warn);
}
.tc-n {
  margin: 0 0 6px;
  font-size: 11px;
  color: var(--ink2);
}
.tc-n code {
  font: 10.5px var(--mono);
  color: var(--ink);
  background: var(--s1);
  padding: 1px 4px;
  border-radius: 3px;
}
table.tc-t {
  width: 100%;
  border-collapse: collapse;
  font: 10.5px/1.3 var(--mono);
  margin-top: 7px;
}
.tc-t th {
  text-align: right;
  color: var(--ink3);
  font-weight: 500;
  padding: 3px 0 5px;
  border-bottom: 1px solid var(--line);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size:10px;
}
.tc-t th:first-child,
.tc-t td:first-child {
  text-align: left;
}
.tc-t td {
  text-align: right;
  padding: 3px 0;
  color: var(--ink);
}
.tc-t td.muted {
  color: var(--ink3);
}
.tc-t td.warn {
  color: var(--warn);
}

/* ── legend + reading line ──────────────────────────────────────────── */
.tl-legend {
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 11px 0 0;
  font: 10px/1 var(--mono);
  color: var(--ink3);
  flex-wrap: wrap;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.tl-legend .lg i.solid {
  width: 11px;
  height: 11px;
  border-radius: 3px;
  display: inline-block;
  flex: none;
}
.tl-read {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--ink2);
  max-width: 104ch;
  line-height: 1.65;
}
.tl-read strong {
  color: var(--ink);
  font-weight: 650;
}
.tl-read .tl-warn {
  color: var(--warn);
}

/* ── plain tables inside sections ───────────────────────────────────── */
.tl-table-wrap {
  margin-top: 14px;
  overflow-x: auto;
}
table.tl-t {
  width: 100%;
  border-collapse: collapse;
  font: 11.5px/1.5 var(--mono);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.tl-t th {
  text-align: right;
  padding: 7px 11px;
  color: var(--ink3);
  font-weight: 700;
  border-bottom: 1px solid var(--line2);
  font-size:10px;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  background: linear-gradient(180deg, var(--s3), var(--s2));
}
.tl-t th:first-child,
.tl-t td:first-child {
  text-align: left;
}
.tl-t td {
  padding: 6px 11px;
  text-align: right;
  border-bottom: 1px solid var(--line);
}
.tl-t tbody tr:hover {
  background: var(--glass);
}
.tl-t td.tl-unknown {
  color: var(--warn);
  font-style: normal;
}
.tl-t tr.tl-nofeed td {
  opacity: 0.72;
}

/* ── endpoint cards ─────────────────────────────────────────────────── */
.tl-code {
  font: 10px/1.5 var(--mono);
  color: var(--ink);
  background: var(--s1);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 6px 8px;
  overflow-x: auto;
  white-space: pre;
  margin: 0;
}
.tl-copy {
  background: var(--s2);
  color: var(--ink2);
  border: 1px solid var(--line2);
  border-radius: 5px;
  padding: 4px 9px;
  font: 600 10px/1 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  align-self: flex-start;
}
.tl-copy:hover {
  background: var(--s3);
  color: var(--ink);
  border-color: var(--mint-dim);
}

/* ── controls ───────────────────────────────────────────────────────── */
.tl-hold {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.tl-hold label {
  font: 600 10px/1 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink3);
}
.tl-hold select {
  background: var(--s2);
  color: var(--ink);
  border: 1px solid var(--line2);
  border-radius: 5px;
  padding: 5px 9px;
  font: 11.5px/1 var(--mono);
  cursor: pointer;
}
.tl-hold select:hover {
  border-color: var(--mint-dim);
}
.tl-empty {
  padding: 24px 4px;
  color: var(--ink3);
  font-size: 11.5px;
}
.tl-src {
  margin: 12px 0 0;
  font: 10px/1.6 var(--mono);
  color: var(--ink3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

@media (max-width: 900px) {
  #tools-root .tl-body {
    grid-template-columns: 1fr;
  }
  .tl-rail {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
    padding: 10px;
    gap: 6px;
  }
  .tl-rail-h {
    display: none;
  }
  .tl-tab {
    flex: none;
    width: auto;
    white-space: nowrap;
  }
  .tl-tab i {
    display: none;
  }
  .tl-main {
    padding: 12px 10px 16px;
  }
}

/* ---- spreadsheet add-ins: the product of the Data section ---------------- */
.tl-addin h4{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.tl-pill{font:600 8.5px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;
  padding:4px 7px;border:1px solid var(--line2);border-radius:2px;color:var(--ink3)}
.tl-pill.on{color:var(--good);border-color:var(--good)}
/* Excel is not finished and the page says so rather than implying otherwise. */
.tl-warn{font-size:11.5px;color:var(--warn);border-left:2px solid var(--warn);
  padding-left:11px;margin:2px 0 0}
.tl-actions{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-top:12px}
.tl-btn{display:inline-block;font:600 11px/1 var(--sans);padding:8px 13px;
  border-radius:var(--r-ctl);background:var(--brand-fill);color:var(--on-brand);text-decoration:none;
  border:0}
.tl-btn:hover{background:var(--mint2)}
.tl-sub{margin:26px 0 0;font:600 10px/1 var(--mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink3)}

/* ---- Flow: the treemap's stylesheet was here -----------------------------
   Removed 2026-09-09 along with squarify(), CAT_HUE, renderCoverage() and
   wireFlow(). Flow is a ranked state matrix now: see .mx at the foot of this
   file, and the note above renderFlow() in tools.js for why a treemap could
   not survive a distribution with 94.1% of its mass in three rows. */
.tl-unk-inline { color: var(--warn); }

/* ---- Activity: the tape ------------------------------------------------- */
.tl-big { margin: 4px 0 2px; font: 700 21px/1 var(--sans); letter-spacing: -0.02em; }
.tl-sub2 { margin: 0; font-size: 11px; color: var(--ink3); line-height: 1.45; }
.tl-tape { font: 11px/1.5 var(--mono); font-variant-numeric: tabular-nums; }
.tl-tape td { padding: 5px 9px 5px 0; white-space: nowrap; }
.tl-dim { color: var(--ink3); }
/* Addresses are the point, but they must not out-shout what happened. */
.tl-addr { color: var(--ink3); font-size: 10px; cursor: help; }
.tl-barcell { width: 110px; }
.tl-barcell i { display: block; height: 7px; border-radius: 3px; background: var(--q4); }
.tl-big2 { margin: 5px 0 3px; font: 600 14.5px/1.3 var(--sans); color: var(--ink); }
.tl-addr2 { margin: 6px 0 0; font: 10px var(--mono); color: var(--ink3); cursor: help; }
.tl-note2 { margin: 8px 0 0; font-size: 11px; color: var(--ink3); }
.tl-note2 code { font: 10.5px var(--mono); color: var(--ink2); }

/* ---- Activity: 2x2 flow + live tape ------------------------------------- */
.fq-wrap { margin: 4px 0 22px; }
.fq-grid { display: grid; grid-template-columns: 78px 1fr 1fr; gap: 8px;
  align-items: stretch; }
.fq-hd { font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink3); align-self: end; padding-bottom: 4px; }
.fq-rh { font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink3); align-self: center; }
.fq { position: relative; border: 1px solid var(--line); border-radius: 6px;
  padding: 12px 13px 15px; overflow: hidden; background: var(--bg); }
/* The collar is a DIAGONAL, so the two cells that form it are shaded together.
   That is the whole reason this is a 2x2 and not a four-row table. */
.fq.diag { border-color: var(--mint-dim); background: var(--glass); }
.fq-l { display: block; font: 10px/1.2 var(--sans); color: var(--ink3); }
.fq-v { display: block; margin-top: 3px; font: 700 19px/1 var(--sans);
  letter-spacing: -.02em; color: var(--ink); }
.fq-s { display: block; margin-top: 3px; font: 10px/1 var(--mono); color: var(--ink3);
  font-style: normal; }
/* Width encodes share of premium, so the four cells are comparable by eye. */
.fq-bar { position: absolute; left: 0; bottom: 0; height: 3px; width: var(--w);
  background: var(--q4); }
.fq.diag .fq-bar { background: var(--mint); }

.fr-wrap { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.fr { display: grid;
  grid-template-columns: 42px 54px minmax(0,1fr) 96px 74px 92px 118px 96px;
  gap: 10px; align-items: baseline; padding: 8px 2px;
  border-bottom: 1px solid var(--line);
  font: 11.5px/1.4 var(--mono); font-variant-numeric: tabular-nums;
  animation: frIn .28s ease-out both; }
@keyframes frIn { from { opacity: 0; transform: translateY(-3px) } to { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .fr { animation: none } }
.fr:hover { background: var(--glass); }
.fr-age { color: var(--ink3); font-size: 10px; }
.fr-a { font-weight: 700; color: var(--ink); }
.fr-w { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fr-w.put { color: var(--v1); } .fr-w.call { color: var(--v3); }
.fr-w.sell { opacity: .85; }
.fr-d, .fr-s { color: var(--ink3); font-size: 10.5px; }
.fr-p { font-weight: 700; color: var(--ink); text-align: right; }
.fr-v { color: var(--ink2); font-size: 10.5px; display: flex; flex-direction: column; }
.fr-v i { font-style: normal; font-size:10px; color: var(--ink3); }
.fr-c { color: var(--ink3); font-size:10px; cursor: help; }
/* Under five minutes old: this is the "something is happening" signal. */
.fr.hot { animation: frFlash 2.6s ease-out; }
.fr.hot .fr-age { color: var(--mint); font-weight: 700; }
@keyframes frFlash { from { background: var(--mint-dim) } to { background: transparent } }

/* ---- in-browser Python sandbox ------------------------------------------ */
.py-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; }
.py-chip { font: 10.5px/1 var(--mono); padding: 7px 10px; border-radius: 5px;
  border: 1px solid var(--line2); background: var(--bg); color: var(--ink2);
  cursor: pointer; }
.py-chip:hover { border-color: var(--mint); color: var(--mint); }
.py-chip.lead { background: var(--s1); color: var(--ink); font-weight: 600; }
.py-box { border: 1px solid var(--line2); border-radius: 7px; overflow: hidden; }
#py-src { width: 100%; border: 0; padding: 14px; resize: vertical; min-height: 76px;
  font: 12px/1.6 var(--mono); color: var(--ink); background: var(--bg);
  outline: none; display: block; }
.py-bar { display: flex; align-items: center; gap: 11px; padding: 0 14px 12px; }
.py-hint { font: 10px/1 var(--mono); color: var(--ink3); white-space: nowrap; }
/* The "+" was a "/" and read as a line break at 9.5px. */
.py-hint b { font-weight: 700; color: var(--ink2); }
.py-ghost { font: 10.5px/1 var(--sans); padding: 8px 11px; border-radius: 5px;
  border: 1px solid var(--line2); background: none; color: var(--ink3); cursor: pointer; }
.py-ghost:hover { color: var(--ink); border-color: var(--ink3); }
/* Load state, so a 15 MB download is never mistaken for a hang. */
.py-state { margin-left: auto; font: 10px/1 var(--mono); text-transform: uppercase;
  letter-spacing: .09em; color: var(--ink3); border: 1px solid var(--line2);
  border-radius: 99px; padding: 5px 9px; white-space: nowrap; }
.py-state.load { color: var(--warn); border-color: var(--warn); }
.py-state.ok { color: var(--good); border-color: var(--good); }
.py-state.err { color: var(--bad); border-color: var(--bad); }
/* The console is the one place on this page that earns a dark surface: it is a
   terminal, and monospace output on paper reads as a quote block. */
.py-out { background: #14161b; color: #e8e4d8; padding: 12px 14px;
  max-height: 340px; overflow: auto; font: 11.5px/1.55 var(--mono);
  border-top: 1px solid var(--line2); }
/* pre-wrap, not pre: a wide DataFrame must wrap rather than force the
   whole console to scroll sideways. */
.py-line { white-space: pre-wrap; word-break: break-word; }
.py-line.cmd { color: #63b3f7; }
.py-line.dim { color: #8d887b; }
.py-line.ok { color: #0ca30c; }
.py-line.err { color: #ec835a; }

/* ---- DataFrame output ---------------------------------------------------
   Pandas text output wraps into backslash-continued blocks as soon as a frame
   is wider than the console. A real table scrolls instead of wrapping. */
.py-tab { margin: 8px 0 4px; overflow-x: auto; }
.py-tab table { border-collapse: collapse; font: 10.5px/1.5 var(--mono);
  color: #e8e4d8; width: max-content; min-width: 100%; }
.py-tab thead th { text-align: right; font-weight: 700; color: #63b3f7;
  padding: 5px 11px 7px 0; border-bottom: 1px solid #2c3038; white-space: nowrap; }
.py-tab thead th:first-child { text-align: left; }
.py-tab td { text-align: right; padding: 4px 11px 4px 0; white-space: nowrap;
  border-bottom: 1px solid #1e2128; }
.py-tab td:first-child { text-align: left; color: #b5b0a0; }
.py-tab tbody tr:hover td { background: #1b1e25; }
.tl-sub.first { margin-top: 4px; }

/* Disclosure under the products matrix. Same treatment as the venue panel's
   "Sources & method": the detail stays reachable without occupying a screenful
   above a matrix that already carries its own legend. */
.tl-more { margin: 6px 0 0; }
.tl-more > summary { cursor: pointer; color: var(--ink3); font-size: 11.5px; padding: 2px 0; }
.tl-more > summary:hover { color: var(--ink2); }
.tl-more > summary:focus-visible { outline: 1.5px solid var(--mint2); outline-offset: 2px; }
.tl-more[open] > summary { margin-bottom: 6px; }

/* ---- the Moneyness function set -----------------------------------------
   This surface read as three "export options" with some helper code attached.
   It is a named library — 11 spreadsheet functions, 21 Python functions, one
   metric vocabulary shared by both — and the page now says so before it offers
   a download. */
.fn-hero {
  border: 1px solid var(--line2); border-radius: var(--r);
  background: linear-gradient(180deg, var(--s1), var(--bg));
  padding: 18px 20px; margin: 14px 0 20px;
}
.fn-sig { font: 20px/1.4 var(--mono); letter-spacing: -.01em; }
/* The name is the trademark. It carries the accent; nothing else in the
   signature competes with it. */
.fn-brand { color: var(--mint); font-weight: 700; }
.fn-paren, .fn-sep { color: var(--ink3); }
.fn-arg { color: var(--ink); }
.fn-ret { color: var(--good); margin-left: 10px; font-size: 16px; }

.fn-stats { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 14px;
  padding-top: 13px; border-top: 1px solid var(--line);
  font: 11.5px/1.5 var(--mono); color: var(--ink3); }
.fn-stats b { color: var(--ink); font-weight: 600; }
.fn-stats .fn-rule b { color: var(--mint); }
.fn-stats code { color: var(--ink2); }

.fn-cols { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }
.fn-h { font: 12px/1 var(--sans); color: var(--ink2); margin: 0 0 8px;
  display: flex; align-items: baseline; gap: 8px; }
.fn-h code { font: 600 12.5px/1 var(--mono); color: var(--mint); }
.fn-h i { font-style: normal; font-size: 10.5px; color: var(--ink3);
  text-transform: uppercase; letter-spacing: .06em; }

.fn-t { display: flex; flex-direction: column; }
.fn-r { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px;
  padding: 6px 8px; border-radius: 4px; }
.fn-r:hover { background: var(--s1); }
.fn-n { font: 11.5px/1.4 var(--mono); color: var(--ink); word-break: break-word; }
.fn-n i { font-style: normal; color: var(--ink3); }
.fn-d { font-size: 11px; line-height: 1.45; color: var(--ink3); }
.fn-plots { margin: 8px 0 0; font-size: 11px; color: var(--ink3); line-height: 1.9; }
.fn-plots code { font-size: 10.5px; background: var(--s2); padding: 2px 5px;
  border-radius: 3px; color: var(--ink2); }

@media (min-width: 620px) {
  .fn-r { grid-template-columns: 260px minmax(0, 1fr); gap: 12px; align-items: baseline; }
}

/* ===========================================================================
   THE LIVE TICKER — refreshing board, not a marquee
   ===========================================================================
   WHY THIS BLOCK IS IN tools.css AND NOT style.css
   ------------------------------------------------
   The ticker's markup and its original marquee rules live in style.css, which
   this track does not own. tools.css is linked AFTER style.css in index.html
   (positions 1 and 3), so every rule below wins on cascade order at identical
   specificity — no !important anywhere. If these rules are ever folded back
   into style.css, delete this whole block in the same commit; two stylesheets
   describing one component is how a surface starts rendering one thing and
   measuring another.

   The marquee is gone. It translated a duplicated track to -50% forever, which
   made every item a moving target you could not click and forced a reader who
   missed a fill to wait a full lap for it. The board keeps the fills still and
   rewrites them in place. Selectors verified against the live DOM before
   shipping: #ticker 1, .tk-row 1, .tk-i 6. */

.ticker {
  /* Reserved, not derived. The row's content changes every 20s and its widest
     line ("bought a floor at $65,000" vs "sold the upside") differs by ~90px;
     a height that follows the content would jog the entire page under the
     masthead on every poll. Fixed box, ellipsis inside. */
  height: 46px;
  min-height: 46px;
  overflow: hidden;
  background: var(--s1);
  border-bottom: 1px solid var(--line);
}
/* The edge fades existed to stop items being guillotined as they slid out.
   Nothing slides now, so all they do is wash out the first and last slot —
   which are two of only six. */
.ticker::before, .ticker::after { content: none; }

.tk-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  height: 100%;
  width: 100%;
}

/* A real <button>, so Tab reaches it and Enter/Space activate it without this
   file reimplementing either. */
.tk-i {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  height: 100%;
  padding: 0 14px;
  margin: 0;
  text-align: left;
  background: transparent;
  border: 0;
  border-right: 1px solid var(--line);
  border-left: 2px solid transparent;
  color: var(--ink2);
  font: 11.5px/1 var(--mono);
  cursor: pointer;
}
.tk-row .tk-i:last-child { border-right: 0; }

.tk-l1, .tk-l2 {
  display: flex;
  align-items: baseline;
  gap: 7px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The two facts that identify the fill get the ink; the context is quieter. */
.tk-a { font: 700 12px/1.25 var(--mono); color: var(--ink); flex: none; }
.tk-act {
  font: 600 11.5px/1.25 var(--mono); font-style: normal;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.tk-act.put { color: var(--v1); }
.tk-act.call { color: var(--v3); }
.tk-act.sell { opacity: .85; }
.tk-age { font-style: normal; color: var(--ink3); font-size: 10px; flex: none; }
.tk-p { font-style: normal; font-weight: 700; color: var(--ink); font-size: 10.5px; flex: none; }
.tk-v { font-style: normal; color: var(--ink3); font-size: 10px; flex: none; }
.tk-d { font-style: normal; color: var(--ink3); font-size: 10px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.tk-i:hover { background: var(--glass); color: var(--ink); }
.tk-i:hover .tk-v { color: var(--ink2); }
.tk-i:focus-visible {
  /* ui.css owns the app's one focus ring but its selector list predates this
     control, and that file is not ours to extend. Same token, same 2px, so a
     keyboard user sees no seam between here and everything else. */
  outline: 2px solid var(--mint2);
  outline-offset: -2px;
  border-radius: 4px;
  background: var(--glass);
}
/* A fill with nowhere to drill into is not a control. It stays legible — the
   information is still true — but it stops pretending to be clickable. */
.tk-i[aria-disabled="true"] { cursor: default; }
.tk-i[aria-disabled="true"]:hover { background: transparent; color: var(--ink2); }

/* NEW ARRIVAL — the fill FADES IN rather than being swapped.
   ---------------------------------------------------------
   This is the DexScreener finding applied to a polled feed. Their tape reads
   as alive not because it arrives over a WebSocket but because `color` and
   `background-color` sit in their global transition list, so a tick fades into
   its new value instead of cutting to it. A cut reads as a page redrawing; a
   fade reads as a number moving. We poll on a 20s interval, so the transport
   is not the lever — the transition is.

   Only colour and background-color transition. Never width, height, transform
   or position: the audit found 170 animated elements in this app and zero
   animating an expensive property, and a geometric flash here would also
   reintroduce exactly the layout shift the marquee rewrite exists to remove.

   The tint is a class JS adds and removes 2.4s later — no keyframe, because
   ui.css crushes every animation to .01ms under prefers-reduced-motion and a
   keyframed flash would be silently dead there rather than deliberately off. */

/* Direction is information, so the flash carries it. --good / --bad are used
   here as SIDE, not as judgement: a taker lifting an offer is not "good" and a
   taker hitting a bid is not "bad" — this is the two-colour blotter convention
   for which way the trade went. It is a different axis from the .tk-act
   put/call hues above, which say what the CONTRACT is, and the two never land
   on the same element. A fill whose side the venue did not report gets the
   neutral accent instead of being assigned a direction we do not have. */
.tk-i.tk-fresh { background: var(--mint-dim); border-left-color: var(--mint); }
.tk-i.tk-fresh .tk-a { color: var(--mint); }
.tk-i.tk-fresh.tk-buy { background: var(--glass); border-left-color: var(--good); }
.tk-i.tk-fresh.tk-buy .tk-a,
.tk-i.tk-fresh.tk-buy .tk-p { color: var(--good); }
.tk-i.tk-fresh.tk-sell { background: var(--glass); border-left-color: var(--bad); }
.tk-i.tk-fresh.tk-sell .tk-a,
.tk-i.tk-fresh.tk-sell .tk-p { color: var(--bad); }

@media (prefers-reduced-motion: no-preference) {
  /* .5s in, and the same .5s back to the resting token when JS drops the
     class. A highlight that never settles stops being a signal. */
  .tk-i { transition: background-color .5s ease, border-left-color .5s ease; }
  .tk-i .tk-a, .tk-i .tk-p, .tk-i .tk-act { transition: color .5s ease; }
}
@media (prefers-reduced-motion: reduce) {
  /* Disabled ENTIRELY, not shortened. A tint that appears and vanishes on its
     own is still something moving on the page to a reader who asked for none.
     The arrival is not lost: every slot carries an aria-label naming the fill,
     and the age field still reads "12s". */
  .tk-i, .tk-i .tk-a, .tk-i .tk-p, .tk-i .tk-act { transition: none; }
  .tk-i.tk-fresh,
  .tk-i.tk-fresh.tk-buy,
  .tk-i.tk-fresh.tk-sell { background: transparent; border-left-color: transparent; }
  .tk-i.tk-fresh .tk-a { color: var(--ink); }
  .tk-i.tk-fresh.tk-buy .tk-a, .tk-i.tk-fresh.tk-sell .tk-a { color: var(--ink); }
  .tk-i.tk-fresh.tk-buy .tk-p, .tk-i.tk-fresh.tk-sell .tk-p { color: var(--ink); }
  /* style.css switches the ticker to overflow-x:auto under this setting — that
     was for the paused marquee, and here it would let the box gain a scrollbar
     and change height. */
  .ticker { overflow: hidden; }
}

/* Slots are dropped whole rather than squeezed. A six-column row at 640px
   gives each fill 105px, which truncates "bought a floor" to "bought a…" and
   the slot stops saying anything at all. */
@media (max-width: 1180px) { .tk-i:nth-child(n + 6) { display: none; } }
@media (max-width: 980px)  { .tk-i:nth-child(n + 5) { display: none; } }
@media (max-width: 760px)  { .tk-i:nth-child(n + 4) { display: none; } }
@media (max-width: 560px)  { .tk-i:nth-child(n + 3) { display: none; } }

/* ===========================================================================
   THE TICKER POPOVER — the fix for the click that used to navigate
   ===========================================================================
   Clicking a ticker slot called drillTo() and threw the reader onto the venue
   view. It opens this instead.

   WHY A POPOVER HERE WHEN THE TABLE USES COLUMNS. The strip is 46px tall with
   a stated height, overflow:hidden and the entire application immediately
   beneath it — there is no room to expand INTO, and taking room would jog
   every pixel of the page below on a 20s timer. position:fixed on <body> is
   the one mount that cannot touch the strip's geometry: it is out of flow, so
   the ticker's box measures identical whether this is open or closed.

   Same house pattern as #cov-card and #cw-pop (position:fixed, --z-pop, one
   reused element) with one deliberate difference: those are pointer-events:
   none because they are hover cards, and this one holds controls. */
.tk-pop {
  position: fixed;
  z-index: var(--z-pop);
  width: 340px;
  max-width: calc(100vw - 16px);
  background: var(--bg);
  border: 1px solid var(--line2);
  border-radius: var(--r);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .16);
  padding: 11px 13px 12px;
  font: 11.5px/1.45 var(--sans);
  color: var(--ink);
}
.tk-pop[hidden] { display: none; }

.tk-pop-h {
  display: flex; align-items: baseline; gap: 7px; min-width: 0;
  padding-bottom: 8px; margin-bottom: 9px;
  border-bottom: 1px solid var(--line);
}
.tk-pop-h b { font: 700 13px/1.2 var(--mono); color: var(--ink); flex: none; }
.tk-pop-h span {
  font: 600 11.5px/1.2 var(--mono); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Same two hues the table and the ticker use for what the CONTRACT is, so one
   vocabulary covers all three surfaces. */
.tk-pop-h span.put { color: var(--v1); }
.tk-pop-h span.call { color: var(--v3); }
.tk-pop-h span.sell { opacity: .85; }
.tk-pop-h i {
  margin-left: auto; flex: none; font-style: normal;
  font: 10px/1 var(--mono); color: var(--ink3);
  text-transform: uppercase; letter-spacing: .06em;
}
/* One column at 340px. The shared fact grid auto-fits at 140px, which would
   put two 150px columns here and truncate "left when traded". */
.tk-pop .tw-x-facts { grid-template-columns: 1fr; gap: 3px; }
.tk-pop .tw-x-cps { margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line); }
.tk-pop .tw-x-act {
  flex-direction: row; flex-wrap: wrap; margin-top: 11px;
  padding-top: 10px; border-top: 1px solid var(--line);
}
.tk-pop .tw-x-b { flex: 1 1 auto; white-space: normal; text-align: left; }

/* ===========================================================================
   THE FILL TABLE — every column does its own thing
   ===========================================================================
   Same cascade note as the ticker block above: the tape's original two-line
   row rules live in style.css, which this track does not own, and tools.css is
   linked after it. The .tw-* namespace does not fight them.

   WHAT CHANGED AND WHY IT IS A DIFFERENT TABLE NOW
   -----------------------------------------------
   1. It has its own SECTION. As a column beside the venue grid the panel
      measured 428px, which is why the old layout hid six of eleven columns at
      the width the app is actually read in. In the Trades view it measures
      ~1,234px and every column fits, so nothing has to be dropped to make the
      thing legible.

   2. THE TRACKS ARE FRACTIONAL. Not one fixed px width in the layout: the
      table breathes with the panel instead of scrolling horizontally, and
      there is no width in this file that has to be kept in sync with a number
      in tape.js.

   3. THE TYPE SCALE IS THE HIERARCHY, NOT COLOUR. Four sizes in a row and only
      ONE thing is 15px: the PREMIUM. Your eye lands on the money first, every
      row, without anything shouting in red. Everything else is 12px/600 for a
      live value, 11px/400 grey for its sub-line, 13px/400 grey for the age.

   4. THREE GREYS, NOT ONE. --ink for values, --ink2 for context and for the
      column labels, --ink3 for ABSENT data — so an empty cell recedes instead
      of reading as content. Most tables use one grey.

   5. NO EXPANSION, NO MODAL. Every fact the old disclosure panel held is in a
      column: the contract terms stacked under the action, the notional under
      the size, the cover share under the premium, both counterparties in their
      own columns. Nothing here opens a detail view and nothing here navigates.

   THE ONE RULE THIS BLOCK STILL EXISTS FOR
   ----------------------------------------
   --tape-cols is declared ONCE, on .tw-grid. The header and every row read it
   through grid-template-columns. Nothing measures a width in JavaScript and
   nothing writes a per-cell style, so the header physically cannot drift out
   of alignment with the body.

   CONTAINER QUERIES, NOT MEDIA QUERIES. The panel's own width is what decides
   how many columns fit; the viewport is not it. Measured on the live page, the
   tape panel got WIDER as the window got NARROWER back when it shared a row
   with the grid, and a viewport media query got that exactly backwards. */

/* SCROLL ANCHORING OFF, and only here.
   ------------------------------------
   Chrome anchors scrolling by default: when content changes ABOVE the
   viewport it silently adjusts scrollTop to keep what you are looking at
   still. On almost every list that is a gift. On this one it is a fight —
   new fills PREPEND every 20s, so the browser's heuristic and renderTable's
   own scroll restoration both try to correct the same displacement and the
   list micro-stutters. With it off, every pixel of scrollTop is tape.js's
   decision and the correction happens exactly once.

   Deliberately NOT applied anywhere else. The reference implementation this
   was taken from applies it per ROW, inline, from its virtualiser — it is
   worth having on a live-mutating list and nowhere else, and sprinkling it
   would disable a genuinely useful default across the app. Our tape is 45
   rows and is NOT virtualised: a fixed-length list that fits in one DOM gains
   nothing from windowing but loses find-in-page. */
#tape { overflow-anchor: none; }

.tw {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* THE BLIND SPOT, stated where the table is, not in a footnote. Four of six
   indexed venues publish no fill feed; a table that looked complete would
   imply they had no trades. --warn rather than --ink3 because a reader who
   skims past this reads the table as a census. */
.tw-blind {
  margin: 2px 0 9px;
  padding: 7px 10px;
  border-left: 2px solid var(--warn);
  background: var(--s1);
  font: 10.5px/1.5 var(--sans);
  color: var(--ink3);
}
.tw-blind b { color: var(--ink2); font-weight: 600; }

/* ---- window tabs -------------------------------------------------------- */
.tw-tabs { display: flex; gap: 4px; margin: 0 0 7px; flex-wrap: wrap; }
.tw-tab {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 4px 9px; border: 0; border-radius: 5px;
  background: transparent; color: var(--ink3);
  font: 10px/1 var(--mono); cursor: pointer;
  transition: background-color var(--t), border-color var(--t), color var(--t);
}
.tw-tab b { font-weight: 700; letter-spacing: .04em; }
.tw-tab i { font-style: normal; font-size:10px; opacity: .8; font-variant-numeric: tabular-nums; }
.tw-tab:hover { color: var(--ink); border-color: var(--line2); background: var(--s1); }
.tw-tab[aria-selected="true"] {
  color: var(--mint); border-color: var(--mint2); background: var(--glass);
}
.tw-tab:focus-visible { outline: 2px solid var(--mint2); outline-offset: 2px; }

/* ---- what the columns do, said before you click one --------------------- */
/* A table where every column behaves differently is only an improvement if the
   reader can find that out without clicking each one to see. */
.tw-hint {
  margin: 0 0 8px;
  font: 10.5px/1.55 var(--sans);
  color: var(--ink3);
  max-width: 92ch;
}
.tw-hint b { color: var(--ink2); font-weight: 600; }

/* ---- the active filters ------------------------------------------------- */
/* Filters COMPOSE, so the bar has to show all of them at once — a reader who
   narrowed to XAUT three clicks ago must be able to see why the table is
   short, and undo exactly one of the reasons. */
.tw-fbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0 0 8px; padding: 7px 8px;
  border: 0; border-radius: 6px;
  background: var(--s1);
}
.tw-fbar[hidden] { display: none; }
.tw-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 4px 8px; border-radius: var(--r-ctl); cursor: pointer;
  border: 0; background: var(--s2); color: var(--ink);
  font: 10.5px/1 var(--mono);
}
.tw-chip i { font-style: normal; color: var(--ink3); text-transform: uppercase;
  letter-spacing: .05em; font-size:10px; }
.tw-chip b { color: var(--mint); font-weight: 700; }
.tw-chip:hover { border-color: var(--mint); }
.tw-fn { font: 10.5px/1 var(--mono); color: var(--ink2); margin-left: 2px; }
.tw-fn b { color: var(--ink); font-weight: 700; }
.tw-fmode, .tw-clear {
  margin-left: auto; padding: 4px 8px; border-radius: 5px; cursor: pointer;
  border: 1px solid var(--line2); background: transparent; color: var(--ink3);
  font: 10.5px/1 var(--mono);
}
.tw-clear { margin-left: 0; }
.tw-fmode:hover, .tw-clear:hover { color: var(--ink); border-color: var(--mint2); }
.tw-fmode[aria-pressed="true"] { color: var(--mint); border-color: var(--mint2); }
.tw-chip:focus-visible, .tw-fmode:focus-visible, .tw-clear:focus-visible {
  outline: 2px solid var(--mint2); outline-offset: 2px;
}

/* ---- the grid ----------------------------------------------------------- */
/* THE COLUMN LAYOUT AND THE HIDE-SET MUST BE EDITED TOGETHER. Grid places
   every child, so a cell that is not wanted at a given width has to be
   display:none — leaving it in would push the row onto a second line. The two
   halves of each tier sit adjacent below for that reason; they are one
   decision written twice.

   ALL FRACTIONAL. The ratios are authored, not measured: the action column
   carries the longest string in the table ("bought the upside") plus its
   contract sub-line, the two address columns carry an identicon and 13
   characters, and the age carries three. */
.tw-grid {
  --tape-cols: 1fr minmax(104px, 1.9fr) 3.4fr 2fr var(--tape-share);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* .tw-scoped, and the shown state is `flex`, not `block` — BOTH HALVES OF THAT
   ARE BUG FIXES, caught by a container-tier test that found all nine columns
   rendering at 760px.
   1. `.c-exp{display:none}` and `.tw-c{display:flex}` are both one class, so
      the later rule in the file won and nothing ever hid. Scoping to `.tw`
      makes the hide-set specific enough to mean what it says.
   2. Re-showing with `display:block` would strip the cell's flex layout, so
      the stacked cells would collapse into inline text in every tier except
      the widest. */
.tw .c-exp, .tw .c-size, .tw .c-venue, .tw .c-taker, .tw .c-maker { display: none; }

@container (min-width: 560px) {
  .tw-grid { --tape-cols: 1fr minmax(104px, 1.9fr) 3.2fr 2fr 1.8fr 1.6fr var(--tape-share); }
  .tw .c-exp, .tw .c-venue { display: flex; }
}
@container (min-width: 820px) {
  .tw-grid { --tape-cols: 1fr minmax(104px, 1.9fr) 3fr 1.8fr 2fr 2fr 1.5fr 2.3fr var(--tape-share); }
  .tw .c-size, .tw .c-taker { display: flex; }
}
@container (min-width: 1060px) {
  /* All nine. At the ~1,234px the Trades section actually renders at, the
     action column resolves to ~241px, which is what "bought the upside" plus
     "$65,000 · +1.1%" needs without either line truncating. */
  .tw-grid { --tape-cols: 1fr minmax(104px, 1.9fr) 3fr 1.8fr 2fr 2fr 1.5fr 2.3fr 2.3fr var(--tape-share); }
  .tw .c-maker { display: flex; }
}

.tw-head, .tw-row {
  display: grid;
  /* The one read of the one variable. Both selectors, one source. */
  grid-template-columns: var(--tape-cols);
  align-items: stretch;
  min-width: 0;
}
.tw-head {
  /* top:-1px, NOT 0. At fractional browser zoom a sticky header pinned at 0
     leaves a sub-pixel seam through which the first row's top border bleeds.
     One pixel of overlap costs nothing and removes the seam at every zoom.

     z-index:2 is explicit rather than auto, so stacking here is a decision and
     not an accident of DOM order. It sits above the rows (auto) and below the
     app's sticky section rail (nav.css, z-index 5).

     The opaque background is LOAD-BEARING, not styling: without it the rows
     scroll visibly underneath the header text. */
  position: sticky; top: -1px; z-index: 2;
  /* MEASURED, not decorative. The rows carry a 2px transparent left border as
     the slot for the freshness marker; without the identical border here the
     header's content box is 2px wider than every row's and the fractional
     tracks resolve differently in the two — the exact hairline misalignment
     this single-variable layout exists to make impossible. */
  border-left: 2px solid transparent;
  background: var(--bg);
  border-bottom: 1px solid var(--line2);
}
/* THE LABELS ARE --ink2, one step above the "—" that marks absent data. That
   ordering is the point of the three-grey ladder: an empty cell must recede
   BELOW the furniture, not sit level with it. */
.tw-head .tw-c {
  align-items: center;
  padding: 7px 16px;
  font: 600 8.5px/1.3 var(--mono);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tw-body { display: flex; flex-direction: column; min-width: 0; }

.tw-row {
  position: relative;
  /* A ROW MUST BE PHYSICALLY UNABLE TO CHANGE HEIGHT. Not "should not" — the
     height is stated, and every cell is nowrap + overflow:hidden, so there is
     no content that CAN make a row taller. Premiums on the live feed span
     $1.80 to $1,949 and addresses truncate: one value wrapping to a second
     line would push every row under it down mid-scroll, which on a list that
     reprints every 20s reads as the page shivering.

     44px, from a measured 43.5px. Half a pixel is not worth a fractional row
     height: at 1x DPR a 43.5px row lands every second border on a half pixel
     and the hairlines render at two different weights down the page. */
  height: 44px;
  border-bottom: 1px solid var(--line);
  border-left: 2px solid transparent;
  font-variant-numeric: tabular-nums;
  color: var(--ink2);
  text-align: left;

  /* ---- THE ROW IS ITS OWN BAR CHART ------------------------------------
     One inline custom property per row (--tw-bar, a percentage), one gradient
     here. The bar is this fill's premium as a share of the largest premium on
     the page. BOTH STOPS SIT AT THE SAME PERCENTAGE, which is what makes it a
     hard-edged bar and not a fade — a gradient that ramps has no readable end,
     and the end IS the measurement.

     THE PERCENTAGE IS INLINE AND THE COLOUR IS NOT, which is the one place
     this departs from the reference implementation, on purpose: theirs inlines
     the whole `linear-gradient(...rgba(72,187,120,0.10)...)` per row, and a
     hard-coded rgba cannot follow a theme. Ours inlines the one number that is
     per-row and takes the colour from --good / --bad, so the same markup is
     correct on the paper ground and the near-black one.

     background-image, NOT the background shorthand, and every state below sets
     background-COLOR for the same reason: the shorthand resets background-image
     to none, so a hover written as `background:` would silently erase the bar
     of whichever row you happened to be pointing at. */
  --tw-bar: 0%;
  background-image: linear-gradient(to right,
    var(--tw-bar-ink) var(--tw-bar), rgba(0, 0, 0, 0) var(--tw-bar));
  background-repeat: no-repeat;
}

/* THE OPTICAL CORRECTION, and it is not a rounding error. Red is perceptually
   heavier than green at equal opacity — an equal-alpha pair makes the sells
   scream and turns a balanced tape into a sea of red. The reference dials red
   back ~40% and so does this: buys 10%, sells 6%.

   --good and --bad are used here as SIDE, not as judgement: a taker lifting an
   offer is not "good". This is the two-colour blotter convention for which way
   the trade went, the same axis the ticker's arrival flash uses.

   A fill whose side the venue did not report gets the neutral --mid rather
   than being assigned a direction we do not have. */
.tw-row { --tw-bar-ink: color-mix(in srgb, var(--mid) 9%, transparent); }
.tw-row[data-side="buy"]  { --tw-bar-ink: color-mix(in srgb, var(--good) 10%, transparent); }
.tw-row[data-side="sell"] { --tw-bar-ink: color-mix(in srgb, var(--bad) 6%, transparent); }
/* Dialled back on the dark ground: the same alpha does not carry the same
   weight lightening a near-black surface as it does darkening paper, and the
   dark theme's --good (#2dd4bf) and --bad (#f87171) are both far brighter than
   their paper counterparts. The 10:6 ratio between them is preserved. */
:root[data-theme="dark"] .tw-row { --tw-bar-ink: color-mix(in srgb, var(--mid) 12%, transparent); }
:root[data-theme="dark"] .tw-row[data-side="buy"]  { --tw-bar-ink: color-mix(in srgb, var(--good) 8%, transparent); }
:root[data-theme="dark"] .tw-row[data-side="sell"] { --tw-bar-ink: color-mix(in srgb, var(--bad) 4.8%, transparent); }

.tw-row:hover { background-color: var(--glass); }

/* ── THE TICKET RAIL ───────────────────────────────────────────────────────
   64.8% of the premium on this tape is multi-leg, and the table drew every leg
   as an unrelated trade. The legs of one ticket share a timestamp to the
   millisecond, so they are always a contiguous run of rows and a continuous
   left rail is enough to bind them: no reordering, no nesting, no grouping
   header that would break the fixed 44px row.

   IT COSTS NO LAYOUT. .tw-row already reserves `border-left: 2px solid
   transparent`, so this only fills in a colour that was already taking up
   space. Using box-shadow instead would have collided with the buy/sell hover
   rail at instrument.css:1321, which is an inset 2px on the same edge.

   The rail is deliberately NEUTRAL, not the side colour. A ticket's legs point
   opposite ways on purpose, so colouring the rail by direction would assert a
   direction for the structure that no single leg has. */
.tw-row[data-ticket] { border-left-color: var(--ink3); }

/* The end caps make a run of two read as a pair rather than as two rows that
   happen to be adjacent. 3px, drawn inside the reserved border box. */
.tw-row[data-ticket-pos="first"]::before,
.tw-row[data-ticket-pos="last"]::before {
  content: ""; position: absolute; left: -2px; width: 2px; height: 3px;
  background: var(--ink2);
}
.tw-row[data-ticket-pos="first"]::before { top: -1px; }
.tw-row[data-ticket-pos="last"]::before { bottom: -1px; }

/* THE LABEL SAYS IT ONCE PER TICKET, on the first leg. Repeating "4-leg
   ticket" on all four rows is the noise the rail exists to remove. It is a
   real element rather than a title attribute, because a title is not a
   disclosure and does not exist on a phone. */
/* A CUT TICKET SAYS SO, AND SAYS IT ON THE SUB-LINE. Drawn as ::after on the
   stack's second line rather than as an element beside .stk: a sibling of .stk
   begins a new line, and .tw-row is height:44px with overflow:hidden, so it was
   clipped and collided with the line above. A DOM check comparing right edges
   passed that; only the screenshot caught it.

   The text is written by applyFilters into data-tk-partial, so the label is
   recomputed against what is actually VISIBLE on every paint and every filter
   change, and cannot go stale against the rows beside it. */
/* The per-row label this replaced never rendered: attr() reads the attribute
   off the element the pseudo belongs to, not off an ancestor, so content
   resolved to a bare space. And there was nowhere to put it anyway, the
   sub-line being 151.6px of content in a 151.6px ellipsised box. The sentence
   moved to the filter bar, which is the only place it is ever relevant. */
.tw-fcut { color: var(--ink3); max-width: 62ch; line-height: 1.4; }
.tw-fcut b { color: var(--ink2); font-weight: 600 !important; }

/* A SOLID RAIL MEANS THE WHOLE TRADE IS HERE. Dashed means a filter or the
   window is holding legs back, so counting the railed rows would understate
   it. The two states are never the same mark, because "this is the trade" and
   "this is part of a trade" are the distinction the rail exists to carry. */
.tw-row[data-tk-cut] { border-left-style: dashed; }


/* ---- cells --------------------------------------------------------------
   nowrap + hidden on EVERY cell. This is the other half of the fixed height:
   the row states how tall it is and no cell is capable of arguing. */
.tw-c {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: 4px 16px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Numbers right-align so the digits line up regardless of length — with the
   app-wide tabular-nums that makes a column of premiums scannable as a shape
   before it is read as values. */
.tw-c.num, .tw-head .tw-c.num { align-items: flex-end; text-align: right; }

/* THE TYPE SCALE. Only .c-prem .p is 15px, anywhere in this table. */
.stk { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.tw-c.num .stk { align-items: flex-end; }
.stk .p, .c-asset b {
  font: 600 12px/1.3 var(--mono); color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stk .s {
  font: 400 11px/1.3 var(--mono); color: var(--ink2);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* THE ONE 15px VALUE IN THE ROW. The eye lands on the money first, every row,
   with no colour doing the work. */
.c-prem .stk .p { font: 600 15px/1.2 var(--mono); }
/* The separator is its OWN dimmed span, so "$65,000 · +1.1%" reads as two
   facts rather than one string. */
.sep { font-style: normal; color: var(--ink3); padding: 0 4px; }
/* Age is the quietest live value in the row — it is the thing you scan past. */
.c-age { font: 400 13px/1.3 var(--mono); color: var(--ink2); }
/* ABSENT DATA IS THE DIMMEST THING ON THE PAGE, dimmer than the column labels,
   so an empty cell recedes instead of reading as content. */
.tw-none { font: 400 12px/1.3 var(--mono); font-style: normal; color: var(--ink3); cursor: help; }

/* Same put/call hues the ticker and the popover use, so one vocabulary covers
   all three surfaces. This says what the CONTRACT is; the row's bar says which
   way the trade went, and the two never land on the same element. */
.c-act .p { color: var(--ink); }
.c-act .put .p, .tw-b.put .p { color: var(--v1); }
.c-act .call .p, .tw-b.call .p { color: var(--v3); }
.tw-b.sell .p { opacity: .86; }
.c-asset { }
.c-venue { font: 400 12px/1.3 var(--mono); color: var(--ink2); }

/* ---- a cell that acts --------------------------------------------------- */
/* A real <button>, so Enter and Space activate it without this file
   reimplementing either, and so a screen reader is told it is a control. It
   fills its cell: the click target is the whole cell, not the 30px of text. */
.tw-b {
  display: flex; flex-direction: column; justify-content: center;
  width: 100%; min-width: 0; height: 100%;
  margin: 0 -6px; padding: 0 6px;
  border: 0; border-radius: 4px;
  background: transparent; color: inherit; font: inherit; text-align: inherit;
  cursor: pointer;
}
/* The two cells that name an ENTITY put its mark beside the name, on one line.
   .tw-b is a COLUMN flex by default because most cells stack a figure over a
   sub-line, so a venue cell inherited that and dropped its new mark onto its
   own row — the logo sat above "Paradex" instead of beside it. Same rule, same
   gap as the asset cell, so both entity columns read identically. */
.c-asset .tw-b,
.c-venue .tw-b { flex-direction: row; align-items: center; gap: 6px; }
/* The name yields before the mark does: a clipped word is still readable, a
   half-drawn logo is not. */
.c-venue .tw-b > :not(svg) { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tw-c.num .tw-b { align-items: flex-end; }
.tw-b:hover { background: var(--glass); }
.tw-b:hover .p, .tw-b:hover b {
  text-decoration: underline dotted;
  text-decoration-color: var(--mint2);
  text-underline-offset: 3px;
}
/* THE ACTIVE FILTER IS VISIBLE ON THE VALUE THAT SET IT, not only in the bar
   above — the reader who clicked "XAUT" three clicks ago should see which cell
   they clicked. */
.tw-b.on { background: var(--glass); box-shadow: inset 0 0 0 1px var(--mint2); }
.tw-b.on .p, .tw-b.on b { color: var(--mint); }

/* ---- counterparties ----------------------------------------------------- */
.c-taker, .c-maker { flex-direction: row; align-items: center; gap: 7px; }
/* THE IDENTICON. Deterministic from the address, generated in JS as a data:
   URI — no request, no sprite sheet, no loading flicker. Fixed box, so it
   cannot shift the row whether it resolves or not. */
.tw-ic {
  width: 17px; height: 17px; flex: none;
  border-radius: 3px;
  background-color: var(--s3);
  background-size: cover;
  background-repeat: no-repeat;
}
/* Addresses. A link only exists where the explorer host is verified; the rest
   render as plain text with the full value on hover, because a wrong explorer
   link resolves to a different account and looks authoritative doing it.
   paradex and aevo publish no verified explorer and are plain text on purpose. */
.tw-addr {
  font: 400 12px/1.3 var(--mono); color: var(--ink2); cursor: help;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tw-addr.lk { color: var(--mint); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--line2); }
.tw-addr.lk:hover { text-decoration-color: var(--mint); }

/* ---- sortable header ---------------------------------------------------- */
/* SORT LIVES IN THE HEADER, FILTER LIVES IN THE CELL. Clicking "$1,949" should
   do something about that fill; "sort by premium" is not about that fill, it
   is about the column. */
.tw-sort {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 3px;
  max-width: 100%;
}
.tw-c.num .tw-sort { justify-content: flex-end; }
.tw-sort:hover { color: var(--ink); }
.tw-sort:focus-visible { outline: 2px solid var(--mint2); outline-offset: 2px; }
.tw-caret::after { content: "\2195"; opacity: .5; font-size:10px; }
.tw-c[aria-sort="descending"] .tw-sort,
.tw-c[aria-sort="ascending"] .tw-sort { color: var(--mint); }
.tw-c[aria-sort="descending"] .tw-caret::after { content: "\2193"; opacity: 1; }
.tw-c[aria-sort="ascending"] .tw-caret::after { content: "\2191"; opacity: 1; }

/* ---- row state is an attribute, never a re-render ------------------------ */
/* The window filter and the facet filters both DIM. They do not unmount, and
   that is a correctness rule before it is a performance one: a row that
   disappeared would look exactly like a fill that never happened, and this
   page is not allowed to make an absence ambiguous. */
/* An explicit duration, NOT var(--t). ui.css defines --t as a full shorthand
   value ("130ms cubic-bezier(...)"), which is legal in `transition` and
   illegal in `transition-duration` — the declaration would be dropped silently
   and the dim would snap. */
.tw-row > * { transition-property: opacity; transition-duration: 130ms; }
.tw-row { transition: opacity var(--t), background-color var(--t); }
.tw[data-window="1h"]  .tw-row:not([data-age-h="0"]) { opacity: .5; }
.tw[data-window="6h"]  .tw-row[data-age-h="2"],
.tw[data-window="6h"]  .tw-row[data-age-h="3"] { opacity: .5; }
.tw[data-window="24h"] .tw-row[data-age-h="3"] { opacity: .5; }

/* A FACET MISS IS DIMMER THAN A WINDOW MISS, deliberately: the two states have
   to be distinguishable or a reader cannot tell "outside the hour you picked"
   from "not the asset you picked". */
.tw-row[data-off="1"] { opacity: .2; }
/* Hiding is opt-in, never the default, and the bar says how many rows it is
   hiding while it does it. */
.tw[data-fmode="hide"] .tw-row[data-off="1"] { display: none; }

.tw-row[data-fresh="true"] { border-left-color: var(--mint); background-color: var(--mint-dim); }
.tw-row[data-fresh="true"][data-side="buy"] { border-left-color: var(--good); }
.tw-row[data-fresh="true"][data-side="sell"] { border-left-color: var(--bad); }

/* ---- focus -------------------------------------------------------------- */
/* Inset, so a focus ring can never change a box and push the rows below it
   down — a keyboard user would otherwise watch the table walk down the page as
   they arrowed through it. */
.tw-c:focus-visible, .tw-b:focus-visible, .tw-addr:focus-visible {
  outline: 2px solid var(--mint2);
  outline-offset: -2px;
  border-radius: 4px;
}
.tw-c:focus { outline: none; }

.tw-foot { margin: 8px 0 2px; font: 10px/1.5 var(--mono); color: var(--ink3); }

@media (prefers-reduced-motion: reduce) {
  .tw-row, .tw-row > * { transition: none; }
}

/* ---- the zero-JS absolute timestamp -------------------------------------
   It is ALREADY IN THE DOM, position:absolute and display:none, and CSS alone
   reveals it. No tooltip library, no portal, no positioning maths, no state,
   nothing to fetch on hover: the exact time costs nothing because it was
   always there. Relative time is what you scan; the absolute time is one hover
   away.

   REACHABLE BY KEYBOARD, not hover alone: the age cell is a focus stop in the
   grid, so arrowing onto it reveals the stamp exactly as pointing at it does.
   The sibling combinator works because .tw-ts is a child of the ROW, not of
   the cell — inside .c-age it would be clipped by that cell's own overflow. */
.tw-ts {
  position: absolute;
  display: none;
  left: 10px; top: 100%;
  z-index: 1;
  margin-top: -3px;
  padding: 4px 7px;
  border: 1px solid var(--line2);
  border-radius: var(--r-mark);
  background: var(--s3);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .18);
  font: 400 10.5px/1 var(--mono);
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
}
.tw-row .c-age:hover ~ .tw-ts,
.tw-row .c-age:focus ~ .tw-ts,
.tw-row .c-age:focus-visible ~ .tw-ts { display: block; }
/* #tape clips at its own bottom edge, so the last rows flip their stamp above
   themselves rather than into a scrollport they cannot escape. */
.tw-row:nth-last-child(-n + 2) .tw-ts { top: auto; bottom: 100%; margin: 0 0 -3px; }
.c-age { cursor: help; }

/* ---- shared with the ticker popover -------------------------------------
   These rules dress the popover's facts and counterparties. They were written
   for a row expansion that no longer exists — the columns replaced it — and
   they stay because the popover mounts the same builders. */
.tw-x-facts { display: grid; gap: 4px 16px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.tw-x-f { display: flex; align-items: baseline; gap: 8px;
  font: 11.5px/1.5 var(--mono); }
.tw-x-f span { color: var(--ink3); min-width: 92px; }
.tw-x-f b { color: var(--ink); font-weight: 600; }
.tw-x-f b i { font-style: normal; font-weight: 400; color: var(--ink3); }

.tw-x-cps { display: grid; gap: 6px; }
.tw-x-p { display: grid; grid-template-columns: 46px auto; gap: 2px 8px;
  font: 11.5px/1.4 var(--mono); align-items: baseline; }
.tw-x-k { color: var(--ink3); }
.tw-x-p i { grid-column: 2; font-style: normal; font-size: 10.5px; color: var(--ink3); }

.tw-x-act { display: flex; flex-direction: column; gap: 6px; }
.tw-x-b, .tw-x-x {
  font: 11.5px/1.35 var(--mono); padding: 8px 12px; cursor: pointer;
  border: 1px solid var(--line2); background: var(--s2); color: var(--ink2);
}
.tw-x-b b { color: var(--mint); }
.tw-x-b:hover, .tw-x-x:hover { color: var(--ink); border-color: var(--mint2); }
.tw-x-b:disabled { color: var(--ink3); cursor: default; border-color: var(--line); }
.tw-x-b:focus-visible, .tw-x-x:focus-visible {
  outline: 2px solid var(--mint2); outline-offset: 2px;
}

/* Numbered install steps (2026-08-05 walkthrough fix #3) */
.tl-steps{margin:8px 0 10px;padding-left:20px;font-size:12px;line-height:1.7;color:var(--ink2)}
.tl-steps em{color:var(--ink);font-style:normal;font-weight:600}
.tl-steps code{color:var(--ink)}


/* Mini-tape: the horizontal ticker slots stack vertically in the side panel. */
.mini-tape .tk-row{display:flex;flex-direction:column;width:100%;height:auto}
.mini-tape .tk-i{border-right:0;border-bottom:1px solid var(--line);width:100%;
  flex:none;height:auto;min-height:0;padding:8px 12px}
.mini-tape .tk-i:last-child{border-bottom:0}

/* ---- the table scrolls, not the page ------------------------------------
   Measured: the tape panel rendered 2,261px tall inside a 751px stage, so the
   only scroller was the stage itself — 32 of 45 rows sat below the fold, the
   sticky header slid away with them, and reading row 40 meant dragging the
   whole section. The header was already `position: sticky`; it had nothing to
   stick inside.

   Trades is its own section now, so the panel can own the viewport: the shell
   is the stage's height, the head block is fixed, and #tape is the scrollport.
   The header then sticks against the table's own top rather than the page's,
   which is what makes it useful. */
.view[data-view="trades"] { min-height: 0; }
.view[data-view="trades"] .tape-panel {
  height: 100%;
  max-height: none;          /* the overview cap must not reach in here */
  display: flex; flex-direction: column; min-height: 0;
}
.view[data-view="trades"] .tape-panel > .panel-head { flex: 0 0 auto; }
.view[data-view="trades"] #tape {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  /* The tape PREPENDS new fills. Chrome's scroll anchoring would fight our own
     positioning on every refresh and produce micro-stutter, so scroll position
     is this component's decision. Applied here only — it is not a blanket. */
  overflow-anchor: none;
  /* A floating-pill thumb: a scrollbar thumb cannot take margin, so the inset
     is faked with a border in the track colour. */
  scrollbar-width: thin;
  scrollbar-color: var(--line2) transparent;
}
.view[data-view="trades"] #tape::-webkit-scrollbar { width: 12px; }
.view[data-view="trades"] #tape::-webkit-scrollbar-track { background: transparent; }
.view[data-view="trades"] #tape::-webkit-scrollbar-thumb {
  background-color: var(--line2); border-radius: 12px;
  border: 3px solid var(--bg);
}
.view[data-view="trades"] #tape::-webkit-scrollbar-thumb:hover {
  background-color: var(--ink3);
}
/* Sticky needs an opaque fill or rows show through it while they pass under. */
.view[data-view="trades"] .tw-head { background: var(--bg); }

/* The sticky header sat inside .tw-grid, whose own overflow is visible, so its
   nearest scrollport was #tape two levels up — and a sticky element pins to
   its SCROLLPORT, not to the box you meant. Measured: scrolling the table 600px
   drifted the header -142px. Giving the grid `position: relative` does not fix
   it; the header has to be a direct child of the scrolling element's flow with
   nothing between them that establishes a new containing block. .tw-grid is
   display:flex with no overflow, so it does not — the drift came from the head
   being laid out ABOVE the scroll origin. Pinning it to the scrollport's top
   edge is what the -1px is measured against. */
/* .tw is display:flex column, and a sticky item inside a flex container is
   confined to its own FLEX LINE — a box exactly as tall as the item itself, so
   there is nowhere to travel and the header rides away with the rows.
   Measured: scrolling 800px drifted it -142px with position:sticky and top:-1px
   both computing correctly, which is the tell that the containing block is
   wrong rather than the rule.
   `display: block` on the wrapper puts the header back in normal flow inside
   the scrollport, where sticky means what it says. */
/* THE CONTROLS MUST NOT SCROLL. #tape was the scrollport and .tw its only
   child, so the window tabs, the blind-spot notice, the hint line and the
   filter bar all scrolled away with the rows — leaving the sticky column
   header pinned under a half-cut row with no controls in sight.
   .tw becomes the flex column that fills the scrollport, everything above the
   grid is fixed, and ONLY the grid scrolls. The header then sticks against the
   grid's own top, which is the box a reader is actually reading. */
.view[data-view="trades"] #tape { overflow: hidden; }   /* the shell, not the scroller */
.view[data-view="trades"] .tw {
  display: flex; flex-direction: column;
  height: 100%; min-height: 0;
}
.view[data-view="trades"] .tw > .tw-blind,
.view[data-view="trades"] .tw > .tw-tabs,
.view[data-view="trades"] .tw > .tw-hint,
.view[data-view="trades"] .tw > .tw-fbar,
.view[data-view="trades"] .tw > .tw-foot { flex: 0 0 auto; }
.view[data-view="trades"] .tw > .tw-grid {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  overflow-anchor: none;          /* the tape prepends; we own scrollTop */
  scrollbar-width: thin;
  scrollbar-color: var(--line2) transparent;
}
.view[data-view="trades"] .tw-grid::-webkit-scrollbar { width: 12px; }
.view[data-view="trades"] .tw-grid::-webkit-scrollbar-track { background: transparent; }
.view[data-view="trades"] .tw-grid::-webkit-scrollbar-thumb {
  background-color: var(--line2); border-radius: 12px; border: 3px solid var(--bg);
}
.view[data-view="trades"] .tw-grid::-webkit-scrollbar-thumb:hover { background-color: var(--ink3); }

/* ══════════════════════════════════════════════════════════════════════════
   THE TAPE — AIR AND SCALE (2026-08-05)

   Every other surface was rebuilt tonight: the overview opens with a hero
   figure, the venue panel got a real stylesheet, Strategies got room. Trades
   was left at 12px in a dense grid, which made it the oldest screen in the app
   the moment the others moved.

   Same reasoning, measured: our body text was 12px against 16px on every
   reference site. Density reads "terminal", air reads "product". Nothing here
   changes a number, a colour or a column — only the room they get.

   THE TAPE IS THE EVIDENCE. It is the one surface where the product's whole
   claim lives (fills, not quotes), so it should be the most readable thing we
   ship, not the densest. But it also renders 2,000 rows: nothing here may add
   per-row cost, so this is type and spacing only — no shadows, no blur, no
   per-row backdrop work.

   !important with class specificity is deliberate: instrument.css loads after
   this file with a `body *:not(svg)` !important blanket, and two !important
   declarations resolve by specificity rather than by order.
   ══════════════════════════════════════════════════════════════════════════ */

/* The figures carry the row. */
body .tw-c b { font-size: 14px !important; letter-spacing: -0.01em !important; }
body .tw-c .p { font-size: 13px !important; }   /* §69 ladder: was 14px */
body .tw-c .s { font-size: 11px !important; }

/* Column headers step back, as on every other board now. */
body .tw-head .tw-c {
  font-size: 10px !important;
  letter-spacing: .08em !important;
  color: var(--ink3) !important;
  padding-top: 12px !important;   /* §69 ladder: was 11px */
  padding-bottom: 12px !important;
}

/* Room to read a row without losing the scan. */
body .tw-body .tw-c { padding-top: 10px !important; padding-bottom: 10px !important; }

/* The window tabs become real targets rather than dense chips. */
body .tw-tab {
  padding: 8px 14px !important;
  font-size: 11px !important;   /* §69 scale: was 12px */
  border-radius: 8px !important;
}
body .tw-tabs { gap: 6px !important; margin-bottom: 16px !important; }

/* The section's own count line leads, the way the other surfaces do. */
body .tw-hint { font-size: 12px !important; margin-bottom: 14px !important; }

/* ══════════════════════════════════════════════════════════════════════════
   §  THE TAPE'S TWO-LINE CELLS OVERLAP, AND THIS FILE CAUSED IT (2026-08-09)

   Read on rendered pixels at 1440, #trades, first row: "sold a floor" and
   "$4,300 · -0.8%" are drawn on top of one another, descenders through
   x-heights, in the WHAT HAPPENED, EXPIRY, SIZE and PREMIUM columns — four of
   nine columns, on all 2,000 rows. It is the worst legibility defect in the
   app and it is not new: it reproduces identically on the committed HEAD, so
   it is not a regression from this pass. It is a regression from the block
   above.

   THE ARITHMETIC, WHICH NOBODY DID WHEN THAT BLOCK LANDED:

     .tw-row     height: 44px          stated, and the comment on it is
                                       emphatic: "A ROW MUST BE PHYSICALLY
                                       UNABLE TO CHANGE HEIGHT."
     less        1px border-bottom  ->  43px
     less        10px + 10px padding ->  23px of content box   (this file, above)

     .c-prem .p  15px x 1.2         ->  18.0px
     .stk .s     11px x 1.3         ->  14.3px
                                    ->  32.3px of content wanted

   23px of box, 32.3px of content. The row obeyed its height; the type did
   not fit; and because every stack is a `flex-direction: column` container,
   the two spans did not overflow — they SHRANK. flex-shrink defaults to 1, so
   each item's box was squeezed below its own line box and the glyph rows
   slid into each other. That is why it reads as overlap rather than as
   clipping: nothing is clipped, the boxes are simply smaller than the text
   inside them.

   The block above stepped the type up for good measured reasons ("density
   reads terminal, air reads product") and then spent the remaining room on
   padding as well. Both cannot have it. The type is the part a reader sees,
   so the type keeps its size and the padding gives the room back.

   THE FIX IS THREE LINES AND THE THIRD IS THE IMPORTANT ONE:

     1. padding 10px -> 4px, this file's own original value. 43 - 8 = 35px.
     2. line-height 1.2 on both steps. 18.0 + 13.2 = 31.2px wanted.
        3.8px of slack, which survives a font fallback and a 1.25x DPR.
     3. flex: none on the two spans. This is the guard, not the fix: with it,
        a stack that ever exceeds its box CLIPS against the cell's existing
        overflow:hidden instead of silently squashing into itself. Overlapping
        glyphs are unreadable and look like a rendering fault; a clipped
        descender is legible and looks like a tight row. If this arithmetic is
        ever broken again, it should fail the second way.

   No number, colour, column or row height changes. Nothing is removed. This
   is the same 2,000-row surface with the type it was already asked to carry.
   ══════════════════════════════════════════════════════════════════════════ */

body .tw-body .tw-c { padding-top: 4px !important; padding-bottom: 4px !important; }
body .tw-body .tw-c .p,
body .tw-body .tw-c .s { line-height: 1.2 !important; flex: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   THE STATE MATRIX  (.mx): a reusable rows-by-columns display
   ══════════════════════════════════════════════════════════════════════════
   Built for Flow, written to be reused for ANY venue-by-asset or
   asset-by-share display. Nothing in here names a venue or an asset.

   WHY IT IS NOT A TREEMAP. The board it replaces put 94.1% of its area into
   three tiles and rendered the other fifteen assets as slivers, two of them as
   unlabelled specks. Area cannot encode a distribution whose smallest member
   is 0.00002% of its largest. A ROW CAN: a row is the same height whatever it
   holds, so BOT at $13 is exactly as legible as BTC at $9.34M, and the bar
   beside it still states the true proportion.

   COLOUR ENCODES MEASUREMENT, NOT CATEGORY. There is one accent and it means
   "we observed this". Absence of accent means absence of observation. No
   per-row hue is ever assigned, which is what lets a fortieth row arrive
   without inventing a fortieth colour that would eventually collide with a
   meaningful one. The board this replaces mapped six categories onto six
   ALPHAS of a single teal, so its legend decoded nothing.

   THE FOUR CELL CONDITIONS ARE A CLOSED VOCABULARY, encoded in exactly one
   place, below. A second caller cannot invent a fifth.
     traded   solid accent      we read this feed and we saw money move
     listed   hollow outline    we read this feed and saw nothing. MEASURED ZERO
     unknown  hatched           no feed here. WE CANNOT SAY. Never a zero
     absent   faint rule        this venue does not list this row. Also measured

   THE BLANKET. instrument.css:343 pins font-family, font-size, font-weight,
   letter-spacing, border-radius and box-shadow with !important on
   `body *:not(svg):not(svg *)`, and :1883 hands out --sans through the same
   selector. An author !important beats both at any specificity, so every
   declaration here that the blanket also sets carries one. instrument.css is
   not edited. box-shadow is pinned to none, so every edge here is an outline.
   --fill-1 and --fill-2 are transparent, so surfaces use --hover-wash. */

body .mx { display: block; margin: 0; }

/* ── the key ──────────────────────────────────────────────────────────────
   Reads as a sentence per item, because a swatch on its own decoded nothing
   on the board this replaces. */
body .mx-key {
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  margin: 0 0 14px; padding: 0 0 12px;
  border-bottom: 1px solid var(--line);
}
body .mx-key > span {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sans) !important;
  font-size: var(--t-xs) !important;
  letter-spacing: .06em !important;
  text-transform: uppercase;
  color: var(--ink3);
}
body .mx-sw {
  width: 13px; height: 13px; flex: none; display: block;
  outline: 1px solid var(--line2); outline-offset: -1px;
}

/* ── THE FOUR CONDITIONS. This block is the whole vocabulary. ─────────── */
body .mx-sw.is-traded,
body .mx-c.is-traded { background: var(--accent); outline-color: var(--accent); }

/* MEASURED ZERO. A box we drew, and left empty because nothing was in it. */
body .mx-sw.is-listed,
body .mx-c.is-listed { background: transparent; outline-color: var(--line2); }

/* WE CANNOT SAY. Hatching means this and only this. It is deliberately not a
   shade of the accent, because any shade would read as "a little bit of the
   measured thing" and this is not a small amount, it is no information. */
body .mx-sw.is-unknown,
body .mx-c.is-unknown {
  background-image: repeating-linear-gradient(
    45deg, transparent 0 2px, var(--line2) 2px 3px);
  outline-color: var(--line);
}

/* NOT LISTED HERE. Also a measurement: we read this venue's book and this row
   is not in it. A rule rather than a box, so it cannot be mistaken for the
   empty box that means "listed and quiet". */
body .mx-sw.is-absent,
body .mx-c.is-absent {
  background: transparent; outline: 0;
  border-top: 1px solid var(--line);
  align-self: center; height: 1px;
}

/* ── bands ────────────────────────────────────────────────────────────── */
body .mx-band { margin: 0 0 18px; }
body .mx-band:last-child { margin-bottom: 0; }
body .mx-band-h {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0 0 8px;
}
body .mx-band-h b {
  font-family: var(--sans) !important;
  font-size: var(--t-xs) !important;
  font-weight: 600 !important;
  letter-spacing: .1em !important;
  text-transform: uppercase;
  color: var(--ink);
}
body .mx-band-h i {
  font-style: normal;
  font-family: var(--sans) !important;
  font-size: var(--t-xs) !important;
  color: var(--ink3);
}
/* The count sits in the header so the band states its own size. */
body .mx-band-h u {
  text-decoration: none;
  font-family: var(--mono) !important;
  font-size: var(--t-xs) !important;
  color: var(--ink3);
}

/* ── rows ─────────────────────────────────────────────────────────────── */
body .mx-rows { list-style: none; margin: 0; padding: 0; }
body .mx-row {
  display: grid;
  grid-template-columns:
    54px                 /* row key, a ticker */
    minmax(90px, 360px)  /* the proportion bar, CAPPED. Uncapped at 1fr it
                            stretched to about 780px, so every row below 1%
                            was a 2px tick marooned in empty track and the
                            figures sat a long way from the ticker naming
                            them. */
    86px                 /* the value */
    58px                 /* the share */
    max-content;         /* the state strip */
  justify-content: start;
  align-items: center;
  gap: 0 12px;
  padding: 3px 0;
  border-top: 1px solid var(--line);
}
body .mx-rows > li:first-child { border-top: 0; }
body .mx-row:hover { background: var(--hover-wash); }

body .mx-k {
  font-family: var(--mono) !important;
  font-size: var(--t-sm) !important;
  color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Rows we could not measure do not get the full-strength ink either: the row
   is present because the asset is real, but nothing on it is a figure. */
body .mx-row.is-unmeasured .mx-k { color: var(--ink3); }

/* THE BAR. Strictly linear, so it can never overstate a share. Where a real
   value is thinner than a hairline the fill keeps a 2px floor rather than
   vanishing, because a value we measured must not render as an absence: that
   is the exact failure the board this replaces had at the bottom of its
   treemap. The share printed beside it is always the exact number. */
body .mx-bar { position: relative; height: 10px; background: var(--hover-wash); }
body .mx-bar > i {
  position: absolute; left: 0; top: 0; bottom: 0;
  display: block; background: var(--accent); min-width: 2px;
}
/* A MEASURED ZERO DRAWS NO FILL AND KEEPS ITS TRACK. The empty track is the
   statement: we looked along this axis and found nothing. */
body .mx-bar.is-zero > i { display: none; }
/* AN UNMEASURED ROW HAS NO TRACK AND NO FILL. Drawing an empty track here
   would assert a zero we never measured, and the fill is not emitted at all:
   see the note in tools.js, a zero width fill inherits the 2px floor and
   painted the accent onto every row we cannot see. */
body .mx-bar.is-none { background: none; }
body .mx-bar.is-none > i { display: none; }

body .mx-v, body .mx-s {
  font-family: var(--mono) !important;
  font-size: var(--t-sm) !important;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink);
  white-space: nowrap;
}
body .mx-s { color: var(--ink3); }
/* "no feed" and "none" are words, not figures, so they take the sans and the
   quiet ink. A dash in a figure column reads as a missing number; a word reads
   as an answer. */
/* The single placeholder for a row with no measurement, spanning both figure
   columns. It is a word, not a dash: a dash in a figure column reads as a
   number that failed to load. */
body .mx-na {
  grid-column: 3 / 5;
  text-align: right;
}
body .mx-na, body .mx-v.is-word, body .mx-s.is-word {
  font-family: var(--sans) !important;
  font-size: var(--t-xs) !important;
  color: var(--ink3);
  letter-spacing: .04em !important;
}

/* ── the state strip: one cell per column ─────────────────────────────── */
body .mx-cells { display: flex; gap: 3px; align-items: stretch; height: 13px; }
body .mx-c { width: 13px; height: 13px; flex: none; display: block;
             outline: 1px solid var(--line); outline-offset: -1px; }

/* Column heads sit above the first band only, aligned to the same strip.
   THE NAMES ARE WRITTEN OUT AND ROTATED, not abbreviated to an initial.
   Eleven venues give A, B, B, C, D, D, D, H, O, P, R: derive, deribit and
   delta all collide on D and so do binance and bybit, so an initial would
   mislabel five of eleven columns. Inventing three letter codes would be our
   abbreviation rather than the venue's name, so the column is turned instead
   and carries the real name. */
body .mx-head-row { border-top: 0; padding: 0 0 4px; }
body .mx-head-row:hover { background: none; }
body .mx-heads { display: flex; gap: 3px; align-items: flex-end; }
body .mx-heads > span {
  width: 13px; flex: none; height: 62px;
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--sans) !important;
  font-size: 10px !important;  /* LITERAL, and it is below the scale on purpose.
                                  The scale runs 11/15/16/30. A label turned
                                  through 90 degrees is bounded by the COLUMN
                                  WIDTH, 13px, not by the line box, and 11px
                                  clips against the 3px gutter. Commented here
                                  so it is never "tidied" back onto the scale. */
  line-height: 13px !important;
  letter-spacing: .02em !important;
  color: var(--ink3);
  white-space: nowrap;
}

/* ── the reading line ─────────────────────────────────────────────────── */
body .mx-read {
  margin: 14px 0 0; padding-top: 12px;
  border-top: 1px solid var(--line);
  font-family: var(--sans) !important;
  font-size: var(--t-sm) !important;
  line-height: 1.5 !important;
  color: var(--ink3);
}
body .mx-read b {
  font-family: var(--mono) !important;
  font-size: var(--t-sm) !important;
  font-weight: 400 !important;
  color: var(--ink);
}

/* ── narrow ───────────────────────────────────────────────────────────────
   The treemap this replaces kept a fixed 920x380 viewBox and simply scaled,
   so at 390 every label shrank to about 6px and the chart became a smear.
   Rows reflow instead: the figures move up beside the key and the bar and the
   strip take a line each, at full readable size. */
@media (max-width: 720px) {
  body .mx-row {
    grid-template-columns: 1fr max-content max-content;
    gap: 2px 8px;
    padding: 7px 0;
  }
  /* THE ROW IS PINNED, NOT AUTO PLACED. Assigning columns alone let the
     full width bar claim a row of its own between the ticker and its figures,
     so every row read as four loose lines with the money stranded on line
     three. Rows are named explicitly: identity and figures together, then the
     bar, then the state strip. */
  body .mx-k     { grid-column: 1;      grid-row: 1; }
  body .mx-v     { grid-column: 2;      grid-row: 1; }
  body .mx-s     { grid-column: 3;      grid-row: 1; }
  body .mx-na    { grid-column: 2 / 4;  grid-row: 1; }
  body .mx-bar   { grid-column: 1 / -1; grid-row: 2; }
  body .mx-cells { grid-column: 1 / -1; grid-row: 3; margin-top: 2px; }
  body .mx-head-row { display: none; }
  /* A row with no measurement has no bar to draw, so it does not reserve the
     line. Sixteen of these sit in the last band and the empty lines were the
     bulk of its height. */
  body .mx-bar.is-none { display: none; }
  body .mx-c { width: 12px; height: 12px; }
  body .mx-cells { height: 12px; }
  body .mx-key { gap: 4px 14px; }
}
