/* il-app-skin.css — the Claude Design page, applied to this app's own DOM.
   ===========================================================================
   Written against the real contract in web/static/index.html: .topbar,
   nav.tabs / nav.subtabs, section.view, .card (+.hero), .row, .field, .seg,
   .callout, .tablewrap table, .cardgrid, .badge, .brand. No new markup is
   required — restyling only. Every value is a theme.js token, so all twelve
   moods and all seven shapes keep working.

   Load order matters: this comes after style.css so it wins on equal
   specificity, and before il-design-bridge.css so the shape entrance grammar
   layers on top.

     <link rel="stylesheet" href="/style.css">
     <link rel="stylesheet" href="/il-app-skin.css">
     <link rel="stylesheet" href="/il-design-bridge.css">
     <script src="/il-enter.js" defer></script>

   Three faces, already self-hosted in web/static/fonts/:
   Instrument Serif for display, Manrope for interface, IBM Plex Mono for data.
   =========================================================================== */

:root {
  /* The content column used to be a flat 1240px, which is why the app did not
     appear to respond to zoom: zoomed out, the viewport gets wider in CSS
     pixels and the column stayed put, so the terminal sat in the middle of a
     widening empty page instead of using it. It now tracks the window and only
     stops at 1680, where a 12-column grid of panels is already as wide as it
     can be read comfortably.

     96vw rather than a fixed inset so the margin is proportional too: at
     1280 this lands on 1228, within a couple of pixels of the old value, so
     nothing about the ordinary desktop case changes. */
  --il-shell: min(1680px, 96vw);
  --il-gutter: clamp(16px, 2.2vw, 34px);
  /* The fixed chrome, as numbers three stylesheets and one script all need to
     agree on. They were each carrying a literal 53px, which was the top bar's
     height at some earlier point and is now 70 — so every sticky layer beneath
     it was anchored 17px too high, and anything scrolled to by script landed
     under the bar. One place to change, and `--il-chrome-h` is the sum every
     layer below the section bar actually wants. */
  --il-topbar-h: 70px;
  --il-secbar-h: 44px;
  /* Below 1080px the view rail is a strip stuck under the section bar, so it
     is chrome too -- and it was not counted. Everything that sticks "under the
     chrome" (the company page's chapter strip and its ticket, the page rail,
     the scroll target) stuck under the strip instead: at 400px the chapter
     strip sat exactly behind it, at 900px the ticket lost its top 45px.
     measureChrome() writes the strip's height here, 0 where the rail is the
     desktop's side column; the rail itself anchors below the section bar. */
  --il-viewrail-h: 0px;
  --il-chrome-h: calc(var(--il-topbar-h) + var(--il-secbar-h) + var(--il-viewrail-h));
  --il-panel-blur: blur(18px) saturate(1.15);
  --il-panel-shadow: 0 24px 60px -28px color-mix(in oklab, var(--plane) 12%, #000 88%);
  --il-lift-shadow: 0 14px 34px -18px color-mix(in oklab, var(--brand) 55%, transparent);
}

body {
  font-family: Manrope, system-ui, sans-serif;
  color: var(--text-secondary);
  background: var(--plane);
  text-wrap: pretty;
}

/* ===========================================================================
   Type — the one change from the handoff that clearly worked, applied
   consistently instead of per-view.
   =========================================================================== */

/* --head-ink, not --text-primary: every canvas theme carries a separate
   heading colour, and in the dark moods it is the vivid one that identifies
   the family (Sovereign lavender, Aegis gold, Aether mint, Eclipse ice).
   Collapsing it into body ink is what made the dark moods read as the same
   grey page with a different picture behind it. Falls back to --text-primary
   for any theme that does not set one. */
h1, h2, h3, .brandtext strong {
  font-family: "Instrument Serif", Georgia, serif;
  font-weight: 400;                     /* never bold the serif */
  line-height: 1.1;
  color: var(--head-ink, var(--text-primary));
  letter-spacing: -.2px;
}
h1 { font-size: clamp(38px, 5.4vw, 62px); line-height: 1.03; }
h2 { font-size: clamp(26px, 3.4vw, 34px); margin: 0 0 .35rem; }
h3 { font-size: 21px; margin: 0 0 .3rem; }

/* The eyebrow. style.css uses .small.muted above panels; this gives that
   pairing the design's mono treatment wherever it sits directly under a
   heading or at the top of a card. */
.card > .small.muted:first-child,
.eyebrow {
  font-family: "IBM Plex Mono", monospace;
  font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-muted);
}
.small { font-size: 13.5px; line-height: 1.6; }
.muted { color: var(--text-muted); }
p { line-height: 1.6; max-width: 74ch; }

/* Every number, ticker, score and axis label is mono — this is what stops
   the app reading as a game and starts it reading as a publication. */
td, th, .badge, .count, code, input[type="number"], .figure {
  font-variant-numeric: tabular-nums;
}
td, .badge, .count, code { font-family: "IBM Plex Mono", monospace; }

/* ===========================================================================
   Top bar
   =========================================================================== */

/* The top bar spans the window, edge to edge.
   =========================================================================
   It used to be `max-width: var(--il-shell); margin: 0 auto`, which centred
   the bar inside a 96vw column and left a strip of background either side of
   it. A bar that stops short of the window reads as a card that happens to be
   at the top, not as the application's chrome — and the border-bottom, which
   is meant to be the line under the whole app, stopped short with it.

   Chrome is full-bleed and content is measured: that is the split every
   product with a left rail uses, and it is why the rail below can sit against
   the left edge instead of floating 2vw inside it. */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  max-width: none; margin: 0;
  padding: 14px var(--il-gutter);
  background: color-mix(in oklab, var(--plane) 72%, transparent);
  backdrop-filter: var(--il-panel-blur);
  border-bottom: 1px solid var(--border);
}

/* ---------------------------------------------------------------------------
   The theme rail — the canvas's own control, and its geometry: a 4px-padded
   track holding 34px square buttons with 16px marks, bright/dark riding on the
   end of the same pill. It replaced a "◆ Mood" button that opened a popover
   plus a separate contrast button; picking a mood is one click on the emblem
   now, and the two settings read as the single control they are.

   The active family is marked by a filled plate and the accent, not by colour
   alone -- aria-pressed carries it for anyone not seeing the fill.
   --------------------------------------------------------------------------- */
/* One rail now: the moods. Shapes moved into the Scenery popover. */
.themestack { display: flex; flex-direction: column; gap: 3px; }

/* The shape buttons that used to live on a header rail are gone: a shape is
   picked once and lived with, so it belongs in the Scenery popover beside the
   background and motion controls. The popover styles its own `.shapeopt`
   buttons; nothing here served them. Removed with the rail rather than left
   as rules matching no element -- `--sr` was set only by the rail's markup,
   so check_tokens.py flagged it the moment the markup went. */

.il-famrail {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--surface-1) 55%, transparent);
}
.il-famrail button {
  position: relative; display: grid; place-items: center;
  width: 32px; height: 32px; padding: 0; border: 0; cursor: pointer;
  border-radius: var(--radius-pill); background: transparent;
  /* --text-secondary, not --text-muted: these are six 16px line drawings on a
     translucent bar, and at muted they were barely readable over a dark scene
     -- you could not tell the rapier from the dagger without hovering. */
  color: var(--text-secondary); transition: background var(--fast) var(--ease),
    color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.il-famrail button:hover { color: var(--text-primary);
  background: color-mix(in oklab, var(--brand) 10%, transparent); }
.il-famrail .il-fam[aria-pressed="true"] {
  color: var(--brand); background: color-mix(in oklab, var(--brand) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand) 34%, transparent);
}
.il-famrail svg { display: block; }
/* Bright/dark sits on the same track but is not one of the six, so it is
   separated by a hairline rather than by a gap that would break the pill. */
.il-famrail .il-lum {
  margin-left: 2px; font-size: 14px; line-height: 1; color: var(--text-secondary);
  border-left: 1px solid var(--border); border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
  width: 34px;
}
.il-famrail .il-lum:hover { color: var(--brand); }
@media (max-width: 720px) {
  /* Below this the rail would push the search box off the row; the six marks
     stay reachable in the Scenery panel, which lists them as tiles. */
  .il-famrail .il-fam { display: none; }
  .il-famrail .il-lum { border-left: 0; border-radius: var(--radius-pill); }
}
.brand { display: flex; align-items: center; gap: 11px; background: none; border: 0; padding: 0; cursor: pointer; }
.brandmark { flex: none; width: 30px; height: 30px; display: grid; place-items: center; color: var(--brand); }
.brandtext { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; text-align: left; }
.brandtext strong { font-size: 21px; letter-spacing: .2px; white-space: nowrap; }
.brandtext > span:last-child {
  font-family: "IBM Plex Mono", monospace; font-size: 10px; line-height: 1.4;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-muted);
  /* one line or nothing: this is a strapline, and a wrapped strapline pushes
     the whole top bar taller on every narrow window */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34ch;
}
.brand { flex: 0 1 auto; min-width: 0; }
/* The trailing controls are all fixed-width, so one row needs ~1000px. Below
   that the search field takes a deliberate full-width second row rather than
   letting the smallest button orphan onto its own line. */
.searchwrap { flex: 1 1 240px; min-width: 180px; }
/* The search box is capped at 400px (style.css), so on a wide window the row
   ended with ~110px of dead space after the profile button and the controls
   floated in the middle of nowhere. This splits the bar into the two clusters
   it actually is — identity and search on the left, controls on the right —
   which is also the canvas's shape. .seg is the first of the right-hand group;
   #providerBadge sits before it and is hidden except when a provider warning
   is showing, where being on the left is correct. */
.topbar > .seg { margin-left: auto; }
@media (max-width: 1150px) {
  .searchwrap { order: 10; flex: 1 1 100%; }
}
.searchwrap input {
  width: 100%; box-sizing: border-box; padding: 9px 14px;
  font-family: Manrope, sans-serif; font-size: 13.5px; color: var(--text-primary);
  background: color-mix(in oklab, var(--surface-1) 62%, transparent);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  outline: none; transition: border-color .18s ease, box-shadow .18s ease;
}
.searchwrap input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--brand) 12%, transparent);
}
.badge {
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  padding: 6px 11px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--brand) 12%, transparent);
  color: var(--brand-deep); border: 0; cursor: pointer;
}

/* ===========================================================================
   Buttons — .btn, .btn.primary, .btn.tiny, and the .seg segmented group
   =========================================================================== */

.btn {
  font-family: Manrope, sans-serif; font-size: 13.5px; font-weight: 600;
  padding: 10px 17px; white-space: nowrap; cursor: pointer;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: transparent; color: var(--text-secondary);
  transition: color .18s ease, border-color .18s ease, background .18s ease, transform .18s ease;
}
.btn:hover { color: var(--brand); border-color: var(--brand); }
.btn.primary {
  border-color: transparent; color: var(--plane);
  background: color-mix(in oklab, var(--brand-deep) 88%, var(--text-primary));
  box-shadow: var(--il-lift-shadow);
}
.btn.primary:hover { color: var(--plane); filter: brightness(1.08); }
.btn.tiny { font-size: 12px; padding: 6px 12px; }

.seg {
  display: flex; gap: 4px; padding: 4px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: color-mix(in oklab, var(--surface-1) 45%, transparent);
}
.seg { flex: none; }
.seg button {
  white-space: nowrap; font-family: Manrope, sans-serif; font-size: 12.5px; font-weight: 600;
  padding: 7px 15px; cursor: pointer; border: 0;
  border-radius: var(--radius-pill);
  background: transparent; color: var(--text-muted);
  transition: all .18s ease;
}
.seg button[aria-pressed="true"] {
  background: var(--surface-1); color: var(--brand);
  box-shadow: 0 6px 18px -10px color-mix(in oklab, var(--brand) 55%, transparent);
}

/* ===========================================================================
   The two nav rails — sections above, views below
   =========================================================================== */

.tabs, .subtabs {
  display: flex; gap: 8px; flex-wrap: nowrap; overflow-x: auto;
  max-width: var(--il-shell); margin: 0 auto;
  padding: 12px var(--il-gutter) 0;
  scrollbar-width: none;
}
.subtabs { padding-top: 8px; padding-bottom: 14px; }
.tabs::-webkit-scrollbar, .subtabs::-webkit-scrollbar { display: none; }

.tabs button {
  flex: none; white-space: nowrap;
  font-family: Manrope, sans-serif; font-size: 14px; font-weight: 600;
  padding: 9px 16px; cursor: pointer; border: 0; border-radius: var(--radius-pill);
  background: transparent; color: var(--text-muted); transition: all .18s ease;
}
.tabs button:hover { color: var(--brand); }
.tabs button[aria-selected="true"] {
  background: color-mix(in oklab, var(--brand) 12%, transparent);
  color: var(--brand-deep);
}
.subtabs button {
  flex: none; white-space: nowrap;
  font-family: "IBM Plex Mono", monospace; font-size: 11.5px;
  letter-spacing: .06em; padding: 7px 13px; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-pill);
  background: transparent; color: var(--text-muted); transition: all .18s ease;
}
.subtabs button:hover { color: var(--brand); border-color: var(--border-strong); }
.subtabs button[aria-selected="true"] {
  border-color: var(--brand); color: var(--brand);
  background: color-mix(in oklab, var(--brand) 8%, transparent);
}

/* ===========================================================================
   Views and cards
   =========================================================================== */

main { max-width: var(--il-shell); margin: 0 auto; padding: 0 var(--il-gutter) 96px; }
.view { display: none; flex-direction: column; gap: 18px; }
.view.active { display: flex; }

/* ---------------------------------------------------------------------------
   Which way the site moves when you press a button.

   The six sections are the stages of the loop in the order you walk them, and
   the view tabs inside each one continue that order — so "further along" is a
   real direction, not decoration. Moving forward, the new view enters from the
   right; going back, it enters from the left. app.js stamps data-view-dir on
   the element it is about to make active (see viewDirection()), and because
   the element goes from display:none to displayed in the same frame, the
   animation restarts on its own with no JS to drive it.

   It replaces the old `animation: fadeUp` on .view.active, which moved every
   view the same way regardless of where you came from and so said nothing.
   That rule is (0,2,0) in style.css; these are (0,3,0), so they win on
   specificity rather than on load order.

   Direction is left off entirely for a jump with no order to it — the command
   palette, a deep link, the first paint — where sliding would invent a
   relationship between two views that does not exist. Those keep the plain
   fade, which is what fadeUp degrades to here.

   The distance is small on purpose: 22px is enough to read as a direction
   without the page appearing to travel, and the panels inside are already
   playing the active shape's own entrance on top of it. */
@keyframes ilViewInFwd  { from { opacity: 0; transform: translate3d(22px, 0, 0); }
                          to   { opacity: 1; transform: none; } }
@keyframes ilViewInBack { from { opacity: 0; transform: translate3d(-22px, 0, 0); }
                          to   { opacity: 1; transform: none; } }

.view.active[data-view-dir="fwd"] {
  animation: ilViewInFwd var(--il-view-dur, .34s) cubic-bezier(.2, .85, .24, 1) both;
}
.view.active[data-view-dir="back"] {
  animation: ilViewInBack var(--il-view-dur, .34s) cubic-bezier(.2, .85, .24, 1) both;
}

/* Blade cuts, Soft settles — the transition takes its pace from the shape the
   same way the panel entrances do, so the whole switch reads as one gesture.
   Every shape gets a value rather than most of them falling to the default:
   an unlisted shape is the one that looks wrong next to its own panels. */
:root[data-shape="blade"]      { --il-view-dur: .24s; }
:root[data-shape="frame"]      { --il-view-dur: .28s; }
:root[data-shape="cardinal"]   { --il-view-dur: .34s; }
:root[data-shape="cardinal2"]  { --il-view-dur: .34s; }
:root[data-shape="system"]     { --il-view-dur: .32s; }
:root[data-shape="fluctlight"] { --il-view-dur: .40s; }
:root[data-shape="soft"]       { --il-view-dur: .42s; }

/* Same rule the shape entrances follow: the OS preference is the default for
   someone who has not said otherwise, and an explicit in-app choice is saying
   otherwise. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) .view.active[data-view-dir] { animation: none; }
}

/* ===========================================================================
   App shell — left rail + content column
   ===========================================================================
   Ported from the "Investor Leveling v2" design canvas's terminal shell.
   #sectionNav and #viewNav are unchanged elements built by app.js's existing
   SECTIONS/buildNav/paintNav (app.js:166-233) — only their layout changed,
   from a horizontal tab row to a vertical rail with a per-view header
   (#viewHead) between the rail and the view tabs. Selectors are qualified
   with the element's own id so they out-specificity style.css's plain
   .tabs/.subtabs/main rules regardless of load order — the exact bug class
   the last few porting commits spent a session hunting down. */

