/* il-panels.css — the seven-panel vocabulary + 12-col grid from the
   "Investor Leveling v2" design canvas, in this app's own token language.
   ===========================================================================
   Panels are real .card elements (data-il-panel is a hook, not the shape
   source — style.css's [data-shape] rules already key off .card, so every
   panel gets the existing boot/hover grammar for free). Nothing here uses a
   literal colour; every value is a theme.js token or an existing radius,
   border or text variable, same rule as il-app-skin.css and il-design-bridge.

   Do not write a "--radius-*" style wildcard in a comment in this file: the
   asterisk-slash pair closes the comment early, the remaining prose parses as
   stray tokens, and the parser then discards everything up to the next thing
   it can resynchronise on -- which silently ate the grid rule below for the
   whole first half of this port, leaving every panel stacked full width.

   Load order: after il-design-bridge.css, before il-chrome.js runs.
     <link rel="stylesheet" href="/il-design-bridge.css">
     <link rel="stylesheet" href="/il-panels.css">
   =========================================================================== */

/* ===========================================================================
   1. The 12-column grid
   =========================================================================== */

.il-grid[data-il-grid] {
  display: grid; grid-template-columns: repeat(12, 1fr);
  gap: 16px; align-items: start; min-width: 0;
}
.il-grid[data-il-grid] > * { min-width: 0; }
/* All twelve, not just the four that happened to be used first. An undefined
   span silently fell through to `grid-column: auto` — one column of twelve,
   65px wide — and a panel does not look like it is missing a rule at 65px, it
   looks like the content broke. Found by asking for span 7 and span 5 on the
   arena page and getting two 65px columns and a 3,821px view. */
.il-grid > [data-span="1"]  { grid-column: span 1; }
.il-grid > [data-span="2"]  { grid-column: span 2; }
.il-grid > [data-span="3"]  { grid-column: span 3; }
.il-grid > [data-span="4"]  { grid-column: span 4; }
.il-grid > [data-span="5"]  { grid-column: span 5; }
.il-grid > [data-span="6"]  { grid-column: span 6; }
.il-grid > [data-span="7"]  { grid-column: span 7; }
.il-grid > [data-span="8"]  { grid-column: span 8; }
.il-grid > [data-span="9"]  { grid-column: span 9; }
.il-grid > [data-span="10"] { grid-column: span 10; }
.il-grid > [data-span="11"] { grid-column: span 11; }
.il-grid > [data-span="12"] { grid-column: span 12; }

/* One rule per side of the fold rather than per span: anything that asked for
   half the grid or less pairs up, anything wider takes the full width. */
@media (max-width: 900px) {
  .il-grid > [data-span="1"], .il-grid > [data-span="2"],
  .il-grid > [data-span="3"], .il-grid > [data-span="4"],
  .il-grid > [data-span="5"], .il-grid > [data-span="6"] { grid-column: span 6; }
  .il-grid > [data-span="7"],  .il-grid > [data-span="8"],
  .il-grid > [data-span="9"],  .il-grid > [data-span="10"],
  .il-grid > [data-span="11"], .il-grid > [data-span="12"] { grid-column: span 12; }
}
@media (max-width: 640px) {
  .il-grid > [data-span] { grid-column: span 12; }
}

/* A panel is a .card with extra breathing room removed — .card's own
   padding already applies; these rules only style what's INSIDE it. */
[data-il-panel] .eyebrow { margin-bottom: .3rem; }
[data-il-panel] h3:first-child, [data-il-panel] .eyebrow:first-child + h3 { margin-top: 0; }

/* Panel header: title left, meta right, on one baseline. The title is small
   mono caps rather than this app's serif h3 — in the canvas the serif is spent
   once per screen, on the view title, and every panel underneath labels itself
   quietly in mono. Using the serif at both levels is what made the old cards
   compete with the page header instead of sitting under it.

   The h3 selector carries [data-il-panel] so it outranks style.css's own h3
   rules (0,2,1 against their (0,1,1)) — the cascade trap this project keeps
   walking into; scripts/check_css_specificity.py covers this file. */
/* It wraps. Unwrapped, a title and its aside split whatever width there was:
   on a phone "YOUR RUN" broke into two lines beside a meta that still did not
   fit, and "preview" slid under the "Open the screener" button beside it; on a
   narrow desktop panel "THE SHAPE OF / A COMPANY" and "6 from the / screen"
   were both folded in half. Now the aside drops to its own line when the two
   do not fit, and margin-left: auto keeps it at the right on either line. */
[data-il-panel] > .il-panel-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 6px 14px; margin: 0 0 14px;
}
[data-il-panel] > .il-panel-head h3 {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-secondary); margin: 0; line-height: 1.3;
}
/* Transitional, and the single highest-leverage rule in this file. The 28
   views not yet rebuilt on the panel helpers still label their sections with a
   34px Instrument Serif <h2>, sitting directly under #viewHead's serif view
   title — two display headings stacked, which is most of why those screens
   still read as the old app wearing the new skin. The canvas spends the serif
   once per screen, on the view title, and labels everything under it in mono
   caps. This gives those legacy headings the same treatment a real panel title
   gets, so the whole app reads on one typographic system while the views are
   converted one at a time rather than all at once.

   Beats il-app-skin.css's `h1, h2, h3` (0,0,1) and its `h2` size rule on
   specificity, and `.card.hero h2` (0,2,1) on load order. Remove selectors from
   here as each view stops needing them. */
.view .card > h2,
.view > h2,
/* a card title wrapped in a panel head is still a card title -- putting the
   heading inside .il-panel-head to hang a control off it must not quietly
   return it to the display serif */
.view .card > .il-panel-head > h2,
.view .card > .il-panel-head > h3,
/* A card whose title is an h3 is doing the same job as one titled with an h2 —
   only the first one, so a genuine sub-heading further down a long card keeps
   its own weight. */
.view .card > h3:first-child,
/* .sectionhead is already the canvas's panel header — a title with a count or
   a hint pushed to the right — so it gets the panel-title treatment too. */
.view .sectionhead > h3 {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-secondary); line-height: 1.35; margin: 0 0 12px;
}
.view .sectionhead > h3 { margin: 0; }

/* An <h1> inside a view is never the page's title — #viewHead already rendered
   that, at clamp(38px, 5.4vw, 62px). The character view's rank title is the
   one that does this, and at full h1 size it sets the same serif at the same
   weight directly under "Character". Keep it a headline, but a subordinate
   one. */
.view .card h1 { font-size: clamp(23px, 2.6vw, 29px); line-height: 1.12; }
/* The blurb that usually follows one of those headings is the panel's meta in
   the canvas, so it should sit tight under the label rather than under a
   display heading's generous leading. */
.view .card > h2 + p.small,
.view .card > h2 + p.muted { margin-top: 0; }

[data-il-panel] > .il-panel-head .il-panel-aside {
  display: flex; align-items: center; gap: 10px; flex: 0 1 auto; min-width: 0;
  margin-left: auto;
}
/* The three windows -- the Buy ticket, a figure, a macro series -- carry a
   panel head without being panels, so none of the head's layout reached
   them: the title sat alone on its line and "$10,000 in cash" with the Close
   button wrapped under it, at the left. The same row as a panel: the title
   left, the meta and Close right, wrapping only when they do not fit. The
   title keeps its display serif, being the one heading on its overlay. */
.onboard-modal > .il-panel-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 6px 14px; margin: 0 0 14px;
}
.onboard-modal > .il-panel-head > h3 { margin: 0; }
.onboard-modal > .il-panel-head .il-panel-aside {
  display: flex; align-items: center; gap: 10px; flex: 0 1 auto; min-width: 0;
  margin-left: auto;
}
[data-il-panel] > .il-panel-head .il-panel-meta {
  font-size: 11.5px; color: var(--text-muted); text-align: right; min-width: 0;
}

/* ===========================================================================
   2. Stats — label/value/note tiles
   =========================================================================== */

/* Values are mono, not the display serif. Instrument Serif was used here
   originally and its digits are far too condensed at this size — a lone "0"
   renders as a hairline oval that reads as empty parentheses, which made the
   dashboard's four headline figures unreadable as numbers. The canvas sets
   these in IBM Plex Mono 27px/600 with tabular figures; that is also the
   reason the numbers line up column-to-column here. The serif is spent once
   per screen, on the view title. */
