/* Strategies — the price-ranked board.
   Every colour is a shared token, so this follows night shift with the rest of
   the app. Nothing here is hard-coded; detail.css's 158 raw hex values are the
   cautionary tale.

   NOTE ON THE TWO GREENS. On the paper theme --good (#087f5b) and --mint
   (#1971c2) are green and blue and read apart instantly. On night shift they
   are #2dd4bf and #6ee7d7 — the same teal, two steps of lightness. So credit
   vs cost is NEVER carried by fill colour alone: a credit cell also gets a
   solid --good left edge and the words "upside sold" printed on it. */

.op { padding: 18px 20px 26px; }
.op-h { font: 600 17px/1.25 var(--sans); color: var(--ink); margin: 0 0 6px; }
.op-lede { color: var(--ink2); font-size: 12.5px; line-height: 1.55; margin: 0 0 10px;
  max-width: 72ch; }

/* The neutrality notice, compressed from three sentences of body text to one
   always-visible strip. The CONTENT is non-negotiable, the wordcount was not:
   the old block was long enough that it read as boilerplate and got skipped,
   which is the worst possible outcome for the one paragraph that explains why a
   credit is not free money. It is short here BECAUSE the same fact is now also
   printed on every credit cell, in the expansion, and in the compare tray. */
.op-note {
  border: 1px solid var(--line2); border-left: 3px solid var(--warn);
  background: var(--s1); border-radius: var(--r);
  padding: 8px 12px; margin: 0 0 14px; max-width: 82ch;
  color: var(--ink2); font-size: 12px; line-height: 1.5;
}
.op-note b { color: var(--ink); }

/* ---- controls ---------------------------------------------------------- */

.op-ctl { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center;
  padding: 10px 0 12px; }
/* WRAPS. Measured at 390: the Order group is a label, three buttons and the
   "firm first, always" note on one nowrap line — 445px of content in a 390px
   viewport, which pushed main.stage into a sideways scroll and took the whole
   page with it. The group wraps internally now; .op-ctl's own wrap could not
   help, because the overflowing box was a single flex ITEM. */
.op-ctl-i { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.op-ctl-i > i { font-style: normal; font: 10.5px/1 var(--mono);
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink3); }
.op-bs { display: inline-flex; gap: 3px; }
.op-b { font: 11.5px/1 var(--mono); padding: 6px 10px; cursor: pointer;
  background: var(--s1); color: var(--ink2);
  border: 1px solid var(--line); border-radius: 5px; }
.op-b:hover { color: var(--ink); border-color: var(--line2); }
.op-b.on { background: var(--mint-dim); color: var(--mint); border-color: var(--mint2); }
.op-b:focus-visible { outline: 1.5px solid var(--mint2); outline-offset: 1px; }

/* Firm-first is a property of the data, not a setting — so it is stated beside
   the sort control rather than offered as one. */
.op-lock { font: 10px/1 var(--mono); color: var(--ink3); font-style: normal;
  letter-spacing: .04em; border: 1px dashed var(--line2); border-radius: 4px;
  padding: 4px 6px; white-space: nowrap; cursor: help; }

/* Counts as controls. n_credits / n_split / n_firm_credits used to be printed
   in a caption; a number you can click is worth more than a number you read. */
.op-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding-bottom: 14px; border-bottom: 1px solid var(--line); margin-bottom: 14px; }
.op-chip { font: 11px/1 var(--mono); padding: 6px 9px; cursor: pointer;
  display: inline-flex; align-items: baseline; gap: 5px;
  background: var(--s1); color: var(--ink3);
  border: 1px solid var(--line); border-radius: 99px; }
.op-chip b { color: var(--ink2); font-weight: 600; font-size: 11.5px; }
.op-chip:hover { border-color: var(--line2); color: var(--ink2); }
.op-chip:hover b { color: var(--ink); }
.op-chip.on { background: var(--mint-dim); border-color: var(--mint2); color: var(--mint); }
.op-chip.on b { color: var(--mint); }
.op-chip:focus-visible { outline: 1.5px solid var(--mint2); outline-offset: 1px; }
/* Not a filter — a statement. Zero firm credits is the headline of this whole
   board and it must not look like a button that does nothing.
   NOT class "stat": style.css owns a bare `.stat` (a 22px figure tile with a
   `::before` gradient rule and an 11px/14px padding override inside its media
   query). It would have painted a gradient bar across this chip and resized it
   only below 720px, which is exactly the sort of bug that ships. */
.op-chip.fact { cursor: help; border-style: dashed; }
.op-chip.fact.zero { border-color: var(--warn); color: var(--warn); }
.op-chip.fact.zero b { color: var(--warn); }

/* 24/8, not 18/10 — 3x the space above the label as below it, so it binds
   DOWNWARD to the table it names instead of floating equidistant between
   the block above and the block below. */
.op-sub-h { font: 600 12.5px/1.3 var(--sans); color: var(--ink); margin: 24px 0 8px;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.op-sub-h span { font: 400 11px/1 var(--mono); color: var(--ink3); }

/* ---- the compare tray --------------------------------------------------
   Sits ABOVE the board on purpose: pinning has to visibly do something in the
   same eyeful as the click that caused it, or the affordance reads as dead. */
.op-tray { border: 1px solid var(--line2); border-radius: var(--r);
  background: var(--s1); padding: 10px 12px 12px; margin: 0 0 14px; }
.op-tray-h { display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  font: 600 11px/1 var(--sans); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink3); }
.op-tray-h span { font: 600 11px/1 var(--mono); color: var(--mint); }
.op-tray-h .op-xb { margin-left: auto; }