.ilshell[data-il-shell] {
  display: flex; align-items: flex-start; gap: 0;
  /* Full-bleed, so the rail sits against the left edge of the window rather
     than 2vw inside it. The measured column moved to .ilmain's content, which
     is where a max-width belongs: capping the shell capped the rail too, and
     a navigation rail has no reading width to protect. */
  max-width: none; margin: 0;
  /* The canvas's own rule, verbatim in intent: the shell carries a 55% wash of
     the theme background so the scene behind it is atmosphere rather than
     ground for body copy. Without it the view title, its blurb and the view
     tabs sit directly on whatever the mood is drawing — under Monarch, two
     dark pillars ran straight through the section blurb. theme.js computes
     --app-veil per mood; it falls back to transparent, which is what this
     looked like before. */
  background: var(--app-veil, transparent);
}
/* The six sections, as a horizontal bar under the top chrome.
   -------------------------------------------------------------------------
   Full-bleed and sticky, so it reads as a continuation of the top bar rather
   than as the page's first row. It sits at 53px because that is where the top
   bar ends; the view rail below then sticks under both. */
.secbar {
  position: sticky; top: var(--il-topbar-h); z-index: 30;
  display: flex; align-items: stretch; gap: 2px;
  padding: 0 var(--il-gutter);
  background: var(--app-veil, var(--surface-1));
  border-bottom: 1px solid var(--border);
  backdrop-filter: var(--il-panel-blur);
  overflow-x: auto; scrollbar-width: none;
}
.secbar::-webkit-scrollbar { display: none; }
/* On a phone the chrome sits over running text rather than over the room, and
   a translucent veil there is two lines of type on top of each other. The
   mood's own plane goes underneath, below the width where it happens; the
   desktop glass is unchanged. */
@media (max-width: 860px) {
  .topbar, .secbar { background-color: var(--plane); }
}
.secbar button {
  flex: 0 0 auto; display: flex; align-items: center; gap: .5em;
  padding: 11px 15px; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; white-space: nowrap;
  color: var(--text-secondary);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.secbar button:hover { color: var(--text-primary); }
.secbar button[aria-selected="true"] { color: var(--brand); border-bottom-color: var(--brand); }
.secbar button:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.secbar .sec-row { display: flex; align-items: center; gap: .5em; }
.secbar .sec-ic { opacity: .85; }
/* The blurb is carried in `data-il-info` on the same button, so the popover
   still has it. Rendering it here as well would be the same sentence twice —
   and a permanent one, which is what made the old rail 224px wide. */
.secbar .sec-blurb { display: none; }

/* The views inside the current section, in the space the sections left.
   `align-self: flex-start` plus a max-height is what makes sticky work here —
   a flex child stretches to the row height by default and a sticky box with
   nowhere to move never moves. */
#viewNav.viewrail {
  flex: 0 0 calc(196px + var(--il-gutter));
  display: flex; flex-direction: column; align-items: stretch; gap: 2px;
  max-width: none; overflow: visible;
  padding: 20px 12px 24px var(--il-gutter);
  position: sticky; top: var(--il-chrome-h); align-self: flex-start;
  max-height: calc(100vh - var(--il-chrome-h)); overflow-y: auto;
  border-right: 1px solid var(--border);
}
#viewNav.viewrail button {
  display: block; width: 100%; text-align: left; white-space: normal;
  padding: 7px 13px; border: 0; border-left: 2px solid transparent;
  border-radius: var(--radius-xs); background: none; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text-secondary);
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
}
#viewNav.viewrail button:hover { color: var(--text-primary); background: var(--surface-1); }
#viewNav.viewrail button[aria-selected="true"] {
  color: var(--brand); font-weight: 600; border-left-color: var(--brand);
  background: color-mix(in oklab, var(--brand) 8%, transparent);
}

/* The measured column, moved here from the shell.
   The rail is chrome and fills to the edge; the content has a reading width
   and stops. 1680 is where a 12-column grid of panels is already as wide as it
   can be scanned comfortably — past that the eye loses the row it is on
   travelling back to the next line, which is the same reason a newspaper is
   set in columns and not across the page.

   Left-aligned rather than centred: with the rail against the left edge, a
   centred content column would put a growing gap between the two and make the
   rail look detached at exactly the widths where there is most room. */
.ilmain[data-il-main] {
  flex: 1 1 auto; min-width: 0;
  padding: 0 var(--il-gutter);
  max-width: calc(1680px + 2 * var(--il-gutter));
}

.ilviewhead[data-il-head] { display: flex; align-items: flex-start; gap: 14px; padding: 22px 0 6px; }
.ilviewhead .ilhead-ic { flex: none; font-size: 20px; line-height: 1; margin-top: .2em; color: var(--brand); opacity: .85; }
.ilviewhead h1 { margin: 0 0 .2rem; }
.ilviewhead .ilhead-sec {
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px;
  letter-spacing: .16em; text-transform: uppercase;
}

.ilshell[data-il-shell] main { max-width: none; margin: 0; padding: 0 0 96px; }

@media (max-width: 1080px) {
  /* `align-items: flex-start` is right for the desktop row — a rail should not
     stretch to the height of a long page. Turned to a column it means the
     opposite thing, and both children shrink-wrap in the cross axis instead of
     filling the width: `.ilmain` measured 797px inside a 385px shell, so every
     card on every view laid out to its content width and had the overhang cut
     off by body's `overflow-x: hidden`. Restated here rather than fixed on each
     child, because it is one property meaning two different things in the two
     directions. Kept from the old vertical-section-rail layout: the trap
     belongs to the shell, not to whichever rail is in it. */
  .ilshell[data-il-shell] { flex-direction: column; align-items: stretch; }
  /* Same flex-basis trap, now for the view rail: `flex: 0 0 196px` is a width
     in a row and a *height* in a column, which would lay the view list out
     196px tall and mostly empty. */
  #viewNav.viewrail {
    flex: 0 0 auto; width: auto; flex-direction: row;
    /* Stretched, and allowed below its content's width. The desktop rail sets
       align-self: flex-start (a side column should not be as tall as the
       page), and in this column layout that means "as wide as your content"
       instead: 900px of view names inside a 400px phone, clipped by the page
       rather than scrolled by the strip -- Character and Journal simply could
       not be reached from it. */
    align-self: stretch; min-width: 0;
    /* z-index: cards are positioned (the aura isolates them) and painted over
       an unindexed sticky strip, border and all. Just under .secbar's 30. */
    position: sticky; top: calc(var(--il-topbar-h) + var(--il-secbar-h)); max-height: none; z-index: 29;
    overflow-x: auto; overflow-y: visible;
    padding: 8px var(--il-gutter);
    /* The veil on the mood's plane: translucent alone, it stacked page text
       under the view names as they scrolled past (same fix as .secbar). */
    background: linear-gradient(var(--app-veil, var(--surface-1)), var(--app-veil, var(--surface-1))), var(--plane);
    backdrop-filter: var(--il-panel-blur);
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  #viewNav.viewrail button {
    width: auto; white-space: nowrap; border-left: 0;
    border-bottom: 2px solid transparent; border-radius: 0;
  }
  #viewNav.viewrail button[aria-selected="true"] {
    border-left: 0; border-bottom-color: var(--brand); background: none;
  }
  /* No rail beside it any more, so the content column has the whole width. */
  .ilmain[data-il-main] { max-width: none; }
}

/* A panel is a surface, not a tint over whatever happens to be behind it.
   --surface-2 is translucent, which reads as a panel only while a scene is
   drawn underneath: with the Background control at None the panel and the page
   became the same colour and the terminal looked like an empty page with
   floating text. --panel-solid is theme.js's port of the canvas's own
   --panelSolid (the theme background mixed 30% toward white in light, 7% in
   dark), so a panel holds its edge at every background setting.

   backdrop-filter goes with it. The canvas calls it the most expensive thing
   on the page and takes a near-opaque tint instead; there is nothing left for
   it to blur once the panel is opaque, so it was pure cost. */
.card {
  padding: 24px 26px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-strong);
  background: var(--panel-solid, var(--surface-2));
  box-shadow: var(--il-panel-shadow);
  transition: background 1s ease, border-color 1s ease;
}
/* .card.hero opens a view: give it the page-heading treatment, not a
   bigger box. */
.card.hero { padding: 30px 30px 26px; background: var(--surface-1); }
.card.hero h2 { font-size: clamp(28px, 4.2vw, 40px); }
.card > h2 + p.small { margin-top: 0; }
.cardgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)); gap: 16px; }

.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.row .spacer { flex: 1; }

.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: .6rem; }
.field > span {
  font-family: "IBM Plex Mono", monospace; font-size: 10px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-muted);
}
.field input, .field select, .field textarea, textarea, select {
  width: 100%; box-sizing: border-box; padding: 11px 13px;
  font-family: Manrope, sans-serif; font-size: 14px; color: var(--text-primary);
  background: color-mix(in oklab, var(--surface-1) 60%, transparent);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  outline: none; transition: border-color .18s ease, box-shadow .18s ease;
}
.field input:focus, .field select:focus, .field textarea:focus, textarea:focus, select:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--brand) 12%, transparent);
}

/* ---------- dropdowns ----------

   Chris: *"the dropdown styling looks cheap."*

   It was, and the reason is one word: the rules above dressed a <select> in
   the app's border, surface and font and then left `appearance: auto`, so
   Chrome kept drawing the operating system's control inside them. On Windows
   that is a flat grey triangle hard against the right edge, at a size and
   colour nothing else on the page uses. Thirteen dropdowns, each with a
   Windows widget sitting in the corner of a lacquer-black terminal.

   So: draw the chevron ourselves, at the page's own weight and inset, in the
   page's own muted ink, and light it gold on hover the way every other control
   here answers the cursor.

   Two things beyond paint, both of which were real:

   - A <select> looked exactly like a text input, which is a lie about what it
     does. `cursor: pointer` and a chevron with room around it say "menu".
   - An unset filter read identically to a set one. Every select on the site
     uses `value=""` for its empty option, so `:has(option[value=""]:checked)`
     greys exactly the ones nobody has chosen yet -- you can now see at a
     glance which filters are doing something. */
.field select, select {
  appearance: none; -webkit-appearance: none;
  cursor: pointer;
  /* Room for the chevron, and for the longest value not to run under it. */
  padding-right: 34px;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 11px 7px;
  transition: border-color .18s ease, box-shadow .18s ease, background-image .18s ease;
}
.field select:hover, select:hover,
.field select:focus, select:focus { background-image: var(--select-chevron-lit); }

/* Nothing chosen yet. Not a disabled look -- the control is live, the value
   just is not set. */
select:has(option[value=""]:checked) { color: var(--text-muted); }

/* The popup list. `color-scheme: dark` on :root already keeps Chrome from
   drawing a white sheet; these give the rows the app's own surface so the
   open menu and the closed control are the same object. */
select option {
  background: var(--surface-1); color: var(--text-primary);
  padding: 8px 10px;
}
select option:checked { color: var(--gold); }
textarea { font-family: "IBM Plex Mono", monospace; font-size: 13px; line-height: 1.7; }

/* Callouts. These were 92% transparent because a colour was mixed into
   transparent; mix into the surface instead so they stay opaque over any
   scenery — see docs/HANDOFF.md. */
.callout {
  padding: 15px 17px; border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: color-mix(in oklab, var(--surface-1) 92%, var(--brand));
  color: var(--text-secondary); line-height: 1.6;
}
.callout.warn { background: color-mix(in oklab, var(--surface-1) 88%, var(--gold)); }
.callout.bad  { background: color-mix(in oklab, var(--surface-1) 88%, var(--critical)); }
.callout.good { background: color-mix(in oklab, var(--surface-1) 88%, var(--good)); }

details.doc summary {
  cursor: pointer; font-family: Manrope, sans-serif; font-weight: 600;
  font-size: 14px; color: var(--text-primary); padding: 4px 0;
}
details.doc[open] summary { color: var(--brand); margin-bottom: .6rem; }

/* ===========================================================================
   Tables
   =========================================================================== */

.tablewrap { overflow-x: auto; margin: 0 -26px; padding: 0 26px; scrollbar-width: thin; }
table { width: 100%; border-collapse: collapse; }
/* `cursor: default`, and the hover scoped to `.sortable`.
   This rule and style.css's `th` both handed every column header in the app a
   pointer and a hover-to-brand colour — the visual grammar of a button — and
   not one `th` here is clickable: the screener sorts through `button.il-sort`
   inside a grid. Two stylesheets making the same wrong promise is why fixing
   it in the base alone did nothing. A header opts into looking like a control
   by being one. */
thead th {
  position: sticky; top: 0; z-index: 1;
  text-align: left; white-space: nowrap; cursor: default;
  padding: 11px 12px;
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
  background: var(--surface-sunken);
  backdrop-filter: var(--il-panel-blur);
  border-bottom: 1px solid var(--border-strong);
}
thead th.sortable { cursor: pointer; }
thead th.sortable:hover { color: var(--brand); }
tbody td { padding: 12px; font-size: 13px; border-bottom: 1px solid var(--border); }
tbody td:first-child { font-weight: 500; color: var(--text-primary); }
tbody tr.clickable { cursor: pointer; transition: background .16s ease; }
tbody tr.clickable:hover { background: color-mix(in oklab, var(--brand) 8%, transparent); }
tbody tr:last-child td { border-bottom: 0; }
/* numeric columns right-align — mark them once in the renderer */
td.num, th.num { text-align: right; }

.legend {
  display: flex; gap: 18px; flex-wrap: wrap; padding-top: 12px;
  font-family: "IBM Plex Mono", monospace; font-size: 11px; color: var(--text-muted);
}

/* ===========================================================================
   Rank and tier colour — attribute-driven, so the renderer sets data-tier
   and never a colour. (The handoff's fifth fault: a data-tier the CSS did
   not style, painting scores in a colour that does not exist.)
   =========================================================================== */

[data-tier="S"], [data-rank="S"] { color: var(--brand-deep);     background: color-mix(in oklab, var(--brand) 16%, transparent); }
[data-tier="A"], [data-rank="A"] { color: var(--good);           background: color-mix(in oklab, var(--good) 16%, transparent); }
[data-tier="B"], [data-rank="B"] { color: var(--text-secondary); background: color-mix(in oklab, var(--text-primary) 9%, transparent); }
[data-tier="C"], [data-rank="C"] { color: var(--gold);           background: color-mix(in oklab, var(--gold) 16%, transparent); }
[data-tier="D"], [data-rank="D"],
[data-tier="E"], [data-rank="E"] { color: var(--critical);       background: color-mix(in oklab, var(--critical) 14%, transparent); }
[data-tier], [data-rank] {
  font-family: "IBM Plex Mono", monospace; font-weight: 600;
  padding: 3px 9px; border-radius: var(--radius-sm, 6px);
}

/* ===========================================================================
   Onboarding gate — same picker, given room
   =========================================================================== */

/* Dark glass, the recipe Chris chose for the sign-in gate (#authGate below):
   the mood's plane in 74% black, blurred, so the page stays underneath as depth.
   The base was 30% plane in 70% black with no alpha at all -- an opaque sheet
   the blur could never show through -- and the two overlays that kept it, the
   Buy ticket and the first-visit gate, blacked out the page they opened over:
   you bought KO from a black screen. "Flat 70% black threw the room away" was
   his word on exactly that. */
.onboard-overlay {
  position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; padding: 20px;
  background: color-mix(in oklab, var(--plane) 18%, rgba(0, 0, 0, .74));
  backdrop-filter: blur(14px) saturate(.92); -webkit-backdrop-filter: blur(14px) saturate(.92);
}
.onboard-overlay[hidden] { display: none; }
.card.onboard-modal { width: min(720px, 100%); max-height: 88vh; overflow-y: auto; padding: 28px 30px; }

/* ===========================================================================
   Toasts
   =========================================================================== */