.il-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 16px;
}
.il-stat { display: flex; flex-direction: column; gap: 5px; }
.il-stat-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 10px;
  letter-spacing: .16em; text-transform: uppercase;
}
.il-stat-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 27px; font-weight: 600; line-height: 1.05;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.il-stat-note { font-size: 12.5px; line-height: 1.45; }

/* .stat is the older tile this app already had — twelve of them across the
   company, portfolio and gate views — and its value was set in the display
   serif at 2.15rem, which is the same unreadable-zero problem the panel tiles
   had: Instrument Serif's digits are too condensed at that size and a lone "0"
   reads as empty parentheses. Same fix, so both kinds of tile now show a figure
   the same way. Size stays larger than .il-stat-value because these sit two to
   a row rather than four. */
.stat .value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 1.6rem; font-weight: 600; letter-spacing: 0;
}

/* ===========================================================================
   3. Rows — rank chip + label + note + value
   =========================================================================== */

.il-rows { display: flex; flex-direction: column; }
/* --row-pad travels with the shape (theme.js SHAPES.rowPad): Blade's rows sit
   tight at 10px, Soft's breathe at 15px. Falls back to the old flat 11px if
   theme.js hasn't run yet. */
.il-row-item {
  display: flex; align-items: center; gap: 12px;
  padding: var(--row-pad, 11px) 0; border-bottom: 1px solid var(--border);
}
.il-row-item:last-child { border-bottom: 0; padding-bottom: 0; }
.il-row-item:first-child { padding-top: 0; }
.il-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.il-row-main b { font-family: "IBM Plex Mono", monospace; }
.il-row-value {
  flex: none; font-family: "IBM Plex Mono", monospace; font-size: 13px;
  font-variant-numeric: tabular-nums; text-align: right;
}

/* Direction, in the value itself rather than a chip beside it.
   `data-il-dir` was written into panelRows() and panelTable() from the first
   day of the panel port and never had a rule to its name, so every value that
   claimed to be good or bad rendered the same neutral ink -- the attribute was
   in the DOM, doing nothing. Colour is the canvas's own way of saying it: a
   figure that cleared its bar is simply green, and the sentence next to it
   carries the wording. Both hues are theme tokens, so they invert with the
   mood like everything else, and the direction is never colour alone -- the
   row's note states it in words for anyone who cannot separate the two. */
.il-row-value[data-il-dir="good"], td[data-il-dir="good"],
.il-stat-value[data-il-dir="good"] { color: var(--good); }
.il-row-value[data-il-dir="bad"],  td[data-il-dir="bad"],
.il-stat-value[data-il-dir="bad"]  { color: var(--critical); }
.il-row-value[data-il-dir="warn"], td[data-il-dir="warn"],
.il-stat-value[data-il-dir="warn"] { color: var(--warning); }

/* ===========================================================================
   4. Bars — labelled percentage bars
   =========================================================================== */

.il-bars { display: flex; flex-direction: column; gap: 14px; }
.il-bar-track {
  height: 6px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--border-strong) 70%, transparent);
  overflow: hidden; margin-top: 6px;
}
.il-bar-fill[data-stat-fill] {
  height: 100%; border-radius: inherit;
  background: linear-gradient(90deg, var(--brand), var(--brand-deep));
  transition: width .5s cubic-bezier(.2,.85,.24,1);
}

/* Industry comparison bars: the same track with the median marked on it.
   Scoped to .indbars rather than changing .il-bar-track, because the track's
   `overflow: hidden` is right for every other use of it and would clip this
   one's tick — and because the fill here is coloured by direction rather than
   by the brand gradient. */
.indbars .il-bar-track { overflow: visible; position: relative; height: 8px; }
.indbars .il-bar-fill {
  height: 100%; border-radius: inherit;
  transition: width .5s cubic-bezier(.2,.85,.24,1);
}
.indbars .il-bar-mark {
  position: absolute; top: -3px; width: 2px; height: 14px;
  margin-left: -1px; border-radius: 1px;
  background: var(--text-primary); opacity: .75;
}

/* ===========================================================================
   5. Ladder — wraps the existing rankLadder() markup; no extra styling
   needed here, it already carries its own classes from app.js.
   =========================================================================== */

/* ===========================================================================
   6. Note — prose + "k: t" bullets
   =========================================================================== */

.il-note-list { list-style: none; margin: .8rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.il-note-list li { font-size: 13.5px; line-height: 1.6; }

/* ===========================================================================
   7. Kv — two-column key/value list
   =========================================================================== */

.il-kv { display: flex; flex-direction: column; }
.il-kv-row {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.il-kv-row:last-child { border-bottom: 0; padding-bottom: 0; }
.il-kv-row:first-child { padding-top: 0; }

/* ===========================================================================
   8. Command palette + company drawer
   ===========================================================================
   Reuses il-design-bridge.css's existing .il-scrim/.il-drawer stubs
   (il-design-bridge.css:194-209) for the drawer's backdrop/slide-in shell;
   this file adds the command palette (new) and the drawer's own sizing.
   =========================================================================== */

.il-scrim {
  position: fixed; inset: 0; z-index: 92;
  background: color-mix(in oklab, var(--plane) 25%, #000 75%);
  backdrop-filter: blur(6px);
}
.il-scrim[hidden] { display: none; }

.il-cmdk {
  position: fixed; top: 14vh; left: 50%; transform: translateX(-50%);
  z-index: 93; width: min(560px, 92vw);
  background: var(--surface-1); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: var(--il-panel-shadow, 0 24px 60px -28px rgba(0,0,0,.5));
  overflow: hidden;
}
.il-cmdk[hidden] { display: none; }
.il-cmdk input {
  width: 100%; box-sizing: border-box; border: 0; outline: none;
  padding: 16px 18px; font-family: Manrope, sans-serif; font-size: 15px;
  background: transparent; color: var(--text-primary);
  border-bottom: 1px solid var(--border);
}
.il-cmdk #cmdList { max-height: 50vh; overflow-y: auto; padding: 6px; }
.il-cmdk .il-cmd-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius); cursor: pointer;
  font-size: 13.5px; color: var(--text-secondary);
}
.il-cmdk .il-cmd-item[aria-selected="true"],
.il-cmdk .il-cmd-item:hover { background: color-mix(in oklab, var(--brand) 10%, transparent); color: var(--text-primary); }
.il-cmdk .il-cmd-kind {
  font-family: "IBM Plex Mono", monospace; font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-muted); flex: none;
}
.il-cmdk .il-cmd-empty { padding: 18px 12px; text-align: center; color: var(--text-muted); font-size: 13px; }

/* [hidden] is display:none!important globally (style.css:266, on purpose --
   see the "[hidden] silently overridden" fix in the project history) so the
   drawer's open/close is a plain show/hide, not a transform it would fight. */
.il-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 93;
  width: min(440px, 100vw); overflow-y: auto;
  background: var(--surface-1); border-left: 1px solid var(--border-strong);
  box-shadow: var(--il-panel-shadow, -24px 0 60px -28px rgba(0,0,0,.5));
  padding: 22px 24px 40px;
}
/* The drawer is a flex column (il-design-bridge.css), where a float does
   nothing: the Close button's float:right was stretched into a full-width
   bar across the top of every drawer, the loudest thing in it. It sits at the
   right at its own size, as the float meant. */
.il-drawer > #drawerClose { align-self: flex-end; }

kbd { font-family: "IBM Plex Mono", monospace; font-size: 11px; }

/* The first-visit gate's own title keeps the serif: it is the one heading on
   that overlay, so there is nothing for it to compete with — the "spend the
   serif once per screen" rule is about a view header and a panel title
   colliding, and the gate has no view header. Sized between the two. */
.onboard-modal .onboard-title {
  font-family: "Instrument Serif", Georgia, serif; font-weight: 400;
  font-size: clamp(26px, 3.4vw, 34px); line-height: 1.08;
  letter-spacing: -.01em; margin: .1rem 0 .6rem; text-transform: none;
  color: var(--text-primary);
}
.onboard-modal > .eyebrow { margin-bottom: 0; }