.op-t-g { display: grid; gap: 1px 8px; overflow-x: auto;
  grid-template-columns: 74px repeat(var(--n), minmax(140px, 1fr));
  font: 11.5px/1.35 var(--mono); }
.op-t-k { color: var(--ink3); font-size: 10px; text-transform: uppercase;
  letter-spacing: .05em; padding: 6px 0; display: flex; align-items: center;
  border-top: 1px solid var(--line); }
.op-t-g > .op-t-k:first-child { border-top: 0; }
.op-t-c { padding: 6px 0; color: var(--ink2); display: flex; flex-wrap: wrap;
  align-items: center; gap: 3px 6px; border-top: 1px solid var(--line); min-width: 0; }
.op-t-c b { color: var(--ink); font-weight: 600; }
.op-t-c i { font-style: normal; color: var(--ink3); font-size: 10.5px; }
.op-t-c .mk { flex: none; }
.op-t-c b.credit { color: var(--good); }
.op-t-c.gone { color: var(--warn); font-size: 10.5px; }

.op-t-c.head { border-top: 0; padding-bottom: 8px; gap: 5px; }
.op-t-c.head b { font-size: 12.5px; }
.op-t-c.head i { font-size: 11px; }
/* The credit marker follows the pin into the tray. Same fact, third surface. */
.op-t-c.head.credit { box-shadow: inset 0 -2px 0 var(--good); }
.op-t-x { margin-left: auto; background: none; border: 0; cursor: pointer;
  color: var(--ink3); font: 14px/1 var(--mono); padding: 0 3px; }
.op-t-x:hover { color: var(--bad); }
.op-t-x:focus-visible { outline: 1.5px solid var(--mint2); outline-offset: 1px; }

.op-t-c em { font-style: normal; white-space: nowrap; }
.op-t-c .op-w-v { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }

/* ---- the board ---------------------------------------------------------
   Assets down, tenors across, one number per cell. Colour carries DIRECTION
   (does the cap cover the floor) and intensity carries magnitude, so a column
   can be ranked without reading every figure. --i is set per cell by the JS. */
.op-mx { display: flex; flex-direction: column; gap: 2px;
  overflow-x: auto; padding-bottom: 4px; }
/* --t is the tenor-column count, set by the JS.
   This was `repeat(auto-fit, minmax(96px, 1fr))`. auto-fit does not overflow —
   it drops columns. Measured at 430px: the 60d cell wrapped onto its own line
   BELOW its asset row, so ETH's 60d number sat under BTC's ticker and read as
   BTC's. A matrix whose cells silently reflow into the wrong row is worse than
   one that scrolls, so the column count is now explicit and the track scrolls. */
.op-mx-r { display: grid; align-items: stretch; gap: 4px;
  grid-template-columns: 150px repeat(var(--t, 3), minmax(96px, 1fr));
  min-width: calc(150px + var(--t, 3) * 100px); }

/* A sticky row needs an OPAQUE background and a real z-index. With neither,
   matrix cells scrolled UNDER the header and showed through it, and at any
   intersection with another sticky element the winner was decided by DOM
   order. top:-1px rather than 0 because at fractional zoom a top:0 sticky
   header leaves a 1px seam where the row border bleeds through. */
.op-mx-r.head { position: sticky; top: -1px; z-index: 3; background: var(--bg); }
.op-mx-r.head .op-mx-a,
.op-mx-r.head .op-mx-c {
  background: var(--bg);
  font: 10.5px/1 var(--mono); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink3); padding: 4px 8px; background: none; border: 0;
  display: flex; align-items: center;
}
/* FIGURES RIGHT, WORDS LEFT — and the header sits over its own column.
   The tenor columns hold nothing but figures, so they right-align and the
   header right-aligns with them; a column of numbers that shares one right
   edge can be read DOWN without reading across, which is the whole point of a
   matrix. The asset column is words and stays left. This is the split the
   rows view (.op-l-r) and the Markets board (.a-r / .a-l) already use —
   matched rather than reinvented, per the brief.

   IT IS align-items, NOT justify-content, AND THAT IS THE WHOLE TRICK.
   .op-mx-c is `flex-direction: column`, which the head cell inherits, so
   justify-content drives the VERTICAL axis here — setting it to flex-end
   computed cleanly, reported flex-end, and moved the label not one pixel.
   The cross axis is the horizontal one, and the rule above pins it to
   `center`. Same specificity (0,3,0), so this wins on source order.

   padding-right is 23px to match where the figures actually land: .op-cell
   carries 22px of right padding (§66, to clear the 15px pin button) plus the
   cell's 1px right border, so a label at the head cell's default 8px sat 15px
   right of the column it names. Measured, not eyeballed — the cash glyphs end
   at x=721/1031/1341 and the cells at 744/1054/1364. */
.op-mx-r.head .op-mx-c { align-items: flex-end; padding-right: 30px; }

/* Row header: logo, ticker, spot. */
.op-mx-a { display: flex; align-items: center; gap: 7px; padding: 7px 8px;
  font: 12.5px/1 var(--mono); color: var(--ink2); min-width: 0; }
.op-mx-a b { color: var(--ink); font-weight: 600; }
.op-mx-a i { margin-left: auto; font-style: normal; font-size: 10.5px;
  color: var(--ink3); }
.op-mx-a .mk { flex: none; }
/* Reserve the mark's width whether or not one renders. Eight assets have
   authored art; the rest are equities we cannot draw, and letting their rows
   shift left made the column look broken rather than deliberate. */