#toasts { position: fixed; right: 18px; bottom: 18px; z-index: 95; display: flex; flex-direction: column; gap: 10px; }
#toasts > * {
  padding: 12px 16px; font-size: 13.5px;
  border-radius: var(--radius); border: 1px solid var(--border-strong);
  background: var(--surface-1); box-shadow: var(--il-panel-shadow);
  animation: il-toast-in .3s cubic-bezier(.2,.85,.24,1) both;
}
@keyframes il-toast-in { from { transform: translateY(10px); opacity: 0; } to { transform: none; opacity: 1; } }
/* **The kind of a toast has to show.** `#toasts > *` above is an id selector,
   so its `border` shorthand outranked style.css's coloured left edge -- the
   one thing that told "saved" (good) from "that failed" (bad) and "enter a
   ticker" (warn) -- and all 33 kinded toasts in the app looked identical. Its
   animation outranked the exit the same way, so a toast vanished instead of
   leaving. Both restated here at the same weight. */
#toasts > .toast { border-left: 3px solid var(--border-strong); }
#toasts > .toast.good { border-left-color: var(--good); }
#toasts > .toast:is(.bad, .crit) { border-left-color: var(--critical); }
#toasts > .toast.warn { border-left-color: var(--warning); }
#toasts > .toast.out { animation: il-toast-out .26s ease forwards; }
@keyframes il-toast-out { to { transform: translateY(6px); opacity: 0; } }

/* ===========================================================================
   Responsive — desktop, laptop and phone weighted equally
   =========================================================================== */

@media (max-width: 900px) {
  :root { --il-gutter: 20px; }
  .topbar { gap: 10px; padding-top: 12px; padding-bottom: 12px; }
  .brandtext > span:last-child { display: none; }
  .card { padding: 20px; }
  .card.hero { padding: 24px 22px 20px; }
  .tablewrap { margin: 0 -20px; padding: 0 20px; }
}
@media (max-width: 600px) {
  :root { --il-gutter: 16px; }
  .card { padding: 17px; }
  .tablewrap { margin: 0 -17px; padding: 0 17px; }
  tbody td, thead th { padding: 10px 9px; }
}
/* The mark is the way home on a phone, and the wordmark was what pushed the
   theme switch and the profile onto a row of their own: three rows of top bar
   before anything else. With the name off below 480 the bar is the mark, the
   reading mode, theme and profile on one row, and the search under it.
   #brandBtn carries the name as its aria-label, so a screen reader loses
   nothing. */
@media (max-width: 480px) {
  .topbar .brand .brandtext { display: none; }
}
/* **On a phone the top bar scrolls away; the section bar and the view strip
   stay.** Sticky, the three bars held 266px of an 860px screen (31%, and
   34% at 360px) for as long as you scrolled -- a third of the page spent on
   saying where you are. The top bar is search, theme and profile, all a
   scroll up away; where you are is the other two. A phone on its side is the
   same problem harder (the chrome was over half of a 360px-tall screen), hence
   the height condition. measureChrome() reads the position back, so every
   sticky layer below re-anchors on its own. */
@media (max-width: 600px), (max-height: 520px) {
  .topbar { position: relative; }
}

/* ===========================================================================
   The landing stage
   ===========================================================================
   Two stages in one document. il-stage.js stamps data-stage on <html>; the
   terminal's chrome is hidden by that rather than by touching each element,
   so a new piece of chrome is covered by the rule it is already written under.

   The landing page reuses the app's tokens throughout — same palette, same
   type scale, same shape radii — because the whole point of the playground is
   that what you choose out here is what you get inside. */

:root[data-stage="landing"] .topbar,
:root[data-stage="landing"] .ilshell,
:root[data-stage="landing"] .cmdscrim,
:root[data-stage="landing"] .cmdpalette,
:root[data-stage="landing"] .il-drawer,
/* The section bar is a sibling of .topbar, not a child of it, so it escaped
   the rule above and sat under the last landing section as a 44px strip of
   Status / Hunt / Codex tabs -- a terminal control at the foot of a page
   that is not the terminal. */
:root[data-stage="landing"] .secbar,
/* app.js's boot decides whether to show the first-visit gate before this file
   knows which stage it is on, and a modal over the landing page is two intros
   at once. Hidden by stage rather than by timing; entering the terminal marks
   the visit onboarded, and a deep link past the landing page still gets it. */
:root[data-stage="landing"] #onboardGate { display: none !important; }

/* The wordmark is the way back out (app.js wires #brandBtn to ILStage.exit),
   so there is no separate back control to style. */

.landing { min-height: 100vh; }
.lheader {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
  max-width: var(--il-shell); margin: 0 auto;
  padding: 18px var(--il-gutter);
}
/* flex: 0 0 auto because .brand in the top bar is `0 1 auto; min-width: 0` so
   it can give way to the search field. There is no search field out here and
   the wordmark should not shrink -- it was squeezing the strapline out. */
.lbrand { display: flex; align-items: center; gap: 12px; background: none; border: 0;
  flex: 0 0 auto; min-width: 0; }
/* style.css hides the strapline below 1500px because the top bar has a search
   field and six controls competing for the row. The landing header has the
   wordmark and one button, so it has the room and the line is worth having --
   it is the first thing that says what this actually is. */
.lbrand .brandtext > span:last-child { display: block; max-width: none; }
/* The top bar's 34ch cap was cutting this to "QU…" at 1280px beside one
   button and a screen of empty header. Out here it only needs the cap back
   where the button actually starts to crowd it. */
@media (max-width: 900px) { .lbrand .brandtext > span:last-child { max-width: 34ch; } }
@media (max-width: 560px) { .lbrand .brandtext > span:last-child { display: none; } }

/* Text standing on the room rather than in a card gets a halo in the room's
   own colour. The market floor is a fixed layer the page scrolls over, so its
   candle rows sit behind whatever lands in the bottom third of the screen --
   at 1280x900 that was a heading, a lead paragraph or the closing line at
   every stop down the page, with candles striking through the glyphs. A halo
   in --plane is invisible over the plain background and only shows where
   there is something behind the text to separate it from, and it repaints
   under all twelve moods with no per-mood value. */
.landing :is(.lhero-copy, .lsection, .lplay-copy, .lcta) > :is(h1, h2, p),
.landing .lmodel-foot, .landing .lrun-bar .small {
  text-shadow: 0 0 2px var(--plane), 0 0 10px var(--plane), 0 0 22px var(--plane);
}

.lmain {
  max-width: var(--il-shell); margin: 0 auto;
  padding: 0 var(--il-gutter) 96px;
  display: flex; flex-direction: column; gap: 72px;
}

/* The hero is two columns: the argument, and the argument happening.
   Flex-wrap rather than a breakpoint so the visual drops under the copy on its
   own basis — the same lesson as the shortlist window, which started with a
   grid and a media query and was wrong at every width the query did not name. */
/* Top-aligned, not centred. The card is taller than the copy block, so
   centring pushed the headline down the page and the hero lost its top edge --
   the eyebrow and the card should start on one line. */
.lhero { display: flex; flex-wrap: wrap; gap: 26px 44px; align-items: flex-start;
  padding-top: 34px; }
/* 440 rather than 400: at the narrower basis the headline broke to three
   lines, and "A price is what you pay. A value is what it is worth." is a
   two-part sentence that should set as two. */
.lhero-copy { flex: 3 1 440px; max-width: 60ch; min-width: 0; padding-top: 6px; }
/* Capped and pushed right: the column grows to fill the row, but a 320px
   shape centred in 850px of card is mostly empty card. */
.lhero-viz { flex: 2 1 320px; min-width: 0; max-width: 420px; margin-left: auto; }

/* The card.
   ---------------------------------------------------------------------------
   A quiet frame with one loud number in it. The shape is the hook, the move to
   fair value is the argument, and everything else is set small enough that the
   eye reaches those two first. The gradient is the faintest possible nod to the
   panel treatment in the terminal, so the landing and the app read as one
   product without the landing borrowing the terminal's chrome wholesale. */
.lhv-card {
  position: relative; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in oklab, var(--brand) 9%, transparent), transparent 62%),
    var(--surface-1);
  padding: 16px 18px 14px; text-align: center;
  box-shadow: var(--shadow-lg, 0 18px 50px rgba(0,0,0,.28));
}
.lhv-flake { display: grid; place-items: center; min-height: 256px; margin-top: -2px; }
.lhv-flake svg { overflow: visible; max-width: 100%; height: auto; }
.lhv-loading { margin: 0; padding: 3rem 0; }

.lhv-who { display: flex; align-items: baseline; justify-content: center; gap: .4rem;
  flex-wrap: wrap; margin-top: .1rem; }
.lhv-who b { font-family: var(--font-mono); font-size: 1rem; letter-spacing: .03em; }
.lhv-name { font-size: .82rem; color: var(--text-muted); }
.lhv-verdict { font-size: .76rem; color: var(--text-secondary); margin-top: .1rem; }

/* The number the page is about. Everything else on the card is set against
   this being the largest thing on it. */
.lhv-gap { margin: .7rem 0 .6rem; }
.lhv-gap-n { display: block; font-family: var(--font-mono);
  font-size: clamp(2.1rem, 4.4vw, 2.9rem); line-height: 1; letter-spacing: -.02em; }
.lhv-gap.under .lhv-gap-n { color: var(--good); }
.lhv-gap.over .lhv-gap-n { color: var(--critical); }
.lhv-gap.fair .lhv-gap-n { color: var(--text-secondary); font-size: 1.8rem; }
.lhv-gap-w { display: block; font-size: .76rem; color: var(--text-secondary);
  line-height: 1.45; margin-top: .3rem; max-width: 30ch; margin-inline: auto; }
.lhv-gap-s { display: block; font-size: .72rem; color: var(--text-muted); margin-top: .25rem; }
.lhv-gap.none { padding: .7rem 0; }
.lhv-gap.none .lhv-gap-w { max-width: 34ch; }

/* worth / trading at, as two labelled figures rather than a sentence. */
.lhv-nums { display: flex; justify-content: center; align-items: baseline; gap: 1.3rem;
  flex-wrap: wrap; padding-top: .7rem; border-top: 1px solid var(--border); }
.lhv-nums span { display: flex; flex-direction: column; align-items: center; gap: .05rem; }
.lhv-nums i { font-style: normal; font-size: .66rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-muted); }
.lhv-nums b { font-family: var(--font-mono); font-size: 1.02rem; color: var(--text-primary); }
.lhv-ccy { font-family: var(--font-mono); font-size: .68rem; color: var(--text-muted);
  align-self: flex-end; }

/* The picker, small and inside the card.
   ---------------------------------------------------------------------------
   These were tall two-line tiles at the hero's full width, and Chris's read was
   "looks bad... make the icons smaller and make it look better all together on
   the right side."

   So: one line each, ticker and score side by side, at roughly a third of the
   previous height. Sixteen of them fit five across in four quiet rows under the
   card's own rule, which reads as the control for the shape above rather than
   as a band of buttons competing with the headline. The selected one is the
   only coloured thing in the grid, so the eye goes to the card and comes back
   to the row rather than the other way round. */
/* **The logo above its ticker, not beside it.**
   Chris: *"the google logo could be better... And if the space is too small
   for the logos make some adjustments that everyting fits the best way
   possible. COuld be putting the clickable companies into their own small tabs
   below the snowflake or so."*

   Side by side, a chip spent most of its width on four mono characters and
   left the logo 15px -- small enough that a good mark and a bad one look the
   same, which is why the Alphabet tile could sit there unnoticed. Stacking
   them turns the width the ticker was using into height the logo can have:
   **six columns instead of four, and a 26px tile instead of 15px**, which is
   73% more logo in about the same total height, because two rows replace
   three.

   The column count is pinned rather than `auto-fit`. Twelve divides evenly by
   six and by four but not by five, and an auto-fit grid that lands on five
   leaves a last row of two — a ragged edge directly under the card's most
   deliberate object. Six across in the card, four on a phone. */
.lhv-chips {
  display: grid; gap: 5px 2px; margin-top: .85rem; padding-top: .8rem;
  border-top: 1px solid var(--border);
  grid-template-columns: repeat(6, 1fr);
}
@media (max-width: 560px) { .lhv-chips { grid-template-columns: repeat(4, 1fr); } }
.lhv-chip {
  display: flex; flex-direction: column; align-items: center; gap: .26rem;
  padding: .3rem .1rem .26rem; min-width: 0; cursor: pointer; line-height: 1.2;
  border: 1px solid transparent; border-radius: var(--radius-xs);
  background: transparent; color: var(--text-muted);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.lhv-chip:hover { background: var(--surface-sunken); color: var(--text-secondary); }
.lhv-chip[aria-pressed="true"] {
  border-color: color-mix(in oklab, var(--brand) 55%, transparent);
  background: color-mix(in oklab, var(--brand) 16%, transparent);
  color: var(--text-primary);
}
.lhv-chip-t { font-family: var(--font-mono); font-size: .58rem; letter-spacing: .02em;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lhv-chip-n { font-family: var(--font-mono); font-size: .7rem; opacity: .85; }

/* The company mark.
   `--mark-h` is the only thing JavaScript decides; the tint is mixed into the
   active theme's own surface here, so twelve marks sit inside whichever of the
   twelve moods is on instead of punching a rainbow through it. Same reason the
   letters take their colour from a token rather than from the hue. */
/* An emblem, not a placeholder. A flat pastel square with 10px mono letters
   read as "the logo failed to load" -- which, beside real logos, is exactly
   what it looked like. It is lit now: a gradient in the company's hue mixed
   into the mood's surface, a fine rim and a top highlight, and the letters
   set in the display serif, the one typeface on the page that looks cut
   rather than printed. Two letters still, because NVDA and NVO share a
   first one. */
.lhv-mark {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 7px;
  display: grid; place-items: center;
  font-family: var(--font-display); font-size: 13.5px; font-weight: 400;
  letter-spacing: -.02em; color: var(--text-primary);
  background:
    linear-gradient(155deg,
      color-mix(in oklab, hsl(var(--mark-h, 210) 64% 60%) 44%, var(--surface-1)) 0%,
      color-mix(in oklab, hsl(var(--mark-h, 210) 58% 42%) 30%, var(--surface-sunken)) 100%);
  box-shadow:
    inset 0 1px 0 0 color-mix(in oklab, #fff 26%, transparent),
    inset 0 0 0 1px color-mix(in oklab, hsl(var(--mark-h, 210) 62% 52%) 42%, transparent),
    0 2px 6px -3px color-mix(in oklab, hsl(var(--mark-h, 210) 60% 30%) 55%, transparent);
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.lhv-chip[aria-pressed="true"] .lhv-mark {
  background:
    linear-gradient(155deg,
      color-mix(in oklab, hsl(var(--mark-h, 210) 68% 62%) 64%, var(--surface-1)) 0%,
      color-mix(in oklab, hsl(var(--mark-h, 210) 62% 46%) 50%, var(--surface-1)) 100%);
  box-shadow:
    inset 0 1px 0 0 color-mix(in oklab, #fff 34%, transparent),
    inset 0 0 0 1px color-mix(in oklab, hsl(var(--mark-h, 210) 66% 56%) 70%, transparent),
    0 0 0 2px color-mix(in oklab, var(--brand) 45%, transparent);
}
/* Knocked out: the one categorical verdict the grid still carries now the
   scores have moved to the card. Grey rather than red -- a red tile beside a
   crimson selected state reads as two kinds of selected. */
.lhv-chip.is-ko .lhv-mark {
  background: var(--surface-sunken); color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border);
}
/* The real logo, laid over the generated mark.
   The mark stays underneath rather than being replaced, so `onerror` removing
   the image is all it takes to fall back — there is no state where the tile is
   empty. `object-fit: contain` because a favicon can be any aspect, and the
   inset keeps a square logo off the tile's rounded corners. */
.lhv-mark { position: relative; overflow: hidden; }
/* The generated letters sit in their own element so the logo can hide them
   rather than paint over them. A favicon with a transparent background --
   Nvidia's and Visa's both are -- used to show the fallback letters through
   itself, which reads as a printing error rather than as a logo. */
/* One serif capital, set to the tile rather than typed into it.
   `markLetters` now yields a single letter (the company's initial, not the
   ticker's), so this is a monogram rather than an abbreviation: display face,
   optical size, and a hair of negative tracking so the letter sits centred
   on its own stem instead of on its bounding box. Two mono capitals at 26px
   looked like a slot waiting for a logo; for Alibaba and Palantir, which
   publish no square mark anywhere, this *is* the mark. */
.lhv-letters {
  display: block; line-height: 1;
  font-family: var(--font-display); font-weight: 400;
  font-size: 1.02rem; letter-spacing: -.02em;
  /* The tinted plate behind it is light in both themes, so the letter takes
     the same ink the rest of the chip does rather than a hue-derived colour
     that would fail contrast on half the twelve moods. */
  color: var(--text-primary); opacity: .92;
  transform: translateY(.02em);
}
.lhv-mark.has-logo .lhv-letters { opacity: 0; }
.lhv-logo {
  /* 1px rather than 2: at 26px the tile can afford to give the mark almost all
     of itself, and the rounded corner below is what keeps it off the edge. */
  position: absolute; inset: 1px; width: calc(100% - 2px); height: calc(100% - 2px);
  object-fit: contain; border-radius: 5px;
  /* Invisible until `onload` says it arrived — see `logoTag`. A pending or
     failed request must never hide the letters it is sitting on. */
  opacity: 0;
}
.lhv-logo.is-on { opacity: 1; background: var(--logo-plate); }
@media (prefers-reduced-motion: reduce) { .lhv-mark { transition: none; } }
/* Waiting on the filings: the marks and tickers are already right, so the row
   only has to look inactive. */
.lhv-chip.is-pending { cursor: default; opacity: .45; }

/* The card's skeleton, while a cold EDGAR fetch runs.
   A keyframe rather than anything driven from script: this is the state the
   page is in before its data arrives, so it has to hold up in a tab that is
   never painted, and an element whose animation does not run sits at its
   ordinary style -- a plain rule, which is exactly right. */
.lhv-skel { display: block; width: 100%; height: auto; }
.lhv-skelrows { display: flex; flex-direction: column; align-items: center; gap: .55rem;
  padding: .3rem 0 .2rem; }
.lhv-skelrows i {
  display: block; height: 9px; border-radius: 999px; background: var(--surface-sunken);
  animation: lhvSkel 1.5s ease-in-out infinite;
}
.lhv-skelrows i.tall { height: 22px; }
.lhv-skelrows i:nth-child(2) { animation-delay: .12s; }
.lhv-skelrows i:nth-child(3) { animation-delay: .24s; }
.lhv-skelrows i:nth-child(4) { animation-delay: .36s; }
@keyframes lhvSkel { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .lhv-skelrows i { animation: none; } }
.lhv-err { margin: 0; padding: 1.2rem .4rem; line-height: 1.5; }
@media (prefers-reduced-motion: reduce) { .lhv-chip { transition: none; } }

/* The premium, under the move it is the other half of. */
.fp-sub { display: block; font-size: .72rem; color: var(--text-muted); margin-top: .1rem; }

/* Sized by the window, capped by its own column. The headline is two
   sentences and should set as two lines, and "A value is what it is worth."
   is 8.4em long -- which fits the 540px column at every width but one band:
   from 900 to 999 the card stands beside the copy, the column narrows to
   452-485px while 6vw is still 54-58px, and the couplet broke into three.
   11.5cqi keeps the longer sentence inside the column there and changes
   nothing anywhere else (measured at 360-1920: 62px from 1100 up, as before). */
.lhero-copy { container-type: inline-size; }
.lhero h1 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(34px, min(6vw, 11.5cqi), 62px); line-height: 1.04; letter-spacing: -.015em;
  margin: .2rem 0 .6rem; text-wrap: balance;
}
.lhero .lead { font-size: clamp(15px, 1.5vw, 18px); line-height: 1.6; color: var(--text-secondary); }
.lhero .row { margin-top: 1.4rem; }

/* auto-fit rather than a fixed column count: the six cards have to read the
   same at 1280px, at 400px and at every browser zoom in between. */
.lgrid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}
.lcard {
  padding: 22px 24px; border-radius: var(--radius-lg);
  border: 1px solid var(--border); background: var(--surface-2);
}
.lcard h2 {
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-secondary);
  margin: 0 0 .55rem;
}
.lcard p { margin: 0; font-size: 14px; line-height: 1.62; color: var(--text-secondary); }

/* --- landing: what you do here ------------------------------------------
   Four cards, each with a figure above the words rather than beside them.
   Beside was tried first and loses twice: the figure gets half a card's width
   at 1280px and then stacks under the text on a phone anyway, so the desktop
   layout is paying for a phone case it does not even produce.

   The figure sits on `--surface-sunken` with a rule under it, which is the
   same relationship a chart has to its caption everywhere else in this app --
   a drawn thing is a well in the panel, not a picture hung on it. */
/* Two by two, not auto-fit. Auto-fit put three in a row at 1280px and left
   the fourth -- the clan card, the one carrying the NEXT chip -- alone on a
   second row, where an orphan reads as an afterthought rather than as the
   direction. Four is a square number; the grid should say so. */
.lwhat-grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 760px) { .lwhat-grid { grid-template-columns: minmax(0, 1fr); } }
.lwhat-card {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface-2); overflow: hidden;
  display: flex; flex-direction: column;
}
/* A fixed ratio rather than a height: the four figures are drawn to one
   viewBox and must stay the same shape as the column narrows, or the row
   develops a ragged top edge at exactly the width where two columns become
   one. */