/* The model page's rationale filter. Rows are hidden with display:none rather
   than removed, so every entry stays in the DOM — the record is the feature,
   and Ctrl+F has to keep finding all of it. */
#ratCard[data-rat-filter="changed"] .ratrow:not(.changed),
#ratCard[data-rat-filter="kept"] .ratrow.changed { display: none; }
#ratCard .il-panel-head { flex-wrap: wrap; }
#ratCard .seg button { font-size: 11px; padding: 4px 9px; }

/* Each group is a <details>, so the page opens as a contents list rather than
   3,584px of prose. The summary has to go on reading as the section label it
   replaced — a mono-caps eyebrow — not as the 14px Manrope sentence that
   details.doc's own summary rule would otherwise give it. The count sits after
   the name so a closed group still says how much is inside it.
   Qualified with #ratCard because `details.doc summary` in il-app-skin.css is
   (0,1,2) and a plain `.ratgroup > summary` is only (0,1,1) — it lost outright
   regardless of load order, and rendered 14px Manrope. Same cascade trap that
   has cost this port five fixes already. */
#ratCard .ratgroup > summary {
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text-secondary);
  padding: .7rem 0 .45rem; display: flex; align-items: baseline; gap: .5rem;
  flex-wrap: wrap;
}
/* The finding drops to its own line under the group's name and count: it is a
   sentence, and a sentence does not belong in a baseline row of mono labels.
   Scoped through #ratCard for the reason spelled out above this block -- a
   plain `.ratfinding` would lose to the summary rule's own typography.

   `flex-basis: 100%` alone does NOT force the wrap: a max-width on a flex item
   clamps its *hypothetical* main size before the line is broken, so the item
   measured 625px, fit beside the count, and sat on the same row. The reading
   width and the line break have to live on different boxes -- the item wraps,
   the text inside it is what gets capped. */
#ratCard .ratgroup > summary .ratfinding {
  flex: 0 0 100%; max-width: 100%; margin-top: .28rem;
  font-family: var(--font); font-size: 12.5px; font-weight: 400;
  letter-spacing: 0; text-transform: none; line-height: 1.5;
  color: var(--text-muted);
}
#ratCard .ratgroup > summary .ratfinding > span { display: block; max-width: 82ch; }
#ratCard .ratgroup[open] > summary .ratfinding { color: var(--text-secondary); }
/* the disclosure triangle is a flex item here, so its own margin would stack
   on top of the gap */
#ratCard .ratgroup > summary::before { margin-right: 0; }
#ratCard .ratgroup[open] > summary { color: var(--brand); margin-bottom: 0; }
#ratCard .ratgroup > summary .small { letter-spacing: .04em; text-transform: none; }
#ratCard .ratgroup[open] { padding-bottom: .3rem; }

/* ===========================================================================
   A view's own explanation
   ===========================================================================
   Every view opened with a heading and three to six lines saying why the view
   exists. Good prose, and in the way by the sixth visit — which is the case
   the ⓘ was built for. The heading keeps its place and the paragraph moves
   behind the same control the panels use, so there is still exactly one way to
   ask this app for more detail.

   Baseline rather than centre: the button is small and a heading is large, and
   centring puts the dot of the "i" level with the middle of a 34px serif. */
.hero-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.hero-head > h2 { margin-right: 0; }

/* ===========================================================================
   The company page's section tabs
   ===========================================================================
   Sticky under the top bar, because the whole point is knowing which of the
   five groups you are three screens into. 53px is the top bar's height, the
   same offset the nav rail uses. */
/* The company page's snowflake is authored at 340 units and `svg()` stretches
   it to its host, so a wide card rendered it at 1.95x with its axis labels at
   19.5px — twice the size the same labels take everywhere else. Capping the
   host is the right lever here rather than measuring: unlike a bar chart, a
   radar has an intended size, and past it the shape stops reading as a shape
   and starts reading as a diagram. */
#profileRadar { max-width: 340px; }

/* The drawer's shape. Centred and capped for the same reason as the company
   page's radar: a snowflake has an intended size, and the drawer is narrow
   enough that letting it fill would crowd the panel it opens underneath. */
.drawer-shape { margin: 18px 0 4px; text-align: center; }
.drawer-shape #drawerFlake { max-width: 260px; margin: 0 auto; }
.drawer-shape p { margin: .4rem 0 0; }
#drawerStat:not(:empty) { margin-top: 14px; }

/* The way in, sized like one: a field and a button on one line, no surface. */
.corow { display: flex; gap: 10px; align-items: center; margin: 0 0 14px; }
.corow input {
  flex: 0 1 260px; min-width: 0; padding: 8px 13px;
  text-transform: uppercase; font-family: "IBM Plex Mono", monospace;
  font-size: 12.5px; letter-spacing: .06em;
}
.corow input::placeholder { text-transform: none; letter-spacing: 0; }

/* ===========================================================================
   The company page: one scroll, with an index that follows you down it
   ===========================================================================
   The rail is a position, not a table of contents — the entry you are actually
   reading is lit, updated by an IntersectionObserver rather than a scroll
   handler so it costs nothing while you read.

   `align-self: start` on the rail is what makes `position: sticky` work inside
   a grid: a grid item stretches to the row's full height by default, and a
   sticky element with nowhere to move inside its own box never moves. This has
   cost an hour on other projects; it is one line and it is not obvious. */