.op-mx-a { --mk: 18px; }
.op-mx-a::before { content: ""; flex: none; width: 0; }
.op-mx-a:not(:has(.mk))::before { width: var(--mk); }

.op-mx-c { display: flex; flex-direction: column; align-items: flex-end;
  justify-content: center; gap: 2px; padding: 6px 5px; border-radius: 5px;
  font: 12px/1.2 var(--mono); }
.op-mx-c.val { position: relative; border: 1px solid transparent; padding: 0; }
.op-mx-c.val b { font-weight: 600; color: var(--ink); }

/* The cell IS a button now. The hover popover it replaces never fired on touch
   and vanished the moment the pointer moved toward it — which meant the only
   place the two legs were ever written out was, in practice, unreachable. */
.op-cell { display: flex; flex-direction: column; align-items: flex-end;
  justify-content: center; gap: 3px; width: 100%; padding: 7px 16px 7px 6px;
  background: none; border: 0; border-radius: 5px; cursor: pointer;
  text-align: right; font: 12px/1.2 var(--mono); color: inherit; }
/* THE CASH, LEADING. 16px against the net's 10px — a 1.6x step, which is what
   makes one of two adjacent numbers read as "the number" rather than "the
   first number".
   COLOUR IS DELIBERATELY NOT SET HERE. `.op-mx-c.val.credit b` already paints
   a credit cell green, and this cash IS a <b>, so it inherits the board's
   existing green-means-credit language for free. The first draft set green
   unconditionally and it only LOOKED right because the cell measured happened
   to be a credit — every cost row would have shipped a green headline saying
   the opposite of what the shading said. Specificity is matched to
   `.op-mx-c.val b` (0,2,1) on purpose: below it, the size was being overridden
   too. */