.lwhat-fig {
  aspect-ratio: 16 / 7; background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
}
.lwhat-fig svg { display: block; width: 100%; height: 100%; }
/* In two columns a card is ~580px wide, and 16/7 of that scaled the drawings'
   9px labels past the size of the body copy under them. A fixed well height
   holds them near the scale they were drawn for; the SVGs are `meet`, so the
   drawing centres in the wider well and every card still has the same top
   edge. One column keeps the ratio, where the width is a phone's. */
/* Scoped to the cards: #lmodel's matrix borrows .lwhat-fig for its well and
   must keep its own ratio. */
@media (min-width: 761px) { .lwhat-card .lwhat-fig { aspect-ratio: auto; height: 176px; } }
.lwhat-copy { padding: 17px 21px 19px; }
/* Weight 400 and a serif size. `h1, h2, h3` sets "never bold the serif" at the
   top of this file, and 600 here asked the browser for a weight the face does
   not ship -- Instrument Serif is 400 only -- so it smeared a synthetic bold
   across 15px type. The serif carries a heading by size, not by weight. */
.lwhat-copy h3 {
  margin: 0 0 .4rem; font-size: 21px; font-weight: 400;
  color: var(--head-ink, var(--text-primary)); line-height: 1.15;
}
.lwhat-copy p { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--text-secondary); }
/* The honest chip. A feature that is designed and not built says so on its
   own card -- see the comment on #lwhat in index.html and docs/GUILDS.md. */
.lwhat-next {
  display: inline-block; vertical-align: 2px; margin-left: .5em;
  font-family: "IBM Plex Mono", monospace; font-size: 9px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  padding: .18em .5em; border-radius: 999px;
  color: var(--text-muted-on-panel, var(--text-muted));
  border: 1px solid var(--border-strong); background: var(--surface-sunken);
}

/* --- landing: the model, beside the matrix it is named after -------------
   The figure leads at desktop width because it is the argument; the three
   gates read as its caption. Below 900px they stack, figure first, for the
   same reason. */
.lmodel-wrap {
  display: grid; gap: 22px; align-items: start;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}
@media (max-width: 900px) { .lmodel-wrap { grid-template-columns: 1fr; } }
/* Its own ratio: this one is a section rather than a card topper, and 16/7
   would slice the quadrant labels off a 420x260 drawing. */
.lmodel-fig {
  aspect-ratio: 42 / 26; border: 1px solid var(--border);
  border-radius: var(--radius-lg); background: var(--surface-sunken);
}
.lmodel-gates { display: flex; flex-direction: column; gap: 14px; }
.lmodel-gate {
  padding: 14px 18px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2);
}
.lmodel-gate h3 {
  display: flex; align-items: baseline; gap: .6rem;
  margin: 0 0 .3rem; font-size: 19px; font-weight: 400;   /* see .lwhat-copy h3 */
  color: var(--head-ink, var(--text-primary));
}
.lmodel-n {
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 700;
  letter-spacing: .08em; color: var(--brand);
}
.lmodel-gate p { margin: 0; font-size: 13px; line-height: 1.6; color: var(--text-secondary); }
.lmodel-foot { margin: .2rem 0 0; line-height: 1.55; }

/* The figures draw themselves when they arrive.
   Every animated property is a transform, an opacity or a dash offset, so
   nothing here lays out a second time mid-animation. The `is-in` class is set
   once by an IntersectionObserver and never removed -- a figure that replays
   every time it scrolls past is a distraction rather than an entrance. */
.lwhat-fig [data-grow] {
  transform: scaleX(0); transform-origin: left center;
  transition: transform .75s var(--ease, cubic-bezier(.22,1,.36,1));
  transition-delay: var(--d, 0s);
}
.lwhat-fig.is-in [data-grow] { transform: scaleX(1); }
.lwhat-fig [data-rise] {
  opacity: 0; transform: translateY(6px);
  transition: opacity .5s ease, transform .5s var(--ease, cubic-bezier(.22,1,.36,1));
  transition-delay: var(--d, 0s);
}
.lwhat-fig.is-in [data-rise] { opacity: 1; transform: translateY(0); }
.lwhat-fig [data-draw] {
  stroke-dasharray: var(--len, 300); stroke-dashoffset: var(--len, 300);
  transition: stroke-dashoffset 1.1s var(--ease, cubic-bezier(.22,1,.36,1));
  transition-delay: var(--d, 0s);
}
.lwhat-fig.is-in [data-draw] { stroke-dashoffset: 0; }
@media (prefers-reduced-motion: reduce) {
  .lwhat-fig [data-grow], .lwhat-fig [data-rise], .lwhat-fig [data-draw] {
    transition: none; transform: none; opacity: 1; stroke-dashoffset: 0;
  }
}

.lplay {
  display: grid; gap: 28px;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  align-items: start;
}
.lplay-copy h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(24px, 3vw, 34px); line-height: 1.1; margin: .2rem 0 .6rem;
}
.lplay-copy p { color: var(--text-secondary); line-height: 1.6; }
.lplay-panel {
  padding: 18px 20px; border-radius: var(--radius-lg);
  border: 1px solid var(--border-strong); background: var(--surface-1);
  box-shadow: var(--shadow-lg);
}
/* The picker is written for a fixed popover; inside the playground it is a
   block in the flow, so the popover's own positioning must not apply. */
.lplay-panel.moodpicker,
.lplay-panel { position: static; }

.lcta { text-align: center; padding: 8px 0 24px; }
.lcta h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(26px, 4vw, 42px); line-height: 1.08; margin: 0 0 .5rem;
}
.lcta .btn { margin-top: 1rem; }
/* .small carries a measure; without auto margins the box sat at the left of a
   centred section and the line under the heading read as misplaced. */
.lcta p { margin-inline: auto; }
/* Display type needs a wider cushion than body text: its strokes are thin
   and far apart, and a 10px halo left candles showing between the letters. */
.landing :is(.lhero-copy, .lsection, .lplay-copy, .lcta) > :is(h1, h2) {
  text-shadow: 0 0 3px var(--plane), 0 0 12px var(--plane), 0 0 28px var(--plane),
               0 0 28px var(--plane);
}

/* The stage change borrows the view transition's keyframes so going in and
   coming back read as the same gesture as moving through the app. */
.stage-enter[data-view-dir="fwd"] {
  animation: ilViewInFwd var(--il-view-dur, .34s) cubic-bezier(.2, .85, .24, 1) both;
}
.stage-enter[data-view-dir="back"] {
  animation: ilViewInBack var(--il-view-dur, .34s) cubic-bezier(.2, .85, .24, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) .stage-enter { animation: none; }
}

@media (max-width: 640px) {
  .lmain { gap: 48px; }
  .lhero { padding-top: 20px; }
}

/* --- landing: the two engine-fed sections ------------------------------- */
.lsection { max-width: 74ch; }
.lsection h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(24px, 3vw, 34px); line-height: 1.1; margin: .2rem 0 .5rem;
}
.lsection .lsub { color: var(--text-secondary); line-height: 1.6; margin: 0 0 1.4rem; }
.lsection { max-width: none; }
.lsection > .eyebrow, .lsection > h2, .lsection > .lsub { max-width: 74ch; }

.lstatgrid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.lstat {
  display: flex; gap: 12px; padding: 18px 20px;
  border-radius: var(--radius-lg); border: 1px solid var(--border);
  background: var(--panel-solid, var(--surface-2));
}
.lstat-ic { flex: none; font-size: 18px; line-height: 1.3; color: var(--brand); }
.lstat b { font-size: 14px; color: var(--text-primary); }
.lstat-sub {
  font-family: "IBM Plex Mono", monospace; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-muted-on-panel, var(--text-muted));
  margin: .25rem 0 .4rem;
}
.lstat p { margin: 0; font-size: 13px; line-height: 1.6; color: var(--text-secondary); }

.lranks { display: flex; flex-direction: column; gap: 8px; }
.lrank {
  display: flex; align-items: flex-start; gap: 14px; padding: 14px 18px;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--panel-solid, var(--surface-2));
}
.lrank-l {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: var(--radius-sm); background: var(--surface-sunken);
  border: 1px solid var(--border-strong);
  font-family: "IBM Plex Mono", monospace; font-size: 15px; font-weight: 600;
  color: var(--head-ink, var(--text-primary));
}
.lrank b { font-size: 14px; color: var(--text-primary); }
.lrank-cut {
  margin-left: .5em; font-family: "IBM Plex Mono", monospace; font-size: 11px;
  color: var(--text-muted-on-panel, var(--text-muted));
}
.lrank p { margin: .2rem 0 0; font-size: 13px; line-height: 1.55; color: var(--text-secondary); }

/* The row's text column has to fill the row, or the track cannot be an axis.
   `.lrank` is a flex row and this div was sized by its own content, so each
   of the six tracks came out as wide as its blurb -- six bars at six
   different scales, which is worse than no bars at all because they invite a
   comparison they cannot support. `min-width: 0` alongside, so a long title
   shrinks rather than pushing the track wider than the row. */
.lrank > div { flex: 1 1 auto; min-width: 0; }

/* The share of the scale a rank owns, on one 0-100 track.
   The track is full width and the band is positioned inside it by percentage,
   so the six rows share one axis -- which is the whole point. Six bars that
   each start at their own left edge would be six bars; six bars on one axis
   are a ladder. */
.lrank-track {
  position: relative; height: 4px; margin-top: .6rem; border-radius: 999px;
  /* The unfilled part of the axis has to be visible or the band is floating
     rather than positioned -- on the darker moods `--surface-sunken` and the
     panel it sits on are within a shade of each other, and the 0-100 track
     disappeared entirely. The inset line is what makes it an axis. */
  background: var(--surface-sunken); overflow: hidden;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 70%, transparent);
}
.lrank-band {
  position: absolute; top: 0; bottom: 0; border-radius: 999px;
  background: var(--brand); opacity: .75;
  /* An animation rather than a transition, and deliberately so: a transition
     needs something to change it, which on this page would be a class set
     from rAF -- and rAF does not fire in a hidden tab, which is the bug
     tests/test_chart_animation_settles.py exists about. A keyframe runs
     without anyone driving it, and an element whose animation never runs sits
     at its ordinary computed style, which is the drawn state. The failure
     mode is "no animation", never "no bar". */
  transform-origin: left center;
  animation: lrankIn .7s var(--ease, cubic-bezier(.22,1,.36,1)) both;
}
@keyframes lrankIn { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.lrank:nth-child(1) .lrank-band { animation-delay: .04s; }
.lrank:nth-child(2) .lrank-band { animation-delay: .12s; }
.lrank:nth-child(3) .lrank-band { animation-delay: .20s; }
.lrank:nth-child(4) .lrank-band { animation-delay: .28s; }
.lrank:nth-child(5) .lrank-band { animation-delay: .36s; }
.lrank:nth-child(6) .lrank-band { animation-delay: .44s; }
@media (prefers-reduced-motion: reduce) { .lrank-band { animation: none; } }
/* The top of the ladder is rare, and the row says so without a second label:
   the higher the rank, the more of the brand colour its band carries. */
.lrank[data-letter="S"] .lrank-band { opacity: 1; }
.lrank[data-letter="A"] .lrank-band { opacity: .92; }
.lrank[data-letter="E"] .lrank-band,
.lrank[data-letter="D"] .lrank-band { opacity: .5; }

/* --- landing: the live screener demo ------------------------------------
   Deliberately built from the same parts as .lrank above rather than as a
   table: the landing page has one row idiom and a second one would read as a
   different product. The numbers are mono and tabular for the reason every
   figure in this app is -- Instrument Serif cannot show a digit. */
.lrun-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 1.1rem;
}
.lrun-bar .small { max-width: 46ch; line-height: 1.5; }
.lrun-rows { display: flex; flex-direction: column; gap: 8px; }
.lrun-row {
  display: flex; align-items: center; gap: 14px; padding: 13px 18px;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--panel-solid, var(--surface-2));
}
/* A knockout is an exclusion, not a low score, so it is marked rather than
   sorted away -- a screen that hides its failures is an advertisement. */