.coflow {
  display: grid; gap: 0 28px;
  grid-template-columns: 176px minmax(0, 1fr);
  align-items: start;
}
.conav {
  position: sticky; top: calc(var(--il-chrome-h) + 10px); align-self: start;
  display: flex; flex-direction: column; gap: 1px;
  max-height: calc(100vh - var(--il-chrome-h) - 24px); overflow-y: auto;
  padding-right: 4px;
}
.conav-h {
  font-family: "IBM Plex Mono", monospace; font-size: 9.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-muted);
  margin: 0 0 8px; padding-left: 11px;
}
.conav-i {
  display: block; padding: 5px 11px; border-radius: var(--radius-xs);
  font-size: 12.5px; line-height: 1.35; color: var(--text-secondary);
  text-decoration: none; border-left: 2px solid transparent;
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.conav-i:hover { color: var(--text-primary); background: var(--surface-1); }
.conav-i.on {
  color: var(--brand); border-left-color: var(--brand); font-weight: 600;
  background: color-mix(in oklab, var(--brand) 8%, transparent);
}
/* Below this the rail costs more width than it returns: 176px out of 383px is
   nearly half the screen to say where you are. It goes horizontal and scrolls,
   which is the one place a scrolling strip is right — it is a position
   indicator, not a set of controls you need to see all of at once. */
@media (max-width: 860px) {
  .coflow { grid-template-columns: minmax(0, 1fr); }
  .conav {
    position: sticky; top: var(--il-chrome-h); z-index: 20;
    flex-direction: row; gap: 4px; max-height: none;
    overflow-x: auto; overflow-y: hidden; margin-bottom: 14px;
    padding: 6px 0; backdrop-filter: var(--il-panel-blur);
    background: color-mix(in oklab, var(--surface-1) 88%, transparent);
    border-bottom: 1px solid var(--border);
  }
  .conav-h { display: none; }
  .conav-i {
    flex: 0 0 auto; white-space: nowrap; border-left: 0;
    border-bottom: 2px solid transparent; border-radius: 0;
  }
  .conav-i.on { border-left: 0; border-bottom-color: var(--brand); background: none; }
}

/* ===========================================================================
   Two questions, two benchmarks
   ===========================================================================
   The question is set in the interface face at reading size, not as a mono
   label: a label is scanned and a question is read, and the whole point of
   this block is that the reader takes in which question the number answers.
   Side by side above ~760px, stacked below — two columns of 40ch prose at
   phone width would be four words each. */
.twoq { display: grid; gap: 26px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.twoq-row { min-width: 0; }
.twoq-q {
  margin: 0 0 .5rem; font-size: 14.5px; font-weight: 700; line-height: 1.4;
  color: var(--text-primary); max-width: 34ch;
}
.twoq-a { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.twoq-n {
  font-family: "IBM Plex Mono", monospace; font-size: 34px; font-weight: 600;
  line-height: 1; font-variant-numeric: tabular-nums; color: var(--text-primary);
}
.twoq-note { margin: .5rem 0 0; max-width: 46ch; line-height: 1.55; }

/* ===========================================================================
   The sizing hero
   ===========================================================================
   The one number this view exists to produce, with the company it is about
   beside it rather than at the far end of the card. `space-between` used to
   push them apart, which read as two unrelated blocks the moment the shell
   stopped being 1,240px wide. */
.sizehero .sh-head {
  display: flex; align-items: flex-end; justify-content: flex-start;
  gap: clamp(24px, 4vw, 64px); flex-wrap: wrap;
}
.sizehero .sh-co { font-size: 15px; margin-top: .15rem; }
.sizehero .sh-co b { font-family: "IBM Plex Mono", monospace; }
.sizehero .sh-out { display: flex; flex-direction: column; }
.sizehero .sh-num {
  font-family: "IBM Plex Mono", monospace;
  font-size: clamp(2.6rem, 5vw, 3.4rem); font-weight: 700; line-height: .95;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  background: linear-gradient(120deg, var(--amber-soft), var(--gold));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.sizehero .sh-cash { margin-top: .3rem; }
/* A refusal to size is not a gold headline. Same size and weight so the block
   keeps its shape, in the muted ink so it does not read as a recommendation. */
.sizehero .sh-none {
  background: none; -webkit-background-clip: initial; background-clip: initial;
  color: var(--text-muted); font-weight: 600; letter-spacing: -.01em;
}
/* The inputs, as a sentence under the answer they explain. */
.sizehero .sh-from {
  margin: 1rem 0 0; max-width: 74ch; line-height: 1.65;
  color: var(--text-secondary);
}

/* ===========================================================================
   Who owns it
   ===========================================================================
   The ring and its reading sit side by side and stack below ~640px, where a
   168px ring beside a 58ch paragraph leaves the paragraph two words wide. */
.ownwrap { display: flex; gap: 26px; align-items: flex-start; flex-wrap: wrap; }
.ownring { flex: 0 0 168px; max-width: 168px; }
.ownside { flex: 1 1 320px; min-width: 0; }

/* Advanced sections. One attribute on the root decides, so nothing has to be
   re-rendered when it flips — and the default is off, which means a first
   visit shows the answer rather than the spreadsheet behind it. */
:root[data-advanced="0"] [data-adv="1"] { display: none; }

.advopt[aria-pressed="true"] {
  border-color: var(--brand); color: var(--brand);
  background: color-mix(in oklab, var(--brand) 14%, transparent);
}

/* ===========================================================================
   The five-year record, as small multiples
   ===========================================================================
   Seven charts, all visible. `auto-fit` with a 340px floor gives three across
   on a wide screen, two on a laptop and one on a phone without a breakpoint
   each — and a chart narrower than 340px stops being readable, so the floor is
   where it stops rather than a round number. */
.histgrid {
  display: grid; gap: 22px 26px;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
}
.histcell { margin: 0; min-width: 0; }
.histcell figcaption {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 6px;
}
.histcell-t { font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
/* The stat each chart belongs to. A reader who has learned that VIT is the
   balance sheet can find the two charts behind it without reading titles. */
.histcell-s {
  font-family: "IBM Plex Mono", monospace; font-size: 9.5px; font-weight: 600;
  letter-spacing: .12em; color: var(--text-muted); flex: none;
}
.histcell .histlegend { margin-bottom: 2px; }
.histcell-f { margin: .5rem 0 0; line-height: 1.5; }

/* Every cell opens.
   ---------------------------------------------------------------------------
   The whole figure is the hit target, not a small control in its corner: the
   chart is what the reader is already looking at, so the chart is what they
   should be able to click. The "Open" label is the only visible affordance and
   it stays at low contrast until the cell is hovered or focused -- eight of
   these shouting at once would turn a page of charts into a page of buttons.

   Negative margin plus matching padding so the hover surface is larger than
   the chart without the grid gaps changing. */
.histcell {
  position: relative; cursor: pointer;
  margin: -8px -10px; padding: 8px 10px;
  border-radius: var(--radius-sm);
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.histcell-open {
  position: absolute; top: 6px; right: 10px;
  font-family: "IBM Plex Mono", monospace; font-size: 9px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-muted); opacity: 0;
  transition: opacity var(--fast) var(--ease);
}
.histcell:hover, .histcell:focus-visible {
  background: var(--surface-sunken); outline: none;
  box-shadow: inset 0 0 0 1px var(--border);
}
.histcell:hover .histcell-open,
.histcell:focus-visible .histcell-open { opacity: 1; color: var(--gold); }
/* The stat tag and the "Open" label would collide in the same corner. */
.histcell:hover .histcell-s,
.histcell:focus-visible .histcell-s { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .histcell, .histcell-open, .histcell-s { transition: none; }
}

/* The window a shortlist opens into.
   ---------------------------------------------------------------------------
   Master-detail: the list stays put on the left while the company fills the
   right, because the reason to enlarge a shortlist is to compare across it and
   a layout that replaces the list with one company defeats that.

   The list column is fixed-width and the detail takes the rest. Both scroll
   independently, so a long strict-failures list on one company does not push
   the list out of reach. */
.modal.listwin { width: min(1040px, 100%); }
.lw-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; margin-bottom: 6px;
}
.lw-blurb { margin: 0 0 .9rem; line-height: 1.55; max-width: 74ch; }
/* Flex-wrap rather than a two-column grid with a breakpoint.
   A grid of `250px 1fr` holds its two columns however narrow the container
   gets -- at phone width the detail was squeezed to 46px and overflowed by
   172px, and the only thing preventing that was a viewport media query, which
   is a different measurement from the one that actually matters here. Wrapping
   on the rail's own basis means the layout is correct at any width the modal
   can be, breakpoint or no breakpoint. */
.lw-body { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start; }
.lw-body > .lw-list { flex: 1 1 210px; max-width: 250px; }
.lw-body > .lw-detail { flex: 5 1 320px; }

.lw-list {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
  max-height: 60vh; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 4px;
}
.lw-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 8px; border: 0; border-radius: var(--radius-xs);
  background: transparent; color: inherit; font: inherit; text-align: left;
  cursor: pointer; transition: background var(--fast) var(--ease);
}
.lw-row:hover { background: var(--surface-sunken); }
.lw-row[aria-selected="true"] {
  background: var(--surface-sunken); box-shadow: inset 2px 0 0 var(--gold);
}
.lw-flake { flex: none; width: 34px; height: 34px; display: block; }
.lw-who { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.lw-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-nums { flex: none; display: flex; flex-direction: column; align-items: flex-end; line-height: 1.25; }

.lw-detail { min-width: 0; max-height: 60vh; overflow-y: auto; padding-right: 4px; }
/* Wraps rather than squeezing: a 150px shape beside a company name leaves the
   name about 100px at phone width, and "Microsoft Corporation" then sets on
   three lines next to a lot of nothing. Below its own basis the shape drops
   under the name instead. */
.lw-dhead { display: flex; flex-wrap: wrap; align-items: flex-start;
  justify-content: space-between; gap: 14px; }
.lw-dhead > div:first-child { flex: 1 1 190px; }
.lw-dflake { flex: 0 0 auto; }
.lw-dhead h4 { margin: 0; font-size: 1.05rem; }
.lw-verdict { margin: .5rem 0 .9rem; }
.lw-gates { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.lw-gate {
  padding: 11px 13px; border: 1px solid var(--border);
  border-radius: var(--radius-xs); background: var(--surface-sunken);
}
.lw-big { display: block; font-size: 1.5rem; line-height: 1.2; margin: .15rem 0; }
.lw-figs {
  display: grid; gap: 10px 14px; margin-top: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
.lw-fig b { display: block; font-size: 1rem; line-height: 1.3; }
.lw-flags { margin-top: 1rem; padding-top: .8rem; border-top: 1px solid var(--border); }
.lw-flags ul { margin: .3rem 0 0; }
.lw-actions { display: flex; gap: 10px; margin-top: 1.2rem; flex-wrap: wrap; }

@media (max-width: 760px) {
  /* Once it has wrapped to one column the rail is a full-width strip above the
     detail, so it should be short and the detail should stop scrolling inside
     its own box and just be part of the page. */
  .lw-body > .lw-list { max-width: none; max-height: 210px; }
  .lw-detail { max-height: none; }
  .lw-gates { grid-template-columns: 1fr; }
  .lw-head { flex-direction: column; }
}

/* The window a chart opens into.
   ---------------------------------------------------------------------------
   Wider than the editing modals because the point of it is a chart and a table
   of every period behind it, and a 560px table of twenty-four quarters is a
   worse version of the five-bar chart the reader just clicked away from. */
.modal.chartwin { width: min(920px, 100%); }
.cw-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; margin-bottom: 10px;
}
/* Wraps: there are two switches here now (period, and what growth is measured
   against) plus Close, and three controls in a row do not fit a phone. */
.cw-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  flex: 0 1 auto; justify-content: flex-end; }
.cw-legend { margin-bottom: 4px; }
/* Matches the height `chartWindow` picks for the SVG, so the box does not
   reserve 300px for a 210px chart on a phone. */
.cw-chart { min-height: 300px; }
@media (max-width: 620px) { .cw-chart { min-height: 210px; } }
.cw-find { margin: .7rem 0 .2rem; line-height: 1.55; }
.cw-tablewrap {
  margin-top: 1rem; max-height: 340px; overflow: auto;
  border: 1px solid var(--border); border-radius: var(--radius-xs);
}
.cw-table { width: 100%; border-collapse: collapse; font-size: 12px; }
/* Sticky header, because the table scrolls inside the modal and a column of
   numbers whose heading has scrolled away is a column of numbers. */
.cw-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2); text-align: right;
  padding: 7px 10px; font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-muted); border-bottom: 1px solid var(--border);
}
.cw-table thead th:first-child, .cw-table tbody th { text-align: left; }
.cw-table tbody th {
  padding: 6px 10px; font-weight: 600; color: var(--text-secondary);
  white-space: nowrap;
}
.cw-table td { padding: 6px 10px; text-align: right; color: var(--text-primary); }
/* The growth column sits tight against the figure it belongs to and is set a
   size down: it is a reading of the number to its left, not a number of its
   own, and at the same weight two columns per series compete for the eye. */
.cw-table td.cw-g { padding-left: 2px; font-size: 11px; }
.cw-table th.cw-gh { font-weight: 500; opacity: .75; padding-left: 2px; }
.cw-table td.cw-g[role="button"] { cursor: help; }
/* **Named explicitly, because `.cw-table td` out-specifies `.pos`.**
   `.cw-table td { color: var(--text-primary) }` is (0,1,1) and the global
   `.pos` is (0,1,0), so every growth figure rendered in body ink and the
   up/down read -- the entire reason for a signed column -- was lost. This is
   the cascade trap `scripts/check_css_specificity.py` was written for; it did
   not catch this one because the collision is inside a single file rather than
   between a base sheet and an override. */
.cw-table td.cw-g.pos { color: var(--good); }
.cw-table td.cw-g.neg { color: var(--critical); }
.cw-table td.cw-g.muted { color: var(--text-muted); }
.cw-table tbody tr + tr th, .cw-table tbody tr + tr td {
  border-top: 1px solid var(--border);
}
.cw-table tbody tr:hover th, .cw-table tbody tr:hover td { background: var(--surface-sunken); }
@media (max-width: 620px) {
  .cw-head { flex-direction: column; }
  .cw-controls { width: 100%; justify-content: space-between; }
}

/* ===========================================================================
   Who owns it, by name
   ===========================================================================
   Four blocks in a responsive grid, each rendering only if its data arrived.
   The holder rows are a four-column grid so every name, bar, percentage and
   direction arrow lines up down the column — a list of holders whose bars all
   start at different x is a list, not a comparison. */
.ownreg {
  display: grid; gap: 18px 26px; margin-top: 20px;
  padding-top: 18px; border-top: 1px solid var(--border);
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
.ownblock-h {
  font-family: "IBM Plex Mono", monospace; font-size: 10px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-muted);
  margin: 0 0 9px;
}
.ownblock-note { margin: .6rem 0 0; max-width: 62ch; }

.ownrow {
  display: grid; align-items: center; gap: 10px; padding: 3px 0;
  grid-template-columns: minmax(0, 1fr) 68px 52px 48px;
}
.ownrow-n {
  font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-secondary);
}
.ownrow-bar {
  height: 7px; border-radius: var(--radius-pill); overflow: hidden;
  background: color-mix(in oklab, var(--border-strong) 55%, transparent);
}
.ownrow-bar i {
  display: block; height: 100%; border-radius: inherit;
  background: color-mix(in oklab, var(--series-3) 75%, transparent);
}
.ownrow-p { font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; }
.ownrow-c { font-size: 10.5px; text-align: right; font-variant-numeric: tabular-nums; }
.ownrow-c.pos { color: var(--good); }
.ownrow-c.neg { color: var(--critical); }