.op-mx-c.val .op-cash {
  font: 700 16px/1.05 var(--mono);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
/* FULL --ink, NOT --ink3, AND NOT --ink2 EITHER.
   Measured by sampling RENDERED PIXELS, not by computing over the declared
   background — the cell's tint is an alpha over the page, so the effective
   backdrop is not any colour in the stylesheet. At the top of the alpha ladder
   (--i .64) this line measured 1.32:1 as --ink3 and 2.45:1 as --ink2. Both
   unreadable; only full ink clears at 6.02:1.
   The trap is that the tint deepens exactly where the number is most extreme,
   so the faintest text sat on the cells a reader most wants to read. .op-cc
   carried this same bug quietly for as long as it existed; it only became
   load-bearing when the net moved into that slot.
   Hierarchy therefore rests on SIZE AND WEIGHT alone — 16px/700 against
   10px/400 — which is a 1.6x step and holds without spending contrast. */
/* .op-net IS NO LONGER EMITTED. It held the SECOND quantity — the collar's
   net, with its direction word — under a headline that was the cap's bid. Two
   different quantities in a 96px cell is what the owner read as a muddle
   ("the table itself should be clear enough"), so the net moved to the cell's
   title, the expansion and the rows view. The rule is kept, unused, only
   because the contrast finding below it is the expensive part and it applies
   to anything that ever lands in that slot again: on this board the cell tint
   deepens exactly where the number is most extreme, so a muted ink sits on
   the cells a reader most wants to read. Measured on RENDERED PIXELS at the
   top of the alpha ladder (--i .64): 1.32:1 as --ink3, 2.45:1 as --ink2, and
   6.02:1 only at full --ink. Hierarchy is bought with SIZE AND WEIGHT here,
   never with contrast. */
.op-mx-c.val .op-net {
  display: block; margin-top: 1px; font-style: normal;
  font: 10px/1.2 var(--mono); color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.op-mx-c.val:hover { border-color: var(--line2); }
.op-cell:focus-visible, .op-pin:focus-visible, .op-l-b:focus-visible {
  outline: 1.5px solid var(--mint2); outline-offset: -1px; border-radius: 5px; }

/* Cost: the house accent. Credit: the affirmative token. Both scale their
   fill by --i so magnitude survives without a second encoding. */
.op-mx-c.val { background: color-mix(in srgb, var(--mint) calc(var(--i) * 100%), transparent); }
.op-mx-c.val.credit {
  background: color-mix(in srgb, var(--good) calc(var(--i) * 100%), transparent);
  box-shadow: inset 3px 0 0 var(--good);
}

/* DARK CAPS THE LADDER. The alpha is computed in JS as .14 + .5*(|net|/max),
   topping out at .64, and that top step was tuned against the light palette
   where a deeper tint means a darker cell and text contrast IMPROVES. In dark
   the same alpha runs the other way: --good at 64% is a bright teal, and the
   light text on it measured 2.91:1 by pixel sample — on the single cell
   carrying the most extreme number on the board.
   Rather than re-tune the JS (which would flatten the encoding in BOTH themes
   and cost light theme its range for a problem light theme does not have), the
   dark rule rescales the same --i into a shallower band. Ranking survives —
   the ladder is still monotonic in |net| — it just runs over a narrower spread
   where the palette can afford it. */
:root[data-theme="dark"] .op-mx-c.val {
  background: color-mix(in srgb, var(--mint) calc(var(--i) * 52%), transparent);
}
:root[data-theme="dark"] .op-mx-c.val.credit {
  background: color-mix(in srgb, var(--good) calc(var(--i) * 52%), transparent);
}
.op-mx-c.val.credit b { color: var(--good); }
.op-mx-c.val.open { border-color: var(--mint2); }
.op-mx-c.val.pinned { box-shadow: inset 0 0 0 1px var(--mint2); }
.op-mx-c.val.credit.pinned { box-shadow: inset 3px 0 0 var(--good), inset 0 0 0 1px var(--mint2); }

/* Quoted on request, not resting. Hatched, because the number is real but
   nobody has posted it — the same treatment the venue grid uses. */
.op-mx-c.val.indic {
  background-image: repeating-linear-gradient(-45deg,
    var(--glass) 0 4px, transparent 4px 8px);
  opacity: .88;
}
.op-mx-c.val.indic b { color: var(--ink2); }

/* THE UNIT, UNDER THE FIGURE. The cash and the net are DIFFERENT QUANTITIES —
   $62.40 is the cap's bid on its own, −0.59% is the collar's net — and with
   nothing between them the green $62.40 read as what you receive. One line of
   micro label fixes what no amount of re-weighting could. .op-unit is the
   app's existing micro-label role (instrument.css r1), so this inherits the
   same uppercase/tracked/muted treatment every other unit on the page has. */
.op-mx-c.val .op-unit { display: block; font-style: normal; white-space: nowrap; }

/* FIRMNESS RIDES THE QUALIFIER, AND IT IS THE ONE THING THAT COULD NOT MOVE.
   Everything else the cell used to say went to the title and the expansion;
   this did not, because "a price quoted on request is not a firm resting
   offer" is the distinction this app has shipped wrong twice, and a caveat
   behind a hover is a caveat nobody reads. It is set as part of the unit line
   — "cap bid · on request" — so the cell still says ONE thing about ONE
   figure rather than growing a third row back.

   --warn is the app's caveat role and .op-na (the "unknown, not zero" mark)
   already wears it, so this matches rather than invents. In dark it resolves
   to #8a877e, a warm grey — near --ink3, deliberately. The word is not asked
   to shout: the distinction is carried THREE independent ways, by the labelled
   firm/on-request divider, by the 45deg hatch across the cell, and by this
   word. Colouring 14 of 32 cells loudly would put the noise straight back.

   font-size AND letter-spacing MUST BE RESTATED, AND THIS IS THE !important
   TRAP THIS APP WARNS ABOUT. instrument.css has a blanket
   `body :not(svg):not(svg *) { font-size: var(--t-sm) !important;
   letter-spacing: normal !important }`. That is (0,0,3) — three type
   selectors — and two !important declarations resolve by SPECIFICITY, not by
   source order, so it reached this span and rendered "ON REQUEST" at 15px
   beside an 11px "CAP BID". .op-unit itself escapes only because
   instrument.css names `body .op-unit` explicitly; a NEW child of it inherits
   nothing of that protection. Measured before the fix: unit 11px/0.88px,
   span 15px/normal. `inherit` rather than a hardcoded 11px so the two can
   never drift apart again. (0,4,0) beats (0,0,3). */
.op-mx-c.val .op-unit .op-rfq {
  color: var(--warn);
  font-size: inherit !important;
  letter-spacing: inherit !important;
}

/* Two classes, not one, and the padding is restated. style.css:303 has a bare
   `.empty { padding: 26px 15px; text-align: center; font-size: 11.5px }` for
   empty table bodies. It loses to `.op-mx-c` today only because opps.css is
   linked after style.css — a link-order dependency is not a decision. */
.op-mx-c.empty { color: var(--ink3); opacity: .45; padding: 6px 5px; font-size: 12px; }
/* Priced, but filtered out by a control the reader is holding. Must not look
   like the unpriced cell above — that would turn their own filter into a
   statement about our coverage. */
.op-mx-c.off { color: var(--ink3); opacity: .55; border: 1px dashed var(--line);
  cursor: help; }

/* .op-cf IS NO LONGER EMITTED ON THE BOARD — the badge row is gone from the
   matrix cell. The finding that used to sit here was: "the three
   non-negotiables, printed on the cell. Never a tooltip: the caveat that a
   credit sells your upside is the single most load-bearing sentence in this
   section, and hover text is not a place a sentence can live."
   THAT IS STILL TRUE AND IS STILL HONOURED — one level up. The sentence lives
   in VISIBLE body text in .op-note above the table ("A net credit is a collar:
   the cap's bid pays for the floor's ask because the upside above the cap is
   sold"), where it is stated once and read once. What was removed is its
   repetition inside all 37 cells in 10px type, which is what the owner was
   reading as noise rather than as a caveat.
   The rule stays for .cf, which the ROWS view still uses. */
.op-cf { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 3px;
  font-style: normal; }
.cf { font-style: normal; font: 10px/1 var(--mono); letter-spacing: .03em;
  padding: 1px 3px; border-radius: 3px; white-space: nowrap;
  border: 1px solid transparent; }
/* OPAQUE, NOT TRANSPARENT — and this is the whole fix.
   These chips were `color-mix(... 16%, transparent)`. A transparent wash of
   --good, sitting on a cell whose background is ALSO a --good wash scaled by
   --i, composites into one flat green: the chip's own tint and the cell's tint
   are the same hue, so they add instead of separating. Measured by pixel
   sample on the deepest-tint cell: 1.61:1 light, 3.34:1 dark. The text and its
   backdrop were very nearly the same colour.
   Mixing into var(--s1) instead of into `transparent` makes the chip an opaque
   pill. It now carries its own surface, so its contrast is a property of the
   chip alone and no longer moves with the cell underneath it — which is what
   made this unfixable by tweaking a percentage. Same reason .op-net had to go
   to full --ink: on this board, anything that inherits the tint loses.
   Verified after the change by sampling rendered pixels, not by computing over
   the declared background, which is wrong by ~4x here. */
.cf.credit { color: var(--good-ink); background: color-mix(in srgb, var(--good) 8%, var(--s1));
  border: 1px solid color-mix(in srgb, var(--good) 30%, var(--s1)); }
.cf.rfq { color: var(--warn-ink); background: color-mix(in srgb, var(--warn) 8%, var(--s1));
  border: 1px solid color-mix(in srgb, var(--warn) 30%, var(--s1)); }
/* .two, never .split — style.css:435 has a bare `.split` two-column grid that
   won this element and stretched the badge across the whole 354px cell. */
.cf.two { color: var(--warn-ink); background: color-mix(in srgb, var(--warn) 8%, var(--s1));
  border: 1px dashed color-mix(in srgb, var(--warn) 55%, var(--s1)); }

/* THE UNREMARKABLE STATE GETS NO BADGE. `.cf.ok` used to paint "firm · one
   venue" on 34 of 37 rows — a chip that says nothing happened, repeated down
   the whole table, which is what made the exceptions hard to find. The plain
   words carry the ordinary case; only "on request" and "2 venues" wear a
   chip, so the eye lands on the three rows that need reading. */
.op-w-q { color: var(--ink3); white-space: nowrap; }
/* Venue is an identity, so the mark travels with the name. */
.op-w-v { display: inline-flex; align-items: center; gap: 5px; min-width: 0;
  color: var(--ink2); }
.op-w-v .mk { flex: none; }

/* UNKNOWN, AND VISIBLY SO. Never a 0, and never a bare dash that could be read
   as nil: four of six venues publish no fill feed and this app's whole claim
   is that it says which figures it does not have. Amber, because absence here
   is a caveat, not a value. */
.op-na { font-style: normal; color: var(--warn); cursor: help;
  letter-spacing: .02em; }

/* Pin. A separate button rather than a modifier-click, because a modifier-click
   is invisible, and rather than a nested control inside the cell button,
   because role=button inside role=button is not reachable by keyboard. */
.op-pin { position: absolute; top: 2px; right: 2px; width: 15px; height: 15px;
  display: grid; place-items: center; padding: 0; cursor: pointer;
  background: none; border: 0; border-radius: 3px;
  color: var(--ink3); opacity: .4; }
.op-mx-c.val:hover .op-pin, .op-pin:focus-visible { opacity: 1; }
.op-pin:hover { color: var(--mint); background: var(--s2); }
.op-pin[aria-pressed="true"] { opacity: 1; color: var(--mint); }

/* The firm / quoted-on-request boundary, drawn rather than implied. */
.op-div { display: flex; align-items: center; gap: 10px; margin: 8px 0 6px;
  color: var(--ink3); font: 10px/1 var(--mono); text-transform: uppercase;
  letter-spacing: .07em; }
.op-div::before, .op-div::after { content: ""; height: 1px; background: var(--line);
  flex: 1; }
.op-div span { white-space: nowrap; }

/* ---- the rows view ------------------------------------------------------
   ONE TRACK LIST, SHARED BY THE HEADER AND EVERY ROW.

   The header used to be its own grid (`24px 92px 46px …`) while the body was a
   24px pin plus an inner grid with its own 8px padding. The two never lined
   up: measured at 1440, every label sat 8px left of the column it named, which
   is why "TENOR SPOT" and "NET STANDING" read as jammed pairs rather than as
   four headings. A header that is not over its column is worse than no header.

   So the tracks are a custom property declared once. The head is the same
   two-track row as a body row — pin spacer, then one inner grid — and the
   inner grids are byte-identical in tracks, gap and padding. It is not
   possible for them to drift.

   Eleven columns, one fact each: figures right, words left, in the split
   grid.js already uses on the Markets board. The last track flexes so the
   table fills a wide stage instead of stopping short of it. */

.op-l { display: flex; flex-direction: column; gap: 1px; overflow-x: auto; }
.op-l {
  --op-l-t: 82px 42px 88px 84px 74px 84px 74px 64px 92px 84px minmax(96px, 1fr);
  --op-l-gap: 10px;
  --op-l-pad: 8px;
  --op-l-min: 1004px;
}
.op-l-r { display: grid; grid-template-columns: 22px 1fr; align-items: stretch;
  min-width: var(--op-l-min); border-radius: 5px; }
.op-l-b, .op-l-h {
  display: grid; align-items: center; width: 100%;
  grid-template-columns: var(--op-l-t);
  gap: 0 var(--op-l-gap);
  padding: 6px var(--op-l-pad);
}
.op-l-b { text-align: left; cursor: pointer;
  background: none; border: 0; font: 11.5px/1.3 var(--mono); color: var(--ink2); }

/* The header: small, muted, furniture. NOT sticky, and deliberately so — the
   board's head is, and copying it here was wrong twice over. `.op-l` carries
   `overflow-x: auto`, which computes overflow-y to auto as well, so the
   nearest scrollport is `.op-l` itself; it never scrolls vertically, so a
   sticky head can never trigger. What the attempt DID produce was the opaque
   `background: var(--bg)` a sticky head needs — a flat near-black band across
   a page whose ground is a gradient, which is the most visible element in a
   row of labels that are supposed to recede. No background, no seam. */
.op-l-r.head { color: var(--ink3); align-items: end; }
.op-l-r.head .op-l-h { padding-top: 2px; padding-bottom: 7px; }
.op-l-r.head .op-l-h > span {
  font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink3); cursor: help; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.op-l-r.head:hover { background: none; }
.op-l-p { width: 22px; }

/* FIGURES RIGHT AND TABULAR, WORDS LEFT. instrument.css already puts `.num`
   in mono with tabular figures, so a `.num` cell here inherits the app's
   numeric face rather than declaring a second one. */
.op-l-r.head .num, .op-l-b .num { text-align: right; }
.op-l-b .num { font-variant-numeric: tabular-nums; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.op-l-b b { color: var(--ink); font-weight: 600; }
.op-l-b i { font-style: normal; color: var(--ink3); font-size: 10.5px; }
.op-l-a { display: flex; align-items: center; gap: 6px; min-width: 0; }
.op-l-a b { overflow: hidden; text-overflow: ellipsis; }
.op-l-a .mk { flex: none; }
/* Reserve the mark's slot whether or not one draws, so the tickers keep their
   left edge. Same reasoning as .op-mx-a: eight assets have authored art and
   the rest are equities we have no licence to draw. */
.op-l-a { --mk: 18px; }
.op-l-a:not(:has(.mk))::before { content: ""; flex: none; width: var(--mk); }
/* The word columns. Left-aligned, one state each, nothing wrapping. */
.op-l-w { display: flex; align-items: center; min-width: 0; white-space: nowrap;
  overflow: hidden; }
.op-l-r:hover { background: var(--glass); }
.op-l-r.credit { box-shadow: inset 3px 0 0 var(--good); }
.op-l-r.credit .num b { color: var(--good); }
.op-l-r.indic { background-image: repeating-linear-gradient(-45deg,
  var(--glass) 0 4px, transparent 4px 8px); }
.op-l-r.pinned { box-shadow: inset 0 0 0 1px var(--mint2); }
.op-l-r.credit.pinned { box-shadow: inset 3px 0 0 var(--good), inset 0 0 0 1px var(--mint2); }
.op-l-r.open { background: var(--s1); }
.op-l-r .op-pin { position: static; width: 22px; height: auto; opacity: .45; }
.op-l-r:hover .op-pin { opacity: 1; }

/* ---- the expansion -----------------------------------------------------
   What the hover popover used to be, clicked and persistent. Full width under
   the row so the cell and its two legs are never separated by a scroll. */
.op-x { border: 1px solid var(--mint2); border-radius: var(--r);
  background: var(--s1); padding: 11px 13px 12px; margin: 3px 0 5px;
  font: 11.5px/1.5 var(--mono); color: var(--ink2); }




.op-xb { font: 11px/1 var(--mono); padding: 7px 11px; cursor: pointer;
  background: var(--mint-dim); color: var(--mint);
  border: 1px solid var(--mint2); border-radius: 5px; }
.op-xb.ghost { background: var(--s2); color: var(--ink2); border-color: var(--line2); }
.op-xb.ghost:hover { color: var(--ink); border-color: var(--mint2); }
.op-xb.on { background: var(--mint2); color: var(--bg); }
.op-xb:focus-visible { outline: 1.5px solid var(--mint2); outline-offset: 1px; }

/* ---- fine print ---------------------------------------------------------

   THE LEGEND IS GONE, AND SO IS ITS STYLING.

   `.op-key` was a six-item swatch legend under the board: a colour chip, the
   badge, and a sentence explaining it, six times over. It is the run-together
   text the owner reported — six labels and six explanations that at any width
   where the row wrapped, and in any copy of the page, read as one sentence
   with no punctuation between the parts.

   It was also the last of this section's prose blocks. instrument.css §37
   already moved .op-lede, .op-note and .op-fine behind the click on the same
   argument; the legend was the block that pass did not catch. Every word of it
   is now a `title` on the column header or the badge it describes, and the row
   expansion still spells both legs out in full on click.

   Rule for anything added below this line: if it explains, it is a title or it
   is in the expansion. The table itself has to be clear enough. */

.op-none { color: var(--ink3); font-size: 12.5px; line-height: 1.6; max-width: 68ch;
  padding: 10px 0; }
.op-fine { color: var(--ink3); font-size: 11px; line-height: 1.6; max-width: 82ch;
  margin: 14px 0 0; }
.op-fine b { color: var(--ink2); }

@media (max-width: 720px) {
  .op { padding: 14px 12px 20px; }
  .op-ctl { gap: 8px 12px; }
  /* Narrower tracks so three tenors need less sideways scroll, but still an
     explicit count — never auto-fit. See the note on .op-mx-r. */
  .op-mx-r { grid-template-columns: 104px repeat(var(--t, 3), minmax(84px, 1fr));
    min-width: calc(104px + var(--t, 3) * 88px); }
  .op-mx-a { --mk: 0px; gap: 5px; padding: 7px 5px; font-size: 11.5px; }
  .op-t-g { grid-template-columns: 64px repeat(var(--n), minmax(128px, 1fr)); }
  /* ELEVEN COLUMNS DO NOT FIT A PHONE, AND TIGHTENING THEM DOES NOT HELP.
     Measured at 390: the full track list needs 900px inside a 326px box, so
     the NET — the column this board is ordered and shaded by — sits three
     screen-widths off to the right. A reader would scroll past everything to
     reach the one figure they came for.
     So the five figure columns between Spot and Bid come out on a phone. They
     are NOT deleted: tapping the row spells both legs out in full, with venue,
     strike and price, which is exactly where the brief puts detail. What stays
     is what a phone can be scanned for — which asset, how long, what it nets,
     and the two states that change what the row IS. Firmness and split
     execution never come out at any width; those are the two facts this
     section has shipped wrong before. */
  .op-l {
    --op-l-t: 66px 40px 58px 84px 78px minmax(84px, 1fr);
    --op-l-gap: 8px; --op-l-pad: 6px; --op-l-min: 486px;
  }
  .op-l-b > :nth-child(n+3):nth-child(-n+7),
  .op-l-h > :nth-child(n+3):nth-child(-n+7) { display: none; }
}



/* THE CASH, ON THE CELL. The board led with the collar's net — 0.21% on a BTC
   row whose cap alone pays $817.71 — and buried the concrete number four
   paragraphs down. This puts it on the face. Deliberately smaller and dimmer
   than the net: it is the covered call's number, not this row's, and promoting
   it to equal weight would make the board look like it is quoting something it
   is not. */
.op-mx-c.val.indic 








/* ---- the expansion, rebuilt as five lines ---------------------------------
 *
 * Measured before: 657px / 201 words. Two rounds of trimming got to 464px and
 * the owner's verdict was still "not fixed" — correctly, because the problem
 * was never the wording. It was SEVEN STACKED BLOCKS, each with its own
 * padding, border and margin: a header restating the cell just clicked, a
 * headline panel, a legs group, a comparison paragraph, one or two caveat
 * paragraphs, a button row and a footer disclaimer. Chrome was most of the
 * height. 60px of it was padding on blocks whose content was one line.
 *
 * So the blocks are gone. Five lines, no internal borders, no nested panels:
 *
 *   1  the cash, big — the number that decides whether you read on
 *   2  the arithmetic that turns it into what you actually hand over
 *   3  both legs and the tenor, inline
 *   4  caveats as inline marks ON the row they qualify (title carries the
 *      full sentence — nothing is deleted, only unstacked)
 *   5  the two actions
 *
 * Anything that was a paragraph and is now a title() was checked first for
 * whether it is a WARNING. Warnings keep a visible mark; only their
 * explanation moved to hover. A caveat you cannot see is a caveat nobody read
 * — that finding is why the popover was killed in the first place, and this
 * file is not allowed to reintroduce it.
 */
.op-x {
  padding: 11px 13px 10px;
  display: grid; gap: 7px;
}

/* THE PANEL IS TWO COLUMNS: the answer, and the position.
   See the comment on expansion() in opps.js for what was wrong with the five
   full-width bands this replaces. Left column is capped at a reading measure
   so the sentence stops running the panel's full width; right column sizes to
   its own content and holds the legs, the caveats and the actions. */
.op-x {
  padding: 13px 15px 12px;
  display: grid;
  /* auto, not 1fr, on the first track: 1fr made column one absorb the whole
     panel while .op-x-sum stayed capped at its measure, so the legs were
     pushed 400px away from the arithmetic they belong to — the same
     scattering this restructure exists to fix, just in the other axis.
     Sizing to content keeps the two columns adjacent at any panel width. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 10px 34px;
  position: relative;
}
.op-x-close {
  position: absolute; top: 8px; right: 8px;
  padding: 2px 7px; font-size: 12px; line-height: 1.2;
}

/* ---- left: the answer -------------------------------------------------- */
.op-x-sum { display: grid; gap: 3px; max-width: 44ch; }
.op-x-lab {
  font-family: var(--mono) !important;
  font-size: 10px !important;
  color: var(--ink3);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: .07em !important;
}
/* THE NET IS THE HERO, at the size the cap premium used to occupy. It is the
   only figure on this panel the reader cannot get from the cell they clicked,
   which is the whole reason the panel opens. --ink, not green: green is this
   section's mark for a CREDIT, and spending it on every net would leave the
   credit case with nothing left to say. */
.op-x-net {
  /* !important, and it is the house pattern rather than a shortcut:
     instrument.css normalises every element to var(--t-sm) with !important,
     so a component that wants its own size states it the same way. A class
     outranks that rule's type selectors, so this wins on specificity and not
     on file order. */
  font-family: var(--mono) !important;
  font-size: 26px !important;
  font-weight: 700 !important;
  line-height: 1.05;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em !important;
}
.op-x-net.cr { color: var(--good); }
.op-x-eq {
  font-family: var(--mono) !important;
  font-size: 12.5px !important;
  line-height: 1.45; color: var(--ink2);
  font-variant-numeric: tabular-nums;
}
.op-x-eq em { font-style: normal; color: var(--ink3); padding: 0 3px; }
.op-x-note {
  font-family: var(--sans) !important;
  font-size: 11.5px !important;
  line-height: 1.45; color: var(--ink3);
}

/* ---- right: the position ----------------------------------------------- */
.op-x-pos { display: grid; gap: 9px; justify-items: start; }
/* auto-fit rather than a fixed three, because the cap leg is absent whenever
   nothing is priced at the tenor and a hard column count would leave a hole. */
.op-x-legs {
  display: flex; flex-wrap: wrap; gap: 4px 26px;
  padding-top: 2px;
}
.op-x-leg { display: grid; gap: 1px; }
.op-x-leg i {
  font-family: var(--mono) !important;
  font-size: 10px !important;
  font-style: normal; color: var(--ink3); line-height: 1;
  text-transform: uppercase; letter-spacing: .06em !important;
}
.op-x-leg b {
  font-family: var(--mono) !important;
  font-size: 15px !important;
  font-weight: 600 !important;
  line-height: 1.2;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The distance and the venue read as one line because that is one fact: where
   this strike sits, and who is quoting it. u carries no underline — it is the
   receding half, not a link. */
.op-x-leg u {
  text-decoration: none;
  font-family: var(--mono) !important;
  font-size: 11px !important;
  line-height: 1.3; color: var(--ink3);
  font-variant-numeric: tabular-nums;
}

/* NARROW: the two columns become two rows rather than compressing the measure
   to nothing. The answer stays first — it is what the panel is for. */
@media (max-width: 900px) {
  .op-x { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .op-x-sum { max-width: none; }
}

/* 4 — caveats, inline. Amber is the only colour used here and it is used for
   nothing else in this section, so a mark is legible without reading it. */
.op-x4 { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.op-t.warn {
  font: 10.5px/1 var(--mono); font-style: normal;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--warn); background: color-mix(in srgb, var(--warn) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 26%, transparent);
  border-radius: 3px; padding: 3px 6px;
  cursor: help;
}

/* 5 — actions */
.op-x5 { display: flex; flex-wrap: wrap; gap: 6px; }

/* .op-foot is gone with the legend it framed. It held a hairline and a 20px
   lead-in to separate "the notes on the board" from the board; with the
   legend removed the only thing left inside it was .op-fine, which
   instrument.css §37 hides — so the rule drew a border under an empty box. */

/* ══════════════════════════════════════════════════════════════════════════
   STRATEGIES — AIR, AND A NUMBER THAT LEADS (2026-08-05)

   The overview and the venue panel were rebuilt to open with an answer at
   display size. This surface was still opening with a dense matrix at 12px,
   which made it the oldest-looking screen in the app the moment the other two
   changed.

   Same treatment, same reasoning: our body text measured 12px against 16px on
   every reference site. Density reads "terminal"; air reads "product". Nothing
   here changes the numbers or the palette — only the room they get.

   !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 order.
   ══════════════════════════════════════════════════════════════════════════ */

/* The live count stops being a caption and becomes the section's headline
   figure — it is the only number on this screen that summarises the rest. */
body .op .op-sub-h {
  font-size: 15px !important;
  color: var(--ink) !important;
  margin: 2px 0 20px !important;
  letter-spacing: -0.01em !important;
}

/* The board itself: room to breathe. */
body .op .op-mx-r > * { padding-top: 12px !important; padding-bottom: 12px !important; }  /* §69 ladder: was 11px */
body .op .op-mx-c.val .op-cash { font-size: 17px !important; letter-spacing: -0.02em !important; }
body .op .op-mx-c.val .op-net  { font-size: 11px !important; }
body .op .op-mx-a b { font-size: 13px !important; }  /* §69 scale: was 14px */

/* Column headers step back so the figures carry the board — the same
   relationship the Markets headline uses. */
body .op .op-mx-r.head > * {
  font-size: 11px !important;    /* §69 scale: was 10px */
  letter-spacing: .08em !important;
  text-transform: uppercase;
  color: var(--ink3) !important;
  padding-bottom: 12px !important;  /* §69 ladder: was 10px */
}

/* Controls get real targets rather than dense chips. */
body .op .op-b, body .op .op-chip {
  padding: 8px 13px !important;
  font-size: 12px !important;
  border-radius: 8px !important;
}
body .op .op-ctl { gap: 8px !important; margin-bottom: 18px !important; }

/* Section rhythm: space between blocks, not borders around them. */
body .op section, body .op .op-sec { margin-bottom: 26px !important; }

/* ══════════════════════════════════════════════════════════════════════════
   THE ROWS TABLE IS A DENSE SURFACE, AND INSTRUMENT.CSS HAS A TOKEN FOR IT

   §3 of instrument.css steps every unlisted element to --t-sm (15px) and then
   exempts, by hand, a list of "tables with more columns than a 1440px viewport
   comfortably holds" down to --t-dense (13px). The tape, the board matrix and
   the coverage table are on that list. The rows view was not — it had seven
   columns when the list was written — and at eleven it is squarely the surface
   the token exists for.

   So it takes the TOKEN, not a number of its own. Nothing here invents a size;
   this is the same step .op-mx-c already gets one file over.

   The !important is not decorative. instrument.css loads after this file with
   a `body *:not(svg):not(svg *)` !important blanket, and two !important
   declarations resolve by specificity rather than by order — which the class
   chain here wins.
   ══════════════════════════════════════════════════════════════════════════ */
body .op .op-l-b,
body .op .op-l-b span,
body .op .op-l-b b,
body .op .op-l-b i,
body .op .op-l-b em { font-size: var(--t-dense, 13px) !important; }

/* Headers small and muted — furniture, not content. The figures carry the
   table; the labels only have to be findable. */
body .op .op-l-h > span {
  font-size: var(--t-xs, 10px) !important;
  letter-spacing: 0.08em !important;
  color: var(--ink3) !important;
}

/* The board cell's unit label sits directly under the figure and must not
   compete with it: this is the micro-label step, the same one every other
   unit on the page takes. */
body .op .op-mx-c.val .op-unit {
  font-size: var(--t-xs, 10px) !important;
  letter-spacing: 0.08em !important;
  text-transform: uppercase;
  color: var(--ink3) !important;
  line-height: 1.2;
}

/* The tray is the rows table transposed and carries the same eleven facts, so
   it takes the same step rather than the 15px default that would make a
   two-column compare taller than the board it compares. */
body .op .op-t-c, body .op .op-t-c * { font-size: var(--t-dense, 13px) !important; }
body .op .op-t-k { font-size: var(--t-xs, 10px) !important; letter-spacing: 0.06em !important; }