.lrun-row.is-ko { border-left: 2px solid var(--critical); }
.lrun-rank {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: var(--radius-sm); background: var(--surface-sunken);
  border: 1px solid var(--border-strong);
  font-family: "IBM Plex Mono", monospace; font-size: 15px; font-weight: 600;
  color: var(--head-ink, var(--text-primary));
}
.lrun-main { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.lrun-main b { font-family: "IBM Plex Mono", monospace; font-size: 13px; }
/* inline-block, not inline: text-overflow needs a box to clip against, and an
   inline element does not have one -- the ellipsis silently does nothing and a
   long company name pushes the numbers off the row instead. */
.lrun-name {
  display: inline-block; max-width: 100%; vertical-align: bottom;
  color: var(--text-secondary); font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lrun-sub {
  display: flex; gap: 10px; flex-wrap: wrap; margin-top: .25rem;
  font-size: 11px; color: var(--text-muted-on-panel, var(--text-muted));
}
.lrun-class { color: var(--brand); }
.lrun-nums {
  flex: none; display: flex; align-items: baseline; gap: 16px;
  font-family: "IBM Plex Mono", monospace; font-variant-numeric: tabular-nums;
}
.lrun-power { font-size: 20px; font-weight: 600; color: var(--text-primary); }
.lrun-mos { font-size: 13px; color: var(--text-secondary); min-width: 5ch; text-align: right; }
.lrun-mos i { font-style: normal; font-size: 11px; }
.lrun-foot { margin-top: .9rem; max-width: 78ch; line-height: 1.6; }

@media (max-width: 560px) {
  /* The name is the first thing to go; the ticker, the rank and the two
     numbers are what the row is for. */
  .lrun-name { display: none; }
  .lrun-nums { gap: 11px; }
  .lrun-power { font-size: 17px; }
}

/* --- landing: the roster ------------------------------------------------ */
.lclasses {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.lclass {
  display: flex; gap: 11px; padding: 15px 17px;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--panel-solid, var(--surface-2));
  /* The engine hands each class a colour name; it marks the left edge rather
     than tinting the card, so fifteen of them never turn the page into a
     paint chart. */
  border-left: 2px solid var(--border-strong);
}
.lclass.c-amber    { border-left-color: var(--gold); }
.lclass.c-critical { border-left-color: var(--critical); }
.lclass.c-good     { border-left-color: var(--good); }
.lclass.c-brand    { border-left-color: var(--brand); }
.lclass-ic { flex: none; font-size: 16px; line-height: 1.4; color: var(--text-secondary); }
.lclass b { font-size: 13.5px; color: var(--text-primary); }
.lclass p {
  margin: .2rem 0 0; font-size: 12.5px; line-height: 1.55;
  color: var(--text-secondary);
}
#landingClassMore { margin-top: 1rem; }

/* --- landing: the value-investing primer -------------------------------- */
.lterms {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
}
.lterm {
  padding: 20px 22px; border-radius: var(--radius-lg);
  border: 1px solid var(--border); background: var(--panel-solid, var(--surface-2));
  /* A definition list, so the term leads and the prose follows — the accent
     edge marks it as a term rather than another feature card. */
  border-left: 2px solid color-mix(in oklab, var(--brand) 55%, transparent);
}
.lterm h3 {
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--brand-on-panel, var(--brand)); margin: 0 0 .5rem;
}
.lterm p { margin: 0; font-size: 13.5px; line-height: 1.65; color: var(--text-secondary); }

/* The account gate.
   ---------------------------------------------------------------------------
   Frosted, not blacked out. Chris: "I dont want the whole background to be
   black but more like blurry". The point is that you can still SEE the
   dashboard you were just looking at — that is the argument for signing up, and
   painting over it with 70% black throws the argument away. Every paywall worth
   copying does this: the content stays visible and goes soft.

   The veil is the app's own surface token rather than #000, so it frosts to
   parchment in the light moods and to ink in the dark ones instead of going
   grey in both. */
#authGate {
  /* Dark, and blurred. Chris asked for "more like blurry" and then, when it
     came back pale over a bright page, that the dark one had been better —
     both are true. Flat 70% black threw the room away; a pale wash let the
     page shout through the card. This is dark glass: the landing page stays
     legible underneath as depth rather than as content competing with the
     form on top of it. Tinted with the mood's own plane so it reads as this
     app's dark rather than a generic scrim. */
  background: color-mix(in oklab, var(--plane) 18%, rgba(0, 0, 0, .74));
  backdrop-filter: blur(16px) saturate(.9);
  -webkit-backdrop-filter: blur(16px) saturate(.9);
}
/* The page behind must not scroll — a gate you can scroll past is a
   decoration. Set by app.js::paintAuthGate(). */
html.gated, html.gated body { overflow: hidden; }
.card.onboard-modal.authgate {
  width: min(430px, 100%);
  /* A real card against a blurred page, rather than a second sheet of fog. */
  box-shadow: 0 24px 70px -18px color-mix(in oklab, #000 55%, transparent);
}

/* Who you are and how your book is doing, in the dashboard's first panel head.
   A link rather than a readout: every number on it is a reason to open the
   portfolio page, so it goes there. */
.dashchip {
  display: inline-flex; align-items: baseline; gap: 8px; text-decoration: none;
  padding: 3px 10px; margin-right: 8px; border-radius: var(--radius-xs);
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); font-size: 12px; line-height: 1.5;
}
.dashchip:hover { border-color: var(--brand); color: var(--text-primary); }
.dashchip b { color: var(--text-primary); font-weight: 600; }
.dashchip-n { color: var(--brand); font-weight: 600; }

/* Creating a character needs the room the two pickers want; signing in is four
   lines and should stay four lines wide. */
.card.onboard-modal.authgate.creating { width: min(620px, 100%); }
.gatepicker { margin: .2rem 0 .9rem; }
.gatepicker h4 { margin: .8rem 0 0; font-size: 12px; letter-spacing: .04em; }
/* The picker is authored for a settings panel; inside the gate it is one
   choice among four fields, so its prose steps back to a caption. */
.gatepicker > p.small { font-size: 11.5px; line-height: 1.45; }

/* The starting class, under the mood picker during character creation. A quiet
   card rather than a banner: it is flavour attached to a colour choice, and
   dressing it up like an achievement would undercut the class you actually
   earn from what you own. */
.startclass {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 12px; margin: 0 0 .9rem;
  border: 1px solid var(--border); border-radius: var(--radius-xs);
  background: var(--surface-1);
}
.startclass[hidden] { display: none; }
.startclass b { color: var(--brand); }
.startclass-ic { font-size: 19px; line-height: 1.25; color: var(--brand); }

/* Signed out, the profile button is a sign-in button — wider than two initials
   and labelled, because a circle with someone else's monogram in it is not an
   invitation. */
#profileBtn.signin {
  width: auto; padding: 0 12px; border-radius: var(--radius-xs);
  font-size: 12px; font-weight: 600; letter-spacing: .01em;
  background: var(--brand); color: var(--surface-1); border-color: var(--brand);
}

/* Overlay toggles above the price chart. Same grammar as the range buttons
   below them — these choose what is drawn, those choose how far back. */
.priceovl { display: flex; gap: 4px; margin: 0 0 .5rem; flex-wrap: wrap; }
.priceovl button {
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px; letter-spacing: .03em;
  padding: 2px 9px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: transparent; color: var(--text-muted);
}
.priceovl button:hover { color: var(--text-primary); border-color: var(--border-strong); }
.priceovl button[aria-pressed="true"] {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in oklab, var(--brand) 10%, transparent);
}

/* Add-to-portfolio and add-to-watchlist, inside a search hit. Small and on the
   right so the row still reads as a name first and a pair of actions second. */
.hitadd { display: inline-flex; gap: 4px; margin-left: auto; }
.hitadd button {
  width: 22px; height: 22px; padding: 0; line-height: 1; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-xs);
  background: var(--surface-1); color: var(--text-secondary); font-size: 12px;
}
.hitadd button:hover { border-color: var(--brand); color: var(--brand); }
.hitadd button:disabled { opacity: .55; cursor: default; }

/* A macro series, full size. Wider than the sign-in card because the whole
   point is room: a 68px sparkline says "this went up" and nothing else. */
.card.onboard-modal.macrofocus { width: min(1040px, 100%); }
#macroFocus { background: color-mix(in oklab, var(--plane) 18%, rgba(0,0,0,.74));
  backdrop-filter: blur(14px) saturate(.92); -webkit-backdrop-filter: blur(14px) saturate(.92); }
.mcompare { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--border); }
.mcompare .shapeopt { margin: 0 4px 4px 0; }
/* A tile you can open should look like one. */
.mtile[data-macro], .mrow[data-macro] { cursor: pointer; }
.mtile[data-macro]:hover, .mrow[data-macro]:hover {
  border-color: var(--brand);
  background: color-mix(in oklab, var(--brand) 6%, transparent);
}
.mtile[data-macro]:focus-visible, .mrow[data-macro]:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* One figure, opened. Narrower than the macro window: this is a paragraph and
   three numbers, not a chart wanting room. */
.card.onboard-modal.figurewin { width: min(460px, 100%); }
#figureWin { background: color-mix(in oklab, var(--plane) 18%, rgba(0,0,0,.72));
  backdrop-filter: blur(14px) saturate(.92); -webkit-backdrop-filter: blur(14px) saturate(.92); }
.figrow { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; padding: 5px 0; border-bottom: 1px solid var(--grid); }
.figrow:last-of-type { border-bottom: 0; }
.figbig { font-size: 19px; color: var(--brand); }
.figbar { height: 5px; border-radius: 3px; margin-top: .5rem;
  background: color-mix(in oklab, var(--brand) 14%, transparent); overflow: hidden; }
.figbar i { display: block; height: 100%; background: var(--brand); }
/* A row you can open should look like one. */
.fundrow[data-fig] { cursor: pointer; border-radius: var(--radius-xs); }
.fundrow[data-fig]:hover { background: color-mix(in oklab, var(--brand) 7%, transparent); }
.fundrow[data-fig]:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* How the compared macro series actually relate. Sits between the chart and the
   picker, because it is the conclusion the chart is evidence for. */
.mstats { margin-top: .8rem; padding-top: .7rem; border-top: 1px solid var(--border); }

/* The checks — the company as ticks and crosses, scannable in three seconds. */
.chklist { display: flex; flex-direction: column; gap: 2px; }
.chkgroup > summary, .chkrow {
  display: grid; grid-template-columns: 18px minmax(0,1fr) auto auto;
  gap: 10px; align-items: baseline; padding: 6px 8px;
  border-radius: var(--radius-xs);
}
.chkgroup > summary { cursor: pointer; list-style: none; font-weight: 600; }
.chkgroup > summary::-webkit-details-marker { display: none; }
.chkgroup > summary:hover { background: var(--surface-1); }
.chkrow { cursor: pointer; padding-left: 26px; }
.chkrow:hover { background: color-mix(in oklab, var(--brand) 7%, transparent); }
.chkrow:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.chk-i { font-size: 12px; line-height: 1.4; text-align: center; }
.chk-ok { color: var(--success-text, var(--good)); }
.chk-no { color: var(--critical); }
.chk-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chk-t { white-space: nowrap; }
/* The six fatal tests, above everything. A fired knockout is not one failed
   check among nine, it ends the assessment. */
.chkko { display: flex; gap: 10px; align-items: flex-start; margin: 0 0 .8rem;
  padding: 10px 12px; border-radius: var(--radius-xs);
  border: 1px solid var(--border); background: var(--surface-1); }
.chkko.bad { border-color: var(--critical);
  background: color-mix(in oklab, var(--critical) 8%, transparent); }

/* The dividend. Coverage leads, not yield — a high yield is more often a
   falling price than a generous board. */
.divverdict { padding: 9px 12px; border-radius: var(--radius-xs); margin: 0 0 .8rem;
  border: 1px solid var(--border); background: var(--surface-1); font-size: 13px; }
.divverdict.bad { border-color: var(--critical);
  background: color-mix(in oklab, var(--critical) 8%, transparent); }
.divverdict.warn { border-color: var(--gold);
  background: color-mix(in oklab, var(--gold) 9%, transparent); }
.divstats { display: grid; gap: 10px 18px;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); }
.divstat-v { font-size: 18px; color: var(--brand); line-height: 1.25; }

/* Named peers, ranked. A percentile is a fact about a distribution; a name is a
   company you can click. */
.peerlist { display: flex; flex-direction: column; gap: 1px; }
.peerrow { display: grid; gap: 10px; align-items: center; cursor: pointer;
  grid-template-columns: 62px minmax(0,1fr) 90px 34px 46px;
  padding: 5px 8px; border-radius: var(--radius-xs); }
.peerrow:hover { background: color-mix(in oklab, var(--brand) 7%, transparent); }
.peerrow:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.peerrow.me { background: color-mix(in oklab, var(--brand) 11%, transparent);
  cursor: default; font-weight: 600; }
.peer-n { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12.5px; color: var(--text-secondary); }
.peer-bar { height: 6px; border-radius: 3px; overflow: hidden;
  background: color-mix(in oklab, var(--brand) 13%, transparent); }
.peer-bar i { display: block; height: 100%; background: var(--brand); }
.peer-s { text-align: right; }
.peer-m { text-align: right; }
@media (max-width: 720px) {
  .peerrow { grid-template-columns: 58px minmax(0,1fr) 34px; }
  .peer-bar, .peer-m { display: none; }
}

/* If rates move. The bar is signed from the centre of its own track, so a rise
   and a fall of the same size read as mirror images rather than as two numbers. */
.ratelist { display: flex; flex-direction: column; gap: 2px; }
.raterow { display: grid; gap: 10px; align-items: center;
  grid-template-columns: 56px 92px minmax(60px,1fr) 60px 56px;
  padding: 4px 8px; border-radius: var(--radius-xs); }
.raterow:hover { background: var(--surface-1); }
.rate-bar { height: 6px; border-radius: 3px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 16%, transparent); }
.rate-bar i { display: block; height: 100%; }
.rate-bar.up i { background: var(--good); }
.rate-bar.down i { background: var(--critical); }
.rate-v, .rate-iv { text-align: right; }
@media (max-width: 720px) {
  .raterow { grid-template-columns: 52px minmax(50px,1fr) 58px; }
  .rate-r, .rate-iv { display: none; }
}

/* The book's rate exposure headline. The weighted number is the panel's
   answer; the rows below it are the working. */
.pf-rate-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0 0 .7rem; }

/* The company-page version of this panel lives in a narrow card, where a 1fr
   bar column is a gauge. The portfolio's version is span-12, where the same
   rule stretches it across 1100px and two bars 86% apart look identical. Cap
   the track and let a trailing spacer eat the slack. */
#pfRates .raterow { grid-template-columns: 64px 96px minmax(80px, 240px) 64px 64px 1fr; }
@media (max-width: 720px) {
  #pfRates .raterow { grid-template-columns: 56px minmax(60px,1fr) 60px; }
}

/* ---- Where today sits (macro percentiles) --------------------------------
   The band is the full 25-year range; the pale block inside it the middle 80%;
   the tick the median; the marker today. Drawn rather than described, because a
   percentile is a number most people cannot feel and a marker at the far edge
   of a band is one anybody can read at a glance. */
.mhlist { display: flex; flex-direction: column; gap: 4px; }
.mhrow { display: grid; gap: 12px; align-items: center;
  grid-template-columns: 150px minmax(120px, 1fr) 72px 48px;
  padding: 5px 8px; border-radius: var(--radius-xs); }
.mhrow:hover { background: var(--surface-1); }
.mh-label { font-size: .86rem; }
.mh-band { position: relative; height: 12px; border-radius: 6px;
  background: color-mix(in oklab, var(--text-muted) 12%, transparent); }
.mh-fill { position: absolute; top: 0; bottom: 0; width: 0; border-radius: 6px;
  background: color-mix(in oklab, var(--brand) 26%, transparent);
  transition: width .7s cubic-bezier(.22,.9,.3,1); }
.mh-med { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px;
  background: color-mix(in oklab, var(--text-muted) 60%, transparent); }