/* The insider rows reuse the institution grammar with a wider last column:
   a share count needs more room than an up-or-down arrow. */
.ownrow.ownrow-p3 { grid-template-columns: minmax(0, 1fr) 68px 52px 64px; }
.ownrow.ownrow-p3 .ownrow-bar i {
  background: color-mix(in oklab, var(--brand) 70%, transparent);
}

.ownppl-r, .owntr-r {
  display: grid; align-items: baseline; gap: 10px; padding: 3px 0;
  border-bottom: 1px solid color-mix(in oklab, var(--border) 55%, transparent);
}
.ownppl-r { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) auto; }
/* 86px, not 74: an ISO date is ten mono characters and at 74 it wrapped to two
   lines, which doubled the height of every row in the table to save twelve
   pixels of width. `white-space: nowrap` on the cell as well, so a narrower
   container shrinks the name column rather than breaking the date again. */
.owntr-r  { grid-template-columns: 86px minmax(0, 1fr) auto auto; }
.owntr-d { white-space: nowrap; }
.ownppl-r:last-child, .owntr-r:last-child { border-bottom: 0; }
.ownppl-n, .owntr-n {
  font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The kind of transaction carries the colour, because it is the only part of
   the row that is a judgement rather than a fact. A grant is neither. */
.owntr-k {
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--text-muted) 14%, transparent);
  color: var(--text-muted);
}
.owntr-r[data-kind="buy"] .owntr-k {
  background: color-mix(in oklab, var(--good) 16%, transparent); color: var(--good);
}
.owntr-r[data-kind="sell"] .owntr-k {
  background: color-mix(in oklab, var(--critical) 16%, transparent); color: var(--critical);
}

.shortline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.short-n {
  font-family: "IBM Plex Mono", monospace; font-size: 26px; font-weight: 600;
  line-height: 1; font-variant-numeric: tabular-nums;
}
.short-n[data-il-dir="bad"] { color: var(--critical); }

/* The insider band. The window is a filled region and the company is a pin on
   it — the same grammar as the median tick on the industry bars, so a reader
   who has learned one has learned both. */
.ownband { margin-top: 1rem; }
.ownband-track {
  position: relative; height: 10px; margin-top: 8px;
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--border-strong) 70%, transparent);
}
.ownband-window {
  position: absolute; top: 0; height: 100%; border-radius: inherit;
  background: color-mix(in oklab, var(--good) 40%, transparent);
}
.ownband-pin {
  position: absolute; top: -4px; width: 3px; height: 18px;
  margin-left: -1.5px; border-radius: 2px;
  background: var(--text-primary);
}

/* ===========================================================================
   The financial history
   ===========================================================================
   Six charts behind one segmented control. The tab strip wraps rather than
   scrolls: six labels at ~110px each need two rows below about 700px, and a
   horizontal scroller hides the tabs a reader does not know to look for. */
.histseg { flex-wrap: wrap; margin-bottom: .7rem; }
.histlegend { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: .35rem; }
.histkey { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); }
.histkey > i {
  width: 10px; height: 10px; border-radius: 3px; flex: none; display: block;
}

/* ===========================================================================
   The season chip
   ===========================================================================
   "Season 1" is a fact nobody can act on. The term served and whether the
   season would be graded if it ended are the mechanic, so they ride in the
   chip. The base .seasonchip rule lives in style.css; these are new selectors
   under it rather than overrides of it, which is why they need no extra
   specificity — but they are scoped through .seasonchip anyway so that a
   future .sc-bar elsewhere cannot inherit this geometry by accident. */
.seasonchip { display: inline-flex; align-items: center; gap: .42rem; }
.seasonchip .sc-term {
  font-weight: 500; letter-spacing: .04em; text-transform: none;
  color: var(--text-muted);
}
.seasonchip .sc-bar {
  width: 34px; height: 3px; border-radius: 99px; overflow: hidden;
  background: var(--border-strong); flex: none;
}
.seasonchip .sc-bar > i { display: block; height: 100%; background: var(--brand); }
/* A format that never grades says so in ink rather than showing an empty bar
   that looks like a season with no progress. */
.seasonchip[data-unranked] .sc-term { font-style: italic; }

/* ===========================================================================
   The profile board
   ===========================================================================
   One component in two homes: the header button's panel and the top of the
   Portfolio view. Identity on the left, the six stats as compact tracks on the
   right, wrapping to a stack when the panel is narrow — the popover is 320px
   and the portfolio panel is 950px, and the same markup has to read in both. */

.profboard {
  display: flex; flex-wrap: wrap; gap: 18px 26px; align-items: flex-start;
}
.pb-id { display: flex; gap: 12px; align-items: flex-start; flex: 1 1 220px; min-width: 0; }
.pb-mark {
  flex: none; display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: var(--radius-pill, 999px);
  border: 1px solid var(--border-strong); background: var(--surface-sunken);
  font-family: "IBM Plex Mono", monospace; font-size: 12px; font-weight: 600;
  letter-spacing: .04em; color: var(--text-secondary);
}
.pb-stats {
  flex: 2 1 320px; min-width: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 6px 16px;
}
.pb-stat { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pb-s-short {
  flex: none; width: 2.4em;
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted);
}
.pb-s-track {
  flex: 1 1 auto; min-width: 0; height: 5px; border-radius: 3px;
  background: var(--surface-sunken); overflow: hidden;
}
.pb-s-track > i { display: block; height: 100%; border-radius: 3px; }
.pb-s-val {
  flex: none; min-width: 2ch; text-align: right;
  font-family: "IBM Plex Mono", monospace; font-size: 12px;
  font-variant-numeric: tabular-nums; color: var(--text-primary);
}

/* The header button. Shows your initials, and carries the rank letter as a
   corner pip when there is one — the single piece of character state worth
   putting in the chrome, because it changes when the book changes. */
/* The profile and its caret, as one control with two jobs — who you are, and
   how the place looks. The caret is deliberately small and quiet: it is the
   secondary half, and a second full-size button beside the profile would read
   as two accounts. */
.profilewrap { display: flex; align-items: center; gap: 2px; }
.caretbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 26px; padding: 0; font-size: 10px; line-height: 1;
  border: 0; background: none; color: var(--text-muted); cursor: pointer;
  border-radius: var(--radius-xs);
  transition: color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.caretbtn:hover { color: var(--text-primary); }
.caretbtn[aria-expanded="true"] { color: var(--brand); transform: rotate(180deg); }
.caretbtn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The fundamentals grid: groups in columns, rows inside them.
   `auto-fit` at 260px gives four groups across on a wide card and one on a
   phone, and `break-inside: avoid` keeps a group from being split down the
   middle when the browser balances the columns. */
.fundgrid {
  display: grid; gap: 18px 26px;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  align-items: start;
}
.fundgroup { min-width: 0; break-inside: avoid; }
.fundgroup-h {
  font-family: "IBM Plex Mono", monospace; font-size: 9.5px; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase; color: var(--text-muted);
  margin: 0 0 7px; padding-bottom: 5px; border-bottom: 1px solid var(--border);
}
.fundrow {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 3px 0; font-size: 12.5px;
}
.fundrow span { color: var(--text-secondary); min-width: 0; }
.fundrow b { font-size: 12.5px; font-variant-numeric: tabular-nums; flex: none; }

/* ===========================================================================
   The macro board
   =========================================================================== */

/* A tile per series: name, level, the shape it made, and the two moves that
   matter. `auto-fit` at 150px gives four across in a half-width panel and two
   on a phone. */