.mh-now { position: absolute; top: -4px; bottom: -4px; left: 0; width: 4px; margin-left: -2px;
  border-radius: 2px; background: var(--brand-deep);
  box-shadow: 0 0 0 2px var(--surface-1);
  transition: left .8s cubic-bezier(.22,.9,.3,1); }
.mh-val { text-align: right; font-size: .9rem; }
.mh-pct { text-align: right; font-variant-numeric: tabular-nums; }
.mh-pct[data-band="extreme high"], .mh-pct[data-band="high"] { color: var(--critical); }
.mh-pct[data-band="extreme low"], .mh-pct[data-band="low"] { color: var(--brand-deep); }
.mh-band[data-band="extreme high"] .mh-fill,
.mh-band[data-band="extreme low"] .mh-fill {
  background: color-mix(in oklab, var(--brand) 34%, transparent); }
.mhkey { display: flex; gap: 14px; margin-top: .7rem; flex-wrap: wrap; }
.mhkey span { display: inline-flex; align-items: center; gap: 5px; }
.mhkey i { display: inline-block; width: 14px; height: 8px; border-radius: 3px; }
.mhkey .k-fill { background: color-mix(in oklab, var(--brand) 26%, transparent); }
.mhkey .k-med { width: 2px; height: 12px; background: color-mix(in oklab, var(--text-muted) 60%, transparent); }
.mhkey .k-now { width: 4px; height: 12px; background: var(--brand-deep); }

/* ---- When it last looked like this (analogues) --------------------------- */
.mheplist { display: flex; flex-direction: column; gap: 3px; }
.mhep { display: grid; gap: 12px; align-items: center;
  grid-template-columns: 170px 150px repeat(4, minmax(48px, 1fr));
  padding: 5px 8px; border-radius: var(--radius-xs); }
.mhep-head { padding-bottom: 2px; }
.mhep-now { background: color-mix(in oklab, var(--brand) 9%, transparent);
  margin-bottom: 6px; }
.mheplist .mhep:hover { background: var(--surface-1); }
.mh-cell { text-align: right; font-size: .84rem; }
.mh-sim { display: inline-flex; align-items: center; gap: 7px; }
.mh-simbar { display: block; flex: 1; height: 6px; border-radius: 3px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.mh-simbar b { display: block; height: 100%; width: 0; background: var(--brand);
  transition: width .8s cubic-bezier(.22,.9,.3,1); }

@media (max-width: 820px) {
  .mhrow { grid-template-columns: 110px minmax(70px, 1fr) 62px 42px; gap: 8px; }
  .mhep { grid-template-columns: 130px 100px repeat(4, minmax(38px, 1fr)); gap: 7px; }
  .mh-cell, .mh-label { font-size: .78rem; }
}

/* ---- The Buy button and the trade ticket ---------------------------------
   Chris: "you have to make it much much easier to buy shares, its supposed to
   be a trading simulation game... I dont even fing get where i am supposed to
   buy shares." The button is deliberately the loudest control on the company
   page: it is the one action the whole thing is named for. */
.buybar { display: flex; align-items: center; gap: .8rem; margin: 0 0 1rem;
  padding: .7rem .9rem; border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--brand) 7%, transparent); }
.buybar-co { display: flex; flex-direction: column; gap: .1rem; margin-right: auto; }
.buybar-co b { font-family: var(--font-display); font-size: 1.05rem; }
@media (max-width: 560px) {
  .buybar { flex-wrap: wrap; }
  .buybar-note { width: 100%; }
}
.buybar-note { opacity: .75; }
.buybtn { display: inline-flex; align-items: center; gap: .35rem;
  padding-inline: .8rem; font-weight: 600; letter-spacing: .01em; }
.buybtn-i { font-size: 1.05em; line-height: 1; opacity: .9; }
.buybtn:hover { transform: translateY(-1px); }
.buybtn:active { transform: translateY(0); }

.tradewin { max-width: 460px; }
.tw-top { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; margin: .2rem 0 .9rem; }
.tw-co { font-family: var(--font-display); font-size: 1.25rem; line-height: 1.15; }
.tw-held { margin: 0 0 .9rem; padding: .5rem .7rem; border-radius: var(--radius-xs);
  background: color-mix(in oklab, var(--brand) 10%, transparent); }
.tw-lab { display: block; font-family: var(--font-mono); font-size: .72rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
  margin: .9rem 0 .35rem; }
.tw-amt { display: flex; align-items: center; gap: .4rem;
  border: 1px solid var(--border); border-radius: var(--radius-xs); padding: .1rem .7rem; }
.tw-amt:focus-within { border-color: var(--brand); }
.tw-cur { font-family: var(--font-mono); color: var(--text-muted); }
.tw-amt input { flex: 1; min-width: 0; border: 0; background: none; color: var(--text-primary);
  font-family: var(--font-mono); font-size: 1.35rem; padding: .45rem 0; width: 100%; }
.tw-amt input:focus { outline: none; }
.tw-quick { display: flex; gap: .4rem; margin-top: .5rem; flex-wrap: wrap; }
.tw-shares { margin: .55rem 0 0; min-height: 1.1em; }
#tradeThesis { width: 100%; padding: .5rem .7rem; border: 1px solid var(--border);
  border-radius: var(--radius-xs); background: none; color: var(--text-primary); font: inherit; }
#tradeThesis:focus { outline: none; border-color: var(--brand); }
.tw-go { display: flex; gap: .5rem; margin-top: 1.1rem; }
.tw-go .btn { flex: 1; justify-content: center; }
@media (max-width: 480px) {
  .tw-top { flex-direction: column; gap: .5rem; }
}

/* ---- Your run, on the Status page ---------------------------------------
   Chris: "I should be able to go to status and have a portfolio there where i
   can get an overview and can invest the resources i have! For that there
   should be a button." */
.rb-head { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap;
  margin: 0 0 .9rem; }
.rb-eq { display: flex; align-items: baseline; gap: .5rem; }
.rb-eq b { font-size: 1.7rem; }
.rb-act { margin-left: auto; display: flex; gap: .4rem; align-items: center; }
.rb-list { display: flex; flex-direction: column; gap: 2px; }
.rb-row { display: grid; gap: 12px; align-items: center;
  grid-template-columns: 72px minmax(80px, 240px) 44px 88px 68px auto 1fr;
  padding: 5px 8px; border-radius: var(--radius-xs); cursor: pointer; }
/* The Sell button appears on hover and on keyboard focus. Always-on it would
   put a disposal control on every line of a page people scan, which is a
   different kind of easy from the one being asked for. */
.rb-act-cell { opacity: 0; transition: opacity .15s ease; }
.rb-row:hover .rb-act-cell,
.rb-row:focus-within .rb-act-cell { opacity: 1; }
@media (hover: none) { .rb-act-cell { opacity: 1; } }
.sellbtn { color: var(--text-secondary);
  border-color: color-mix(in oklab, var(--critical) 38%, transparent); }
.sellbtn:hover { color: var(--critical);
  background: color-mix(in oklab, var(--critical) 12%, transparent); }
.rb-row:hover { background: var(--surface-1); }
.rb-w { height: 7px; border-radius: 4px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.rb-w i { display: block; height: 100%; background: var(--brand); }
.rb-s, .rb-v, .rb-p { text-align: right; }
@media (max-width: 720px) {
  .rb-row { grid-template-columns: 62px minmax(50px,1fr) 62px 60px auto; }
  .rb-s { display: none; }
  .rb-act { margin-left: 0; width: 100%; }
}

/* Buy, from the search box. Wider than the + and ★ beside it because it is a
   word rather than a glyph, and louder because it is the one that spends. */
.hitadd .hitbuy { width: auto; padding-inline: .5rem; font-size: .72rem;
  font-weight: 600; letter-spacing: .02em;
  color: var(--brand-deep);
  border-color: color-mix(in oklab, var(--brand) 45%, transparent); }
.hitadd .hitbuy:hover { background: color-mix(in oklab, var(--brand) 16%, transparent); }

/* The framework's own size, offered on the ticket. Offered, never imposed: the
   button fills the box, it does not buy. */
.tz { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin: .7rem 0 0; padding: .5rem .6rem; border-radius: var(--radius-xs);
  background: color-mix(in oklab, var(--brand) 10%, transparent); }
.tz-take { font-weight: 600; }
.tz-no { display: block; background: color-mix(in oklab, var(--critical) 10%, transparent); }
.tz-no b { display: block; margin-bottom: .15rem; }
.tz-warn { margin: .5rem 0 0; color: var(--text-secondary); }

/* How sure you are, on the ticket. It sits with the suggested size because it
   is an input to it, not a field to fill in. */
.tz-conf { display: inline-flex; align-items: center; gap: .4rem; margin-left: auto; }
/* A compact select still needs its chevron to stand clear of the value. */
.tz-conf select { width: auto; max-width: 9rem; padding: .2rem 26px .2rem .4rem;
  font-size: .8rem; background-position: right 8px center; background-size: 9px 6px; }
.tz-no .tz-conf { margin: .6rem 0 0; }

/* Buy on a screener card: pinned to the bottom so every card in the grid has it
   on the same line regardless of how long the company name wrapped. */
.ccard { position: relative; }
.ccard-act { margin-top: .6rem; display: flex; justify-content: flex-end; }

/* ---- What your rules say -------------------------------------------------
   The discipline panel. Three kinds of row, visually ranked by how much
   authority they carry: your own fired rule first, the model's change of mind
   second, what you never wrote at all last and quietest. */
.ra-group + .ra-group { margin-top: 1rem; }
.ra-h { font-family: var(--font-mono); font-size: .72rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted); margin: 0 0 .5rem; font-weight: 500; }
.ra-row { display: flex; align-items: flex-start; gap: 1rem;
  padding: .7rem .85rem; border-radius: var(--radius-xs); margin-bottom: 4px;
  border-left: 3px solid var(--border); background: var(--surface-1); }
.ra-row[data-sev="action"] { border-left-color: var(--critical);
  background: color-mix(in oklab, var(--critical) 8%, var(--surface-1)); }
.ra-row[data-sev="warn"] { border-left-color: var(--gold, var(--brand));
  background: color-mix(in oklab, var(--brand) 7%, var(--surface-1)); }
.ra-main { flex: 1; min-width: 0; }
.ra-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .25rem; }
.ra-tag { font-family: var(--font-mono); font-size: .68rem; letter-spacing: .05em;
  text-transform: uppercase; padding: .1rem .4rem; border-radius: var(--radius-xs);
  border: 1px solid var(--border-strong); color: var(--text-secondary); }
.ra-tag[data-sev="action"] { color: var(--critical);
  border-color: color-mix(in oklab, var(--critical) 45%, transparent); }
.ra-text { margin: 0; }
/* Your own words, quoted back. Set apart because that is the whole point of
   keeping them. */
.ra-pm { margin: .45rem 0 0; padding-left: .7rem;
  border-left: 2px solid color-mix(in oklab, var(--text-muted) 35%, transparent);
  color: var(--text-secondary); font-style: italic; }
.ra-act { flex: 0 0 auto; }
.ra-cov { margin: 1rem 0 0; padding-top: .8rem; border-top: 1px solid var(--border);
  color: var(--text-secondary); }
@media (max-width: 620px) {
  .ra-row { flex-direction: column; gap: .6rem; }
  .ra-act { align-self: flex-start; }
}

/* ---- Watchlist ----------------------------------------------------------
   The page answers one question — how far is this from the level I set — so
   the bar is the widest thing in the row and the gap is written in the unit
   the level was written in. */
.wl-list { display: flex; flex-direction: column; gap: 4px; }
.wl-row { display: grid; gap: 1rem; align-items: center; cursor: pointer;
  grid-template-columns: minmax(130px, 200px) minmax(140px, 1fr) minmax(175px, auto) auto;
  padding: .6rem .8rem; border-radius: var(--radius-xs);
  border-left: 3px solid transparent; }
.wl-row:hover { background: var(--surface-1); }
.wl-row.ready { border-left-color: var(--good);
  background: color-mix(in oklab, var(--good) 9%, transparent); }