.mtiles {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
.mtile {
  padding: 9px 11px; border-radius: var(--radius-xs);
  border: 1px solid var(--border); background: var(--surface-1); cursor: help;
}
.mtile-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.mtile-l { font-size: 11.5px; color: var(--text-secondary); }
.mtile-v { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.mtile-s { height: 28px; margin: 5px 0 3px; }
.mtile-f { display: flex; justify-content: space-between; gap: 8px;
  font-variant-numeric: tabular-nums; }

/* Published statistics are rows, not tiles: they are read down a column and
   compared against their own past rather than against each other. */
.mrows { display: grid; gap: 1px; }
.mrow {
  display: grid; align-items: baseline; gap: 10px; padding: 5px 0;
  grid-template-columns: minmax(0, 1fr) auto 76px 76px;
  border-bottom: 1px solid color-mix(in oklab, var(--border) 55%, transparent);
  cursor: help;
}
.mrow:last-child { border-bottom: 0; }
.mrow.off { grid-template-columns: minmax(0, 1fr) auto; opacity: .55; cursor: default; }
.mrow-l { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mrow-v { font-size: 12.5px; font-variant-numeric: tabular-nums; }
.mrow-c, .mrow-d { text-align: right; font-variant-numeric: tabular-nums; }

/* A reading, with the mechanism it runs through named underneath. The accent
   rule on the left is the severity; the mechanism line is what keeps this from
   being a horoscope. */
.mreadings {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
.mreading {
  padding: 2px 0 2px 13px; border-left: 2px solid var(--border-strong);
}
.mreading[data-sev="warn"] { border-left-color: var(--amber); }
.mreading b { font-size: 13px; }
.mreading p { margin: .3rem 0 0; max-width: 62ch; line-height: 1.55; }
.mreading-m {
  font-family: "IBM Plex Mono", monospace; font-size: 10px; letter-spacing: .04em;
}

/* The price chart's range buttons. A segmented track rather than seven loose
   buttons, because they are one setting with seven values. */
.pricerange {
  display: inline-flex; align-items: center; gap: 1px; padding: 2px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--surface-1) 60%, transparent);
  /* This was five buttons on a price card and never needed to wrap. The ETF
     flows board reuses it for seven fund groups, which is wider than a phone,
     and an inline-flex that cannot wrap pushes the page sideways rather than
     breaking. A wrapped pill is slightly odd-looking at 400px; a horizontally
     scrolling page is worse, and the body rule forbids it. */
  flex-wrap: wrap; max-width: 100%;
}
.pricerange button {
  padding: 3px 9px; border: 0; background: none; cursor: pointer;
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px; font-weight: 600;
  letter-spacing: .04em; color: var(--text-muted);
  border-radius: var(--radius-pill);
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.pricerange button:hover { color: var(--text-primary); }
.pricerange button[aria-pressed="true"] {
  color: var(--brand); background: color-mix(in oklab, var(--brand) 15%, transparent);
}
.pricerange button:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The profile's three tabs. Underlined rather than boxed: the panel is already
   a surface, and a second surface inside it reads as two panels. */
.proftabs {
  display: flex; gap: 2px; margin: -2px 0 12px;
  border-bottom: 1px solid var(--border);
}
.proftabs button {
  flex: 1 1 0; padding: 7px 4px; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.proftabs button:hover { color: var(--text-primary); }
.proftabs button[aria-selected="true"] { color: var(--brand); border-bottom-color: var(--brand); }
.profbody { min-height: 120px; }

/* One fact per line: label, figure, and what the figure means. The figure
   column is fixed so the numbers line up down the panel rather than floating
   wherever the label left them. */
.proflist { display: grid; gap: 2px; }
.profrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "k v" "n n";
  align-items: baseline; gap: 0 10px; padding: 5px 0;
  border-bottom: 1px solid color-mix(in oklab, var(--border) 55%, transparent);
}
.profrow:last-child { border-bottom: 0; }
.profrow > span:first-child { grid-area: k; font-size: 12.5px; }
.profrow > b { grid-area: v; font-size: 13px; font-variant-numeric: tabular-nums; }
.profrow > span:last-child { grid-area: n; }

/* Light or dark, beside the mood it modifies. */
.lumrow { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.lumopt { display: inline-flex; align-items: center; gap: 6px; }
.lumglyph { font-size: 13px; line-height: 1; }

.profilebtn {
  position: relative; width: 34px; height: 34px; padding: 0;
  display: grid; place-items: center;
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 600;
  letter-spacing: .02em;
}
.profilebtn[data-rank]:not([data-rank=""])::after {
  content: attr(data-rank);
  position: absolute; right: -3px; bottom: -3px;
  min-width: 14px; height: 14px; padding: 0 2px;
  display: grid; place-items: center;
  border-radius: 999px; border: 1px solid var(--surface-1);
  background: var(--brand); color: var(--surface-1);
  font-size: 9px; line-height: 1; font-weight: 700;
}
.profilepop { width: min(340px, calc(100vw - 32px)); }
.profilepop .row { flex-wrap: wrap; }
.profilepop kbd { margin-left: .4em; opacity: .7; }

/* ===========================================================================
   Small type on a panel
   ===========================================================================
   theme.js fits five tokens to 4.6:1 against the panel tint rather than
   against the page (see fitOn / the canvas's FIT_ON). These are the places
   that actually need it: 10-12px labels, notes and metas sitting on a panel,
   where several family accents land near 3:1 unfitted. Larger type and
   anything on the page background keeps the unfitted token, because the point
   is legibility at small sizes, not repainting the palette. */
[data-il-panel] .il-panel-meta,
[data-il-panel] .il-stat-label,
[data-il-panel] .il-stat-note,
[data-il-panel] .small.muted,
[data-il-panel] .muted,
[data-il-panel] th,
[data-il-panel] .pb-s-short { color: var(--text-muted-on-panel, var(--text-muted)); }

/* The whole terminal reads on the fitted tokens, as the canvas does.
   ---------------------------------------------------------------------------
   The canvas applies its five fitted inks to the entire app, not only to text
   on a panel: `[data-il-app] { --ink3: var(--ink3App); --accent:
   var(--accentApp); --pos: ...; --neg: ...; --warn: ... }`. The port took them
   for small text inside panels and nowhere else, so the rest of the terminal
   -- the view head's line, a link, a warning chip, a chart's axis labels --
   used the raw inks, and in all six light moods the muted ink was under 4.5:1
   on the page: 3.43 in Vanguard's daylight, 2.99 in Aether's. Fitted, the
   lowest is 4.69 on the page and 4.62 on a panel.

   Only what fails moves, and only as far as it must (theme.js fitOn): the
   muted ink in the light moods, Vanguard daylight's orange accent, the
   warning gold in every light mood, two reds after dark. The landing keeps
   the raw inks, as in the canvas, and so do the scene layers, which are art
   rather than text. A custom property set here outranks the inline one on
   <html> for everything beneath it. */
:root[data-stage="app"] body > :not(.atmosphere, .atmosphere-motifs, #landing) {
  --text-muted: var(--text-muted-on-panel);
  --brand: var(--brand-on-panel);
  --good: var(--good-on-panel);
  --critical: var(--critical-on-panel);
  --warning: var(--warning-on-panel);
}
/* The landing, one step past the canvas and only for the muted ink: its fine
   print carries the privacy sentence, the demo's "about a minute", every
   eyebrow and the strapline, and all of it measured 2.99-3.72 in the light
   moods. Body copy there is already ink2 (6.8+) and the accent is used large,
   so nothing else changes. */
#landing { --text-muted: var(--text-muted-on-panel); }

[data-il-panel] .il-row-value[data-il-dir="good"],
[data-il-panel] td[data-il-dir="good"],
[data-il-panel] .il-stat-value[data-il-dir="good"] { color: var(--good-on-panel, var(--good)); }
[data-il-panel] .il-row-value[data-il-dir="bad"],
[data-il-panel] td[data-il-dir="bad"],
[data-il-panel] .il-stat-value[data-il-dir="bad"] { color: var(--critical-on-panel, var(--critical)); }
[data-il-panel] .il-row-value[data-il-dir="warn"],
[data-il-panel] td[data-il-dir="warn"],
[data-il-panel] .il-stat-value[data-il-dir="warn"] { color: var(--warning-on-panel, var(--warning)); }

/* The same for the semantic classes the older renderers still use inside
   panels -- .pos/.neg are on nearly every figure that can go either way. */
[data-il-panel] .pos { color: var(--good-on-panel, var(--good)); }
[data-il-panel] .neg { color: var(--critical-on-panel, var(--critical)); }

/* ===========================================================================
   The info affordance
   ===========================================================================
   A panel's "how to read this" lives behind this rather than under the
   content. Small, quiet, and in the head where the title already is -- it must
   not read as a warning or a required step. */
.il-info {
  flex: none; width: 18px; height: 18px; padding: 0; margin-left: 2px;
  display: grid; place-items: center; cursor: help;
  border-radius: 999px; border: 1px solid var(--border-strong);
  background: transparent; color: var(--text-muted-on-panel, var(--text-muted));
  font-family: "IBM Plex Mono", monospace; font-size: 10px; font-weight: 600;
  line-height: 1; transition: color var(--fast) var(--ease),
    border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.il-info:hover, .il-info[aria-expanded="true"] {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in oklab, var(--brand) 12%, transparent);
}
.il-info:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The sub-stat note button.
   A criterion label is 11px and there can be eight of them stacked, so the
   panel-head's 18px ⓘ is too heavy here — this is the same control at the size
   the row can carry. Scoped through .substat so it out-ranks both `.il-info`
   above and `.ss-info` in style.css whichever way the two files load; the pair
   are otherwise the same specificity and the winner would be an accident of
   the <link> order. */
.substat .ss-info {
  /* inline-grid, not grid: `.il-info` above sets `display: grid`, which is
     right for a flex panel head and wrong inside a line of text — as a block
     it dropped onto its own row under every criterion label. style.css's
     `.ss-info` had it as inline-grid and lost on load order, which is the
     cascade accident this file keeps having to state its way out of. */
  display: inline-grid;
  width: 13px; height: 13px; margin-left: .3rem; font-size: 8.5px;
  border-width: 1px; vertical-align: middle;
}
/* The target mark and the weight explain themselves on hover. Neither is a
   button: the mark is 2px of a bar and the weight is a label, so both would be
   absurd tab stops — hover and the tooltip are the whole affordance. */
.substat .ss-bar > u[data-il-info],
.substat .ss-w[data-il-info] { cursor: help; }

#ilInfoPop {
  position: fixed; z-index: 80; max-width: min(340px, calc(100vw - 24px));
  padding: 11px 13px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--panel-solid, var(--surface-1));
  box-shadow: var(--shadow-lg);
  font-size: 13px; line-height: 1.55; color: var(--text-secondary);
  opacity: 0; transform: translateY(-4px); pointer-events: none;
  transition: opacity var(--fast) var(--ease), transform var(--fast) var(--ease);
}
#ilInfoPop.on { opacity: 1; transform: none; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) #ilInfoPop { transition: none; }
}

/* Anything that carries an explanation says so with the cursor. .il-info has
   its own rule above; this covers the chips and any future carrier. */
[data-il-info]:not(.il-info) { cursor: help; }
[data-il-info]:not(.il-info):focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The screener's sparkline cell. Fixed height so a row does not change height
   when its series arrives — the table renders before the price history does,
   and a row that jumps on load is worse than a cell that fills in. */
.il-spark { display: block; height: 22px; line-height: 22px; }
.il-spark svg { display: block; }
/* The shape column. Centred in its cell and given the row's full height, so a
   34px mark does not stretch the row that a 22px sparkline already sets. */
.il-flake { display: flex; align-items: center; justify-content: center; height: 34px; }
.il-flake svg { display: block; }
/* A header that cannot sort should not look like one that can. */
.il-sort-off { cursor: default; opacity: .75; }

/* The six stat floors.
   =========================================================================
   Above the general filter grid and visually apart from it, because these are
   the ones people reach for: "everything with VIT over 70" is the question the
   screener exists to answer, and it should not be the seventh control in a
   grid of nine. Each is a chip that lights when it is doing something, so a
   filter left set three sessions ago cannot go on quietly excluding companies.
   Wraps rather than scrolls — six chips fit one row on a laptop and two on a
   phone, and a horizontally scrolling filter bar hides its own contents. */
.statfilters {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding-bottom: 12px; margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.statf {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 8px 5px 10px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong); background: var(--surface-1);
  cursor: help; transition: border-color var(--fast) var(--ease),
                            background var(--fast) var(--ease);
}
.statf.on {
  border-color: var(--brand);
  background: color-mix(in oklab, var(--brand) 12%, transparent);
}
.statf-ic { font-size: .85rem; color: var(--text-muted); line-height: 1; }
.statf.on .statf-ic { color: var(--brand); }
.statf-n {
  font-family: "IBM Plex Mono", monospace; font-size: .68rem; font-weight: 700;
  letter-spacing: .08em; color: var(--text-secondary);
}
.statf.on .statf-n { color: var(--brand); }
.statf input {
  width: 4.2rem; padding: 3px 6px; font: inherit; font-size: .78rem;
  font-variant-numeric: tabular-nums;
  border-radius: var(--radius-xs); border: 1px solid var(--border);
  background: var(--surface-sunken); color: var(--text-primary);
}
.statf input:focus { outline: none; border-color: var(--brand); }

/* The screener's filter grid. auto-fit rather than a column count so the same
   eight controls read at 1680px and at 400px without a breakpoint each. */
.filtergrid {
  display: grid; gap: 10px 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
.filtergrid .field { margin-bottom: 0; }
#screenFilters .btn.tiny[aria-pressed="true"] {
  border-color: var(--brand); color: var(--brand);
  background: color-mix(in oklab, var(--brand) 14%, transparent);
}

/* ===========================================================================
   Disclosures open, rather than appearing
   ===========================================================================
   Chris: *"thanks a lot for now expanding on the macro tabs, keep taht up for
   other tabs as well, but please give it an animation, would make it look much
   much cooler!"*

   Every expandable section in this app is a native `<details>`, which is the
   right element — keyboard, find-in-page and screen readers all get it free —
   and the one thing it does badly is appear instantly, as a hard jump that
   moves everything below it with no indication of what happened.

   **Two layers, because the good version is not universally available yet.**

   The baseline is a fade and a small slide on the contents. It runs everywhere,
   costs nothing, and covers opening, which is the direction a reader is
   actually watching.

   On top of that, `::details-content` with `interpolate-size: allow-keywords`
   grows and shrinks the box itself, so closing is animated too and the page
   below slides rather than snaps. `interpolate-size` is set on the disclosures
   themselves rather than on `:root` deliberately: it changes how every
   `height: auto` transition in its subtree behaves, and turning that on
   document-wide would animate things nobody asked to animate.

   Motion follows the same rule as the info popover and `il-enter.js`: the
   operating system's reduced-motion preference is the default for someone who
   has not said otherwise, and `data-motion-choice="user"` means they have. */
details.doc[open] > :not(summary),
details.chkgroup[open] > :not(summary) {
  animation: il-disclose 220ms var(--ease, ease-out) both;
}
@keyframes il-disclose {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

@supports (interpolate-size: allow-keywords) {
  details.doc, details.chkgroup { interpolate-size: allow-keywords; }
  details.doc::details-content,
  details.chkgroup::details-content {
    block-size: 0;
    overflow: clip;
    transition: block-size 240ms var(--ease, ease-out),
                content-visibility 240ms allow-discrete;
  }
  details.doc[open]::details-content,
  details.chkgroup[open]::details-content { block-size: auto; }
}

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) details.doc[open] > :not(summary),
  :root:not([data-motion-choice="user"]) details.chkgroup[open] > :not(summary) {
    animation: none;
  }
  :root:not([data-motion-choice="user"]) details.doc::details-content,
  :root:not([data-motion-choice="user"]) details.chkgroup::details-content {
    transition: none;
  }
}

/* The marker turns with the box rather than snapping ahead of it. */
details.doc summary::before { transition: transform 200ms var(--ease, ease-out); }
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) details.doc summary::before { transition: none; }
}

/* ---------- attribution bars ----------

   A diverging bar per cause, on a shared centre line, because the question
   being answered is "which of these hurt most" and that is a comparison of
   magnitudes either side of zero. The track's midpoint is fixed at 50% and a
   bar grows left or right from it, so a positive and a negative term of the
   same size are visibly the same size — which a left-anchored bar cannot show.

   Scaled against the largest term rather than against the total gap: when two
   terms have opposite signs, one of them is larger than the total, and a scale
   built on the total would clip it. */
.attr-row {
  display: grid;
  /* The track is capped rather than fluid. On a 12-column panel at 1900px a
     `1fr` track came out 560px wide, and a diverging bar centred in that is a
     thin line floating in an empty box -- the magnitudes stop being
     comparable at a glance, which is the only thing the bar is for. Capped at
     320px the row reads the same at every width, and the prose takes the
     space instead. */
  grid-template-columns: minmax(11rem, 2.4fr) minmax(120px, 320px) 4.5rem;
  gap: 14px; align-items: center; padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.attr-row:last-child { border-bottom: 0; }
.attr-label b { display: block; font-size: .86rem; }
.attr-label span { display: block; line-height: 1.45; margin-top: 2px; }
.attr-track {
  position: relative; height: 12px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--surface-sunken) 80%, transparent);
  overflow: hidden;
}
.attr-mid {
  position: absolute; left: 50%; top: 0; bottom: 0; width: 1px;
  background: var(--border-strong);
}
.attr-bar { position: absolute; top: 1px; bottom: 1px; border-radius: var(--radius-pill); }
/* The residual is a remainder, not a cause. Dimmed and dashed so it reads as
   the arithmetic admitting to itself rather than as a fourth thing you did. */
.attr-resid { opacity: .72; }
.attr-resid .attr-track { border: 1px dashed var(--border-strong); background: none; }

@media (max-width: 620px) {
  /* The bar is the first thing to go: at this width the label and the number
     are the whole message, and a 90px track is a smear. */
  .attr-row { grid-template-columns: 1fr 4.5rem; }
  .attr-track { display: none; }
}


/* ---------- industry benchmark ----------
   One block per group, each holding four small comparisons. A grid rather than
   a table because the unit of reading is "our number against theirs" and a
   table row splits that pair across two columns with nine others between. */
.bmk { display: flex; flex-direction: column; gap: 14px; }
.bmk-group { border: 1px solid var(--border); border-radius: var(--radius);
  padding: 11px 13px;
  background: color-mix(in oklab, var(--surface-1) 55%, transparent); }
.bmk-head { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  margin-bottom: 9px; }
.bmk-cells { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.bmk-cell { display: flex; flex-direction: column; gap: 2px; }
.bmk-lab { font-family: "IBM Plex Mono", monospace; font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; }
.bmk-pair { display: flex; gap: 6px; align-items: baseline; }
.bmk-pair b { font-size: 1.02rem; }
.bmk-gap { font-family: "IBM Plex Mono", monospace; font-weight: 600; }

/* ---------- community calls ----------
   A published call is a paragraph with a number beside it, not a table row:
   the thesis is the point, and a table cell would truncate the one part that
   distinguishes a call from a ticker. */
.callrow {
  display: grid; grid-template-columns: 1fr minmax(140px, auto);
  gap: 14px; align-items: start;
  padding: 11px 0; border-bottom: 1px solid var(--border);
}
.callrow:last-child { border-bottom: 0; }
.callrow.mine { border-left: 2px solid var(--brand); padding-left: 11px; }
.callside { text-align: right; }
.callside b { display: block; font-size: 1.05rem; }
.callrow .pill {
  font-family: "IBM Plex Mono", monospace; font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; border: 1px solid currentColor;
  border-radius: var(--radius-pill); padding: 1px 7px;
}
tr.mine td { background: color-mix(in oklab, var(--brand) 7%, transparent); }

@media (max-width: 620px) {
  .callrow { grid-template-columns: 1fr; }
  .callside { text-align: left; }
}