.wl-id { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.wl-id span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-mid { min-width: 0; }
.wl-track { height: 7px; border-radius: 4px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.wl-track i { display: block; height: 100%; background: var(--brand); }
.wl-row.ready .wl-track i { background: var(--good); }
.wl-gap { margin-top: .3rem; }
.wl-nums { text-align: right; }
.wl-nums .small { white-space: nowrap; }
.wl-act { display: flex; gap: .35rem; align-items: center; }
.wl-drop { opacity: 0; transition: opacity .15s ease; padding-inline: .5rem; }
.wl-row:hover .wl-drop, .wl-row:focus-within .wl-drop { opacity: 1; }
@media (hover: none) { .wl-drop { opacity: 1; } }
.wl-note { grid-column: 1 / -1; margin: .3rem 0 0; font-style: italic; }
@media (max-width: 760px) {
  .wl-row { grid-template-columns: 1fr auto; gap: .5rem; }
  .wl-mid { grid-column: 1 / -1; }
  .wl-nums { text-align: left; }
}

/* A company you own must not look like one you have never bought. */
.calown { font-family: var(--font-mono); font-size: .66rem; letter-spacing: .06em;
  text-transform: uppercase; padding: .05rem .35rem; border-radius: var(--radius-xs);
  border: 1px solid var(--border-strong); color: var(--text-secondary); }
.calown[data-own="held"] { color: var(--brand-deep);
  border-color: color-mix(in oklab, var(--brand) 50%, transparent);
  background: color-mix(in oklab, var(--brand) 12%, transparent); }
.calrow.mine { border-left: 3px solid color-mix(in oklab, var(--brand) 55%, transparent); }
/* On a phone the row's four columns -- days, name and date, verdict,
   freshness -- left the verdict a column too narrow for its own label: "Watch
   -- good business, wrong price" wrapped to five lines and its pill closed
   into an oval with the words inside, and the name beside it broke a word a
   line ("KO The / Coca- / Cola"). Two lines instead, as the Overview's small
   calendar card already does: name, date and freshness on the first, the
   verdict across the width under them. */
@media (max-width: 600px) {
  .calrow { grid-template-columns: 3rem minmax(0, 1fr) auto; row-gap: .4rem; align-items: start; }
  .calrow > .days { grid-row: 1 / span 2; align-self: center; }
  .calrow > div:nth-child(2) { grid-column: 2; grid-row: 1; min-width: 0; }
  .calrow > div:nth-child(3) { grid-column: 2 / span 2; grid-row: 2; min-width: 0; }
  .calrow > div:nth-child(4) { grid-column: 3; grid-row: 1; justify-self: end; }
  /* With the whole width it fits on one line; the longest label ("Quality
     20/100 -- worth less than its debt") takes two, still inside the row. */
  .calrow .verdict { white-space: normal; max-width: 100%; }
}

/* ---- Holdings editor ---------------------------------------------------- */
.hold-head, .hold-row { display: grid; gap: .7rem; align-items: center;
  grid-template-columns: minmax(110px, 230px) 120px 150px 1fr; padding: .35rem .5rem; }
.hold-head { padding-bottom: .1rem; }
.hold-head .num, .hold-row .num { text-align: right; }
.hold-list { display: flex; flex-direction: column; gap: 2px; }
.hold-row { border-radius: var(--radius-xs); }
.hold-row:hover { background: var(--surface-1); }
.hold-in { padding: .3rem .5rem; border: 1px solid transparent; background: none;
  color: var(--text-primary); font-family: var(--font-mono); font-size: .88rem;
  border-radius: var(--radius-xs); width: 100%; }
.hold-in:hover { border-color: var(--border); }
.hold-in:focus { outline: none; border-color: var(--brand); background: var(--surface-1); }
.hold-act { display: flex; gap: .25rem; opacity: 0; transition: opacity .15s ease; }
.hold-row:hover .hold-act, .hold-row:focus-within .hold-act { opacity: 1; }
@media (hover: none) { .hold-act { opacity: 1; } }
.hold-del:hover { color: var(--critical);
  border-color: color-mix(in oklab, var(--critical) 45%, transparent); }
.hold-add { margin-top: .9rem; align-items: center; }
.hold-bulk { margin-top: 1rem; }
.hold-bulk summary { cursor: pointer; }
@media (max-width: 620px) {
  .hold-head { display: none; }
  .hold-row { grid-template-columns: 1fr 1fr auto; }
}

/* ---- A number you can ask about ------------------------------------------
   The affordance has to be visible without being loud: these sit eight to a
   grid and eight blinking question marks would be worse than none. */
.stat-ask { cursor: pointer; text-align: left; width: 100%;
  background: none; border: 1px solid transparent; font: inherit; color: inherit;
  border-radius: var(--radius-xs); padding: .5rem .6rem; margin: -.5rem -.6rem;
  transition: border-color .15s ease, background .15s ease; }
.stat-ask:hover, .stat-ask:focus-visible {
  border-color: color-mix(in oklab, var(--brand) 40%, transparent);
  background: color-mix(in oklab, var(--brand) 7%, transparent); }
.stat-q { display: inline-block; margin-left: .35rem; font-size: .8em;
  opacity: .45; transition: opacity .15s ease; }
.stat-ask:hover .stat-q, .stat-ask:focus-visible .stat-q { opacity: 1; color: var(--brand-deep); }

/* The explanation itself, in the window the fundamentals rows already use. */
.term-what { font-size: 1.02rem; line-height: 1.5; margin: .2rem 0 1rem; }
.term-row { margin: 0 0 .9rem; }
.term-row p { margin: .2rem 0 0; }
.term-k { display: block; font-family: var(--font-mono); font-size: .7rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
.term-watch { padding: .7rem .8rem; border-radius: var(--radius-xs);
  background: color-mix(in oklab, var(--brand) 8%, transparent); margin-bottom: 0; }

/* ---- 52-week range ------------------------------------------------------
   Context, not a signal. Under the chart, never beside the verdict: "near the
   low" is the most reliably misused fact in retail investing. */
.w52 { margin: .2rem 0 .9rem; }
.w52-head, .w52-ends { display: flex; justify-content: space-between; align-items: baseline; }
.w52-head { margin-bottom: .3rem; }
.w52-track { position: relative; height: 8px; border-radius: 4px;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.w52-fill { display: block; height: 100%; width: 0; border-radius: 4px;
  background: color-mix(in oklab, var(--brand) 40%, transparent); }
.w52-pin { position: absolute; top: -3px; bottom: -3px; width: 3px; margin-left: -1.5px;
  border-radius: 2px; background: var(--brand-deep); box-shadow: 0 0 0 2px var(--surface-1); }
.w52-ends { margin-top: .25rem; }

/* ---- Forensic breakdowns ------------------------------------------------ */
.fundrow-ask { cursor: pointer; }
.fundrow-ask:hover { background: color-mix(in oklab, var(--brand) 8%, transparent); }
.fundrow-ask .stat-q { opacity: .4; }
.fundrow-ask:hover .stat-q { opacity: 1; color: var(--brand-deep); }
.fx-list { display: flex; flex-direction: column; gap: 3px; margin-top: .3rem; }
.fx-row { display: flex; gap: .7rem; align-items: flex-start;
  padding: .5rem .6rem; border-radius: var(--radius-xs); background: var(--surface-1); }
.fx-row p { margin: .15rem 0 0; }
.fx-row[data-ok="0"] { background: color-mix(in oklab, var(--critical) 9%, var(--surface-1)); }
.fx-num { align-items: center; }
.fx-num > div:first-child { flex: 1; min-width: 0; }
.fx-bar { flex: 0 0 70px; height: 6px; border-radius: 3px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.fx-bar i { display: block; height: 100%; background: var(--brand); }

/* ---- Is this year normal? -----------------------------------------------
   The gap between the latest print and the company's own five-year norm. A
   wide positive gap is not bad news; it is why the valuation looks
   conservative against the last set of accounts. */
.ny-list { display: flex; flex-direction: column; gap: 3px; }
.ny-row { display: grid; gap: .8rem; align-items: center;
  grid-template-columns: 140px 72px minmax(70px, 1fr) minmax(150px, auto) 68px;
  padding: .4rem .6rem; border-radius: var(--radius-xs); }
.ny-row:hover { background: var(--surface-1); }
.ny-now, .ny-g { text-align: right; }
.ny-bar { height: 7px; border-radius: 4px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.ny-bar i { display: block; height: 100%; background: var(--text-muted); }
.ny-bar.over i { background: var(--good); }
.ny-bar.under i { background: var(--critical); }
.ny-n { text-align: right; white-space: nowrap; }
@media (max-width: 780px) {
  .ny-row { grid-template-columns: 1fr 70px 64px; }
  .ny-bar, .ny-n { display: none; }
}

/* ---- What the owner earns ----------------------------------------------- */
.oe-top { display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; }
.oe-big { font-size: 2rem; line-height: 1.1; }
.oe-side { text-align: right; }
.oe-side > div:first-child { font-size: 1.3rem; }
/* The capex split. One bar, two segments: the maintenance half is deducted
   from owner earnings and the growth half is not, which is the whole reason
   owner earnings and free cash flow disagree. */
.oe-split { display: flex; height: 30px; border-radius: var(--radius-xs); overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 12%, transparent); }
.oe-seg { display: flex; align-items: center; justify-content: center; min-width: 0;
  font-size: .72rem; letter-spacing: .04em; text-transform: uppercase;
  font-family: var(--font-mono); cursor: help; }
.oe-seg span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 .4rem; }
.oe-seg.maint { background: color-mix(in oklab, var(--critical) 40%, transparent); }
.oe-seg.growth { background: color-mix(in oklab, var(--good) 38%, transparent); }
.oe-legend { display: flex; justify-content: space-between; margin-top: .4rem; }
.oe-wc { margin-top: 1rem; padding: .7rem .8rem; border-radius: var(--radius-xs);
  background: color-mix(in oklab, var(--brand) 8%, transparent); }

/* ---- Insider summary ---------------------------------------------------- */
.ins-sum { margin: 0 0 .8rem; padding: .6rem .7rem; border-radius: var(--radius-xs);
  background: var(--surface-1); }
.ins-grid { display: flex; gap: 1.6rem; flex-wrap: wrap; }
.ins-grid > div { display: flex; flex-direction: column; gap: .1rem; }
.ins-grid b { font-size: 1.05rem; }

/* When a company reports in one currency and trades in another, the ticket has
   to say so — otherwise the price looks simply wrong to anyone who knows the
   stock. */
.tw-ccy { margin: .6rem 0 0; padding: .5rem .6rem; border-radius: var(--radius-xs);
  background: color-mix(in oklab, var(--brand) 9%, transparent); }

/* ---- Your position ------------------------------------------------------
   What you own of this company and how it has gone. Above the price, because
   what you already hold changes how you read what it costs today. */
.pos-top { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: .9rem; }
.pos-value { font-size: 2rem; line-height: 1.1; }
.pos-pnl { text-align: right; }
.pos-pnl-n { font-size: 1.5rem; line-height: 1.1; }
.pos-row { display: flex; gap: 1.8rem; flex-wrap: wrap;
  padding-top: .8rem; border-top: 1px solid var(--border); }
.pos-row > div { display: flex; flex-direction: column; gap: .1rem; }
.pos-row b { font-size: 1.02rem; }
/* Your own sentence, read back at the moment you are deciding again. */
.pos-thesis { margin: .9rem 0 0; padding-left: .8rem;
  border-left: 2px solid color-mix(in oklab, var(--brand) 45%, transparent);
  display: flex; flex-direction: column; gap: .15rem; font-style: italic; }
.pos-nothesis { margin: .9rem 0 0; }
@media (max-width: 560px) {
  .pos-pnl { text-align: left; }
  .pos-row { gap: 1.1rem; }
}

/* A valuation that worked can still be resting on something worth arguing with.
   The warnings used to appear only when there was no number at all, which is
   precisely when they matter least. */
.val-caveats { margin-top: .8rem; }
.val-caveats b { display: block; margin-bottom: .3rem; }
.val-caveats p { margin: .3rem 0 0; }

/* Strict mode's column: how many of the original eleven a company fails. */
.strictgap { font-family: var(--font-mono); }
.strictgap small { opacity: .55; margin-left: .15rem; }

/* The second Buy door, beside the score rather than above the page. */
.profile-act { display: flex; align-items: center; gap: .6rem; margin-top: .9rem;
  padding-top: .8rem; border-top: 1px solid var(--border); }

/* Yearly / Quarterly on the record card. The same segmented control the price
   range uses, because it does the same job. */
.histspan { display: inline-flex; gap: 2px; padding: 2px;
  border: 1px solid var(--border); border-radius: var(--radius-pill); }
.histspan button { padding: .15rem .6rem; border: 0; background: none; cursor: pointer;
  color: var(--text-muted); font-size: .76rem; border-radius: var(--radius-pill);
  font-family: var(--font-mono); letter-spacing: .02em; }
.histspan button[aria-pressed="true"] {
  background: color-mix(in oklab, var(--brand) 18%, transparent); color: var(--text-primary); }
.histspan button:hover { color: var(--text-primary); }
.histspan-note { margin: 0 0 .8rem; }

/* ---- What this does ------------------------------------------------------
   The newcomer panel: four real companies, each with its shape and the one
   comparison the platform exists to make. Shown only when there is nothing of
   the reader's own to look at yet. */
.sc-grid { display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.sc-card { display: flex; gap: .8rem; align-items: center; cursor: pointer;
  padding: .8rem; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1);
  transition: border-color var(--fast) var(--ease), transform var(--fast) var(--ease); }
.sc-card:hover, .sc-card:focus-visible {
  border-color: color-mix(in oklab, var(--brand) 55%, transparent);
  transform: translateY(-2px); outline: none; }
.sc-flake { flex: 0 0 92px; }
.sc-body { min-width: 0; flex: 1; }
.sc-top { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; }
.sc-score { font-size: 1.15rem; }
.sc-score small { opacity: .5; font-size: .7em; }
.sc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sc-value { display: flex; align-items: baseline; gap: .3rem; margin-top: .4rem; flex-wrap: wrap; }
.sc-verdict { margin-top: .3rem; font-size: .78rem; font-family: var(--font-mono); }
.sc-verdict.under { color: var(--good); }
.sc-verdict.over { color: var(--critical); }

/* ---- What it paid for its own stock -------------------------------------
   Above the line is money spent above the model's value; below is money spent
   below it. The two colours are the two outcomes, not good and bad taste. */
.bb-verdict { margin: 0 0 .9rem; padding: .7rem .8rem; border-radius: var(--radius-xs); }
.bb-verdict.dear { background: color-mix(in oklab, var(--critical) 10%, transparent); }
.bb-verdict.cheap { background: color-mix(in oklab, var(--good) 10%, transparent); }
.bb-verdict.fair { background: var(--surface-1); }
.bb-list { display: flex; flex-direction: column; gap: 2px; }
.bb-row { display: grid; gap: .8rem; align-items: center;
  grid-template-columns: 52px 76px minmax(70px, 1fr) 62px minmax(150px, auto);
  padding: .4rem .6rem; border-radius: var(--radius-xs); }
.bb-row:hover { background: var(--surface-1); }
/* A year left out of the verdict is shown, and shown as set apart. */
.bb-row.out { opacity: .55; }
.bb-row.out .bb-y::after { content: "*"; opacity: .7; }
.bb-s, .bb-p { text-align: right; }
.bb-bar { height: 7px; border-radius: 4px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.bb-bar i { display: block; height: 100%; }
.bb-bar i.over { background: var(--critical); }
.bb-bar i.under { background: var(--good); }
.bb-v { text-align: right; white-space: nowrap; }
@media (max-width: 760px) {
  .bb-row { grid-template-columns: 50px 1fr 60px; }
  .bb-bar, .bb-v { display: none; }
}

/* Where the cash went — the capital-allocation panel.
   Each bucket gets its own colour so the same use is the same colour on every
   company: money going back into the business reads as the brand, money going
   out to owners as the positive tone, debt as the neutral one. The point of a
   fixed palette here is comparison across companies, not decoration. */
.ca-verdict { margin: 0 0 .9rem; padding: .7rem .8rem; border-radius: var(--radius-xs);
  background: var(--surface-1); }
.ca-verdict.reinvested { background: color-mix(in oklab, var(--brand) 10%, transparent); }
.ca-verdict.returned { background: color-mix(in oklab, var(--good) 10%, transparent); }
.ca-verdict.deleveraged { background: color-mix(in oklab, var(--text-muted) 12%, transparent); }
.ca-list { display: flex; flex-direction: column; gap: 2px; }
.ca-row { display: grid; gap: .8rem; align-items: center;
  grid-template-columns: minmax(150px, 190px) minmax(70px, 1fr) 56px minmax(80px, auto);
  padding: .4rem .6rem; border-radius: var(--radius-xs); }
.ca-row:hover { background: var(--surface-1); }
/* The remainder is not one of the buckets and is not drawn as one. */
.ca-rest { opacity: .7; border-top: 1px solid var(--border); margin-top: .3rem;
  padding-top: .6rem; }
.ca-p, .ca-a { text-align: right; }
.ca-bar { height: 9px; border-radius: 5px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.ca-bar i { display: block; height: 100%; }
.ca-bar i.ca-capex { background: var(--brand); }
.ca-bar i.ca-acquisitions { background: var(--brand-deep); }
.ca-bar i.ca-debt_repaid { background: color-mix(in oklab, var(--text-muted) 55%, transparent); }
.ca-bar i.ca-dividends { background: var(--good); }
.ca-bar i.ca-buybacks { background: color-mix(in oklab, var(--good) 62%, var(--brand)); }
@media (max-width: 760px) {
  .ca-row { grid-template-columns: 1fr 56px 78px; }
  .ca-bar { display: none; }
}

/* Every assumption behind that number.
   A three-column row — what it is, the figure, why it matters — because the
   third column is the whole point. A list of twenty numbers with no argument
   attached is a specification; the same list with one line each on what moves
   if you disagree is something a reader can actually push back on. */
.as-details > summary { font-weight: 650; }
.as-body { display: flex; flex-direction: column; gap: 1.1rem; padding-top: .3rem; }
.as-group > h4 { margin: 0 0 .5rem; font-size: .8rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); font-family: var(--font-mono); }
.as-row { display: grid; gap: .9rem; align-items: baseline;
  grid-template-columns: minmax(150px, 210px) minmax(90px, 120px) 1fr;
  padding: .35rem .5rem; border-radius: var(--radius-xs); }
.as-row:hover { background: var(--surface-1); }
.as-v { text-align: right; }
.as-w { line-height: 1.45; }
.as-cocgrid { display: grid; gap: .35rem .9rem;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.as-coc { display: flex; justify-content: space-between; gap: .8rem;
  padding: .3rem .5rem; border-radius: var(--radius-xs);
  border-bottom: 1px solid var(--border); font-size: .88rem; }
.as-coc > span { color: var(--text-secondary); }
@media (max-width: 760px) {
  .as-row { grid-template-columns: 1fr auto; }
  .as-w { grid-column: 1 / -1; }
}
.as-none { font-weight: 400; }

/* The eleven-gate card, shown only in Strict and leading the page there.
   Deliberately louder than the callout it replaces: in Strict this is the
   answer, not a footnote to one. */
.strictlead { border-color: var(--border-strong); }
.strictverdict { margin: 0 0 .7rem; padding: .7rem .8rem; border-radius: var(--radius-xs);
  background: var(--surface-1); line-height: 1.55; }
.strictverdict.pass { background: color-mix(in oklab, var(--good) 12%, transparent); }
.strictverdict.fail { background: color-mix(in oklab, var(--critical) 10%, transparent); }
.strictfails { margin: 0; }
/* Untestable rules sit apart from failed ones: no cross, no red, and a line
   saying why. Both are shown, only one is counted. */
.strictunknown { margin-top: .5rem; opacity: .72; }
.strictunknown > li { list-style: none; }
.strictgap .strictu { font-style: normal; opacity: .6; margin-left: .3rem; font-size: .8em; }
/* The rank line under a margin of safety: a second thought, not a second number. */
.mosrank { opacity: .78; }
/* The liquidity line in the trade ticket: a remark, not a warning, until it is. */
.tw-liq { display: block; margin-top: .35rem; }
.tw-liq.heavy { color: var(--warning); }

/* Ex-dividend and pay date. Two facts with one piece of mechanics between
   them, so they sit together rather than in the stat grid above. */
.divdates { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; }
.divdate { display: flex; align-items: baseline; gap: .55rem;
  padding: .45rem .7rem; border-radius: var(--radius-xs);
  border: 1px solid var(--border); background: var(--surface-1); cursor: help; }
.divdate.past { opacity: .62; }
/* The rules summary on the run hero: a pointer, not a panel. */
.ra-sum { border-top: 1px solid var(--border); padding-top: .6rem; }
.ra-sum.hot { color: var(--warning); }
.ra-sum a { color: inherit; }

/* Funds from operations. A REIT gets no fair value here, so this panel is the
   substitute and is styled as a finding rather than as a valuation. */
.ffo-top { display: flex; flex-wrap: wrap; gap: 1.6rem; margin-bottom: .8rem; }
.ffo-v { font-size: 1.5rem; line-height: 1.15; }
.ffo-verdict { margin: 0 0 .9rem; padding: .7rem .8rem; border-radius: var(--radius-xs);
  background: var(--surface-1); line-height: 1.55; }
.ffo-list { display: flex; flex-direction: column; gap: 2px; }
.ffo-row { display: grid; gap: .8rem; align-items: center;
  grid-template-columns: 52px minmax(70px, 1fr) 84px minmax(150px, auto);
  padding: .4rem .6rem; border-radius: var(--radius-xs); }
.ffo-row:hover { background: var(--surface-1); }
.ffo-n { text-align: right; }
.ffo-d { text-align: right; white-space: nowrap; }
.ffo-bar { height: 9px; border-radius: 5px; overflow: hidden;
  background: color-mix(in oklab, var(--text-muted) 14%, transparent); }
.ffo-bar i { display: block; height: 100%; background: var(--brand); }
@media (max-width: 760px) {
  .ffo-row { grid-template-columns: 52px 1fr 84px; }
  .ffo-d { display: none; }
}
/* A remembered peer says how old its figures are. */
.peer-age { opacity: .7; cursor: help; }

/* How the run got here. The range buttons borrow the price card's shape so the
   two charts on the site that offer a window behave the same way. */
.rp-range { display: flex; gap: .3rem; margin-bottom: .6rem; }
.rp-range button { padding: .25rem .6rem; border-radius: 999px; font-size: .78rem;
  border: 1px solid var(--border); background: transparent; color: var(--text-secondary);
  cursor: pointer; }
/* Same treatment as the five-year record's Yearly/Quarterly switch, so the
   three windowed charts on this site do not each look pressed differently. */
.rp-range button[aria-pressed="true"] {
  background: color-mix(in oklab, var(--brand) 18%, transparent);
  border-color: var(--brand); color: var(--text-primary); }
.rp-tiles { display: grid; gap: .8rem; margin-top: .9rem;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.rp-tile { padding: .6rem .7rem; border-radius: var(--radius-xs);
  background: var(--surface-1); border: 1px solid var(--border); }
.rp-v { font-size: 1.25rem; line-height: 1.2; }

/* Per share, three ways.
   Bars are signed off a shared scale rather than each normalised to itself:
   the whole point is that one of these can be negative while another is up
   double digits, and three bars each filling their own row would hide it. */
.ps-list { display: flex; flex-direction: column; gap: .1rem; }
.ps-row { display: grid; grid-template-columns: minmax(120px, 1.4fr) minmax(60px, 3fr) auto;
  gap: .7rem; align-items: center; padding: .45rem .3rem; border-radius: var(--radius-xs); }
.ps-row:hover { background: var(--surface-sunken); }
.ps-row.used { background: color-mix(in oklab, var(--brand) 7%, transparent); }
.ps-l { min-width: 0; font-size: .86rem; }
.ps-tag { font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand); border: 1px solid var(--border);
  border-radius: 999px; padding: .05rem .3rem; margin-left: .2rem; }
.ps-bar { position: relative; height: 7px; border-radius: 999px;
  background: var(--surface-sunken); overflow: hidden; }
.ps-bar i { position: absolute; inset: 0 auto 0 0; display: block; border-radius: 999px; }
.ps-bar.up i { background: var(--good); }
.ps-bar.down i { background: var(--critical); }
.ps-v { font-size: .95rem; }
.ps-warn { margin: .7rem 0 0; line-height: 1.55; color: var(--warning); }
@media (max-width: 520px) {
  .ps-row { grid-template-columns: 1fr auto; }
  .ps-bar { grid-column: 1 / -1; }
}

/* Where the cash went, as three figures before the itemised list.
   Deliberately three separate tiles rather than one stacked bar: they are
   shares of cash generated and do not sum to 100, and a single bar would
   promise that they do. */
.ca-shape { display: grid; gap: .7rem; margin: .2rem 0 .5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr)); }
.ca-sh { padding: .55rem .7rem; border-radius: var(--radius-xs);
  background: var(--surface-1); border: 1px solid var(--border); }
.ca-shv { display: block; font-size: 1.3rem; line-height: 1.2; }

/* The book's record: the curve wide, the three figures beside it.
   Flex-wrap rather than a grid with a breakpoint -- the panel is a grid item
   whose width is not the viewport's, so a viewport media query is measuring
   the wrong thing (the same mistake the shortlist window's layout started
   with). Below its own wrap point the figures fall under the chart and become
   a normal tile row. */
.pf-rec-split { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start;
  margin-top: 1rem; }
.pf-rec-split > :first-child { flex: 4 1 320px; min-width: 0; }
.pf-rec-side { flex: 1 1 210px; margin-top: 0;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

/* The shape of a company — the dashboard's top-right panel.
   Chips first because switching is the point; the shape is large because
   seeing it is the point. */
.fp-chips { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .7rem; }
.fp-chip { padding: .2rem .5rem; border-radius: 999px; font-size: .74rem;
  font-family: var(--font-mono); border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary); cursor: pointer; }
.fp-chip[aria-pressed="true"] { background: color-mix(in oklab, var(--brand) 18%, transparent);
  border-color: var(--brand); color: var(--text-primary); }
.fp-body { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.fp-flake { cursor: pointer; flex: 0 0 auto; }
.fp-meta { flex: 1 1 130px; min-width: 130px; display: flex; flex-direction: column; gap: .35rem; }
.fp-name { font-weight: 650; line-height: 1.25; }
.fp-line { display: flex; align-items: baseline; gap: .35rem; flex-wrap: wrap; }
.fp-verdict { font-size: .82rem; line-height: 1.4; color: var(--text-secondary); }
.fp-verdict.under { color: var(--good); }
.fp-verdict.over { color: var(--critical); }

/* One quarter, opened. The chips are the "individual stacks" to click; the
   badge is the year-on-year trend beside each line. */
.qd { margin: .8rem 0 0; }
.qd-chips { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .7rem; }
.qd-chips button { padding: .18rem .45rem; border-radius: var(--radius-xs);
  font-size: .72rem; font-family: var(--font-mono); border: 1px solid var(--border);
  background: transparent; color: var(--text-muted); cursor: pointer; }
.qd-chips button[aria-pressed="true"] {
  background: color-mix(in oklab, var(--brand) 18%, transparent);
  border-color: var(--brand); color: var(--text-primary); }
.qd-body { display: flex; flex-direction: column; gap: 2px; }
.qd-row { display: grid; gap: .8rem; align-items: baseline;
  grid-template-columns: minmax(120px, 160px) minmax(80px, 110px) 110px 1fr;
  padding: .35rem .5rem; border-radius: var(--radius-xs); }
.qd-row:hover { background: var(--surface-1); }
.qd-v { text-align: right; }
.qd-b { font-size: .78rem; font-family: var(--font-mono); }
.qd-b.pos { color: var(--good); }
.qd-b.neg { color: var(--critical); }
@media (max-width: 760px) {
  .qd-row { grid-template-columns: 1fr auto; }
  .qd-b, .qd-p { grid-column: 1 / -1; }
}

/* The eleven gates, as tiles.
   =========================================================================
   Chris asked to see, when he calls a company up, "what worked and what not"
   against his original rules, "with links that if for example margin gate is
   green that you get there as well". So: a verdict you can read across the
   whole grid at a glance, a number under each so the verdict is arguable, and
   every tile a button into the evidence.

   The colour is carried by a left edge and the dot, never by the tile's
   background. Eleven saturated green and red cards is a traffic light, not a
   panel, and it would fight the twelve moods the rest of the app runs under.
   Same reason the knocked-out chip on the landing row is grey. */
.gmgrid {
  display: grid; gap: .5rem; margin-top: .75rem;
  grid-template-columns: repeat(auto-fill, minmax(198px, 1fr));
}
.gtile {
  position: relative; display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-1); overflow: hidden;
  border-left: 3px solid var(--border);
  transition: border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.gtile.is-pass { border-left-color: var(--good); }
.gtile.is-fail { border-left-color: var(--critical); }
.gtile.is-unknown { border-left-color: var(--border); }
.gtile:hover { transform: translateY(-1px); border-color: var(--text-muted); }

.gt-main {
  display: flex; flex-direction: column; gap: .18rem; align-items: flex-start;
  padding: .55rem .6rem .5rem; border: 0; background: none; cursor: pointer;
  text-align: left; width: 100%; color: inherit; font: inherit;
}
.gt-top {
  display: inline-flex; align-items: center; gap: .34rem;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted);
}
.gt-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--border); }
.is-pass .gt-dot { background: var(--good); }
.is-fail .gt-dot { background: var(--critical); }
.is-pass .gt-top { color: var(--good); }
.is-fail .gt-top { color: var(--critical); }

/* The rule as it was written, then what the company actually reads. The rule
   is the smaller of the two: a reader scanning eleven tiles is looking for the
   number, and already knows which gate they are on from the colour and the
   order, which never changes. */
.gt-rule { font-size: .78rem; color: var(--text-secondary); line-height: 1.25; }
.gt-val {
  font-family: var(--font-mono); font-size: .92rem; font-weight: 600;
  color: var(--text-primary); margin-top: .1rem;
}
.is-unknown .gt-val { color: var(--text-muted); font-weight: 400; font-size: .8rem; }

/* **Where the model does not ask the question the way the slides asked it.**
   Chris: "Damodaran > Me ... its just important to highlight that." So every
   tile carries the answer in a fixed place — a link to the reason where the
   two differ, and the words "as you wrote it" where they do not. Silence on
   the faithful ones would make the marker read as a warning rather than as a
   fact about the model's own record. */
.gt-why {
  display: block; width: 100%; text-align: left;
  padding: .3rem .6rem; border: 0; border-top: 1px solid var(--border);
  background: none; font-family: var(--font-mono); font-size: .58rem;
  letter-spacing: .04em; text-transform: uppercase; color: var(--brand);
  cursor: pointer;
}
.gt-why:hover { background: color-mix(in oklab, var(--brand) 10%, transparent); }
.gt-why.is-kept { color: var(--text-muted); cursor: default; }

.gatematrix .gm-lede { margin: .1rem 0 0; }
.gatematrix .gm-foot { margin: .7rem 0 0; }
/* Under Strict this panel is the answer rather than a comparison beside one. */
.gatematrix.leads { border-color: color-mix(in oklab, var(--brand) 34%, var(--border)); }

/* The other half of "you get there as well": the row the tile sent you to has
   to be findable once you arrive. A ring rather than a background flash, so a
   criterion row's own meter colour is not overpainted while it fades. */
@keyframes gmFlash {
  from { box-shadow: 0 0 0 2px var(--brand); }
  to   { box-shadow: 0 0 0 2px transparent; }
}
.gm-flash { animation: gmFlash 1.6s var(--ease) 1; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) { .gm-flash { animation: none; } }

/* How strong the data is.
   =========================================================================
   A list where every row looks equally important is a list nobody reads to
   the end, and the whole finding here is that the rows are not equally
   important: a thin margin history decides the valuation, a missing dividend
   record does not. So load-bearing rows are set at full strength and the rest
   recede, and the state mark is the only colour on the row. */
.dsrows { margin-top: .7rem; }
.dsrow {
  display: grid; align-items: baseline; gap: .55rem;
  grid-template-columns: 1.1rem 1fr auto auto;
  padding: .42rem 0; border-top: 1px solid var(--border);
}
.dsrow:first-child { border-top: 0; }
.ds-mark {
  font-family: var(--font-mono); font-weight: 700; text-align: center;
  color: var(--text-muted);
}
.dsrow.is-ok .ds-mark { color: var(--good); }
.dsrow.is-thin .ds-mark { color: var(--warning); }
.dsrow.is-missing .ds-mark { color: var(--critical); }

.ds-label { color: var(--text-primary); font-size: .84rem; line-height: 1.3; }
.ds-label small {
  display: block; color: var(--text-muted); font-size: .74rem; margin-top: .1rem;
}
.ds-count {
  font-family: var(--font-mono); font-size: .78rem; color: var(--text-secondary);
  white-space: nowrap;
}
/* How much the answer leans on the row, which is the column that turns this
   from a checklist into a finding. Muted by default; only spelled out at full
   contrast where it actually carries the valuation. */
.ds-bear {
  font-family: var(--font-mono); font-size: .6rem; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted); white-space: nowrap;
}
.dsrow.bearing-high .ds-bear { color: var(--brand); }
.dsrow.bearing-high .ds-label { font-weight: 600; }
.dsrow.bearing-low .ds-label, .dsrow.bearing-low .ds-count { color: var(--text-secondary); }

/* The note only appears on a row that is not fully backed, so it reads as the
   consequence of that row rather than as general commentary. */
.ds-note {
  margin: 0 0 .3rem 1.65rem; padding-left: .6rem;
  border-left: 2px solid var(--border); max-width: 66ch;
}
.datastrength .ds-lede { margin: .1rem 0 0; }
.datastrength .ds-foot { margin: .8rem 0 0; max-width: 72ch; }

@media (max-width: 560px) {
  /* The bearing label is the first thing to go: at this width the row order
     already carries it, since the panel sorts by it. */
  .dsrow { grid-template-columns: 1.1rem 1fr auto; }
  .ds-bear { display: none; }
}

/* Where the rate is standing, under the rows saying what a move would do.
   Set apart from the list rather than appended to it: the rows are a
   sensitivity and this is a fact about the world, and reading it as a sixth
   row would suggest it was another shock. */
.rateanchor {
  margin-top: .75rem; padding: .7rem .8rem;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-sunken);
}
.ra-head { display: flex; align-items: baseline; gap: .6rem; }
.ra-pct {
  font-size: 1.35rem; font-weight: 700; line-height: 1;
  color: var(--brand); flex: 0 0 auto;
}
.ra-txt { font-size: .8rem; color: var(--text-secondary); line-height: 1.35; }
.ra-what { margin: .5rem 0 0; color: var(--text-primary); }
.ra-foot { margin: .4rem 0 0; max-width: 70ch; }
.rate-anchor-note { margin: .6rem 0 0; max-width: 70ch; }

/* What the last filing did, split from what the calendar did.
   Three figures on one row, the news given the emphasis because it is the one
   a reader is actually asking about — the other two are there so the split can
   be checked rather than taken on trust. */
.lmrow {
  display: grid; gap: .6rem; margin-top: .7rem;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}
.lmpart {
  display: flex; flex-direction: column; gap: .12rem;
  padding: .55rem .65rem; border-radius: 9px;
  border: 1px solid var(--border); background: var(--surface-1);
}
.lmpart.is-news { border-color: color-mix(in oklab, var(--brand) 38%, var(--border)); }
.lm-k {
  font-family: var(--font-mono); font-size: .6rem; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-muted);
}
.lm-v { font-size: 1.15rem; line-height: 1.1; }
.lm-n { font-size: .72rem; color: var(--text-muted); }
.lastmove .lm-foot { margin: .7rem 0 0; max-width: 74ch; }

/* What the company does, folded under the shape of what it is worth.
   One line, and the rest a click away. Somebody who types NVDA knows what
   Nvidia sells; somebody meeting a company for the first time does not, and
   both are served by a disclosure rather than by a paragraph at the top of
   the page. Separated by a rule rather than a card of its own, because it is
   a footnote to the snowflake above it. */
.co-about {
  margin-top: .9rem; padding-top: .7rem;
  border-top: 1px solid var(--border);
}
.co-about-line { margin: 0; max-width: 78ch; }
.co-about-more { margin-top: .4rem; }
.co-about-more > summary {
  cursor: pointer; list-style: none;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .05em;
  text-transform: uppercase; color: var(--brand);
}
.co-about-more > summary::-webkit-details-marker { display: none; }
.co-about-more > summary::after { content: " ▸"; }
.co-about-more[open] > summary::after { content: " ▾"; }
.co-about-more > summary:hover { text-decoration: underline; }
.co-about-more p { max-width: 78ch; margin: .5rem 0 0; }

/* The one sentence that makes the rings on the price chart mean something.
   Tight under the chart rather than in the block of notes below it, because
   it is about a mark the reader is looking at right now. */
.pricelegend { margin: .35rem 0 .1rem; max-width: 84ch; }
