/* ==========================================================================
   MarketIQ Design System
   Design language derived from arrow.markets:
   near-black surfaces, hairline borders, electric indigo primary,
   IBM Plex Mono uppercase micro-labels, 8px radii.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Brand — constant across themes */
  --iq-teal: #00daa6;
  --iq-teal-ink: #00705f;   /* unused: AA-safe teal, kept as the fallback for light mode */
  /* Indigo is no longer the brand. It stays as the non-semantic accent: the one
     highlight that is neither teal (identity) nor green/red (a verdict). */
  --iq-indigo: #4e4aff;
  --iq-indigo-hi: #6a67ff;
  --iq-mint: #97fce4;
  --iq-red: #ff5151;
  --iq-amber: #ffb545;

  /* Type */
  --font-sans: "Diatype", "Inter Tight", "Inter", -apple-system,
    BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  /* Roboto Mono for its zero, which carries a slash rather than the dot IBM
     Plex Mono uses. On a page that is mostly figures the zero is the glyph
     seen most often, and Plex offers no slashed alternate — asking for
     font-variant-numeric: slashed-zero against it changes nothing. Every
     fallback behind it is slashed too, so the shape holds if the webfont
     never arrives. */
  --font-mono: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Geometry — tighter than arrow's marketing surfaces. A data tool reads as
     more precise when corners are nearly square. */
  --r: 6px;

  /* Page panels — cards, stat blocks, the activity feed. Square until
     2026-09-02, on the argument that a rounded frame reads as a consumer app
     rather than an instrument. The A2 layout was drawn at 16px, CP approved it
     as drawn, and chose 16px over keeping the square when the two were put
     side by side — so the panel is soft and the instrument feel now comes from
     the type and the density rather than from the corners.

     16px sits deliberately between Lite's 22px and square. It is NOT the 4-8px
     band CP rejects as "the Bootstrap-era default that reads as a decision
     nobody made" — that rejection still stands, so do not split the difference
     if this is ever revisited. Controls and floating overlays keep --r: a
     button with square corners just reads as unfinished. Kept as a token so
     the decision lives in one place instead of in six rules. */
  --r-panel: 16px;
  --r-sm: 3px;
  --r-pill: 7000px;
  --hair: 1px;

  /* Rhythm */
  --gap: 14px;
  --gap-lg: 22px;
  --pad: 18px;

  /* 1560 until 2026-09-11, 1900 until 09-23, 1467 until 09-24 (CP each time).
     Both the content shell and the top bar read this one token, so they widen
     together and the nav stays aligned with the columns under it -- the reason
     it is a variable rather than two numbers. */
  --shell-max: 1267px;
  --nav-h: 64px;
  /* Every control that sits in a top bar shares this height, so the row lines
     up without each component being measured by hand. */
  --ctl-h: 34px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Dark — the default, matched to arrow.markets */
:root,
[data-theme="dark"] {
  /* Paired with the declaration in the light block. Unset, both themes handed
     their scrollbars and native control internals to the OS preference. */
  color-scheme: dark;

  /* #101010, not #000 (CP, 2026-09-24: black "hurts eyes ... after staring at
     it"). The whole dark ladder moves with it, keeping its steps: a surface
     below the page ground reads as a hole cut into the page rather than a card
     sitting on it, and every border here is drawn against those surfaces. */
  --bg: #101010;
  --surface: #161616;
  --surface-2: #1b1b1b;
  --elevated: #202020;
  --elevated-hi: #262626;

  --border: #333333;
  --border-soft: #242424;
  --border-strong: #424242;

  --text: #ffffff;
  --text-2: #c4c4c4;
  --text-3: #7a7a7a;
  --text-4: #4a4a4a;

  /* Identity, in the chrome only. Never inside a table or a card: on ivory the
     brand sits dE 6.5 from --up, and the two must never share a surface. */
  --brand: #00dcfa;   /* the mark's own colour (CP, 2026-09-24), not the app's accent */
  --accent: var(--iq-indigo);
  --primary: #ffffff;
  --primary-hi: #e4e4e4;
  --primary-text: #000000;
  --primary-soft: rgba(255, 255, 255, 0.1);
  --primary-line: rgba(255, 255, 255, 0.32);

  --up: #97fce4;
  --up-soft: rgba(151, 252, 228, 0.12);

  /* --up as TEXT, split from --up as a fill.
     -------------------------------------------------------------------------
     One token was doing both jobs and only one of them can be satisfied at a
     time on a light ground: the mint that makes a good badge or bar fill is
     2.43:1 as type on #f7f7f7, well under the 4.5 a return printed in green
     needs. Darkening the single token would have taken every fill down with
     it. Identical to --up here, because on black the mint is already 17:1. */
  --up-text: #97fce4;
  --down: #ff5151;
  --down-soft: rgba(255, 81, 81, 0.12);
  --flat: #7a7a7a;

  --warn: #e0a020;
  --warn-soft: rgba(224, 160, 32, 0.13);
  --warn-line: rgba(224, 160, 32, 0.4);

  /* Categorical data ramp — one hue stepping down into neutral, so a donut
     reads as a ranked share rather than a set of unrelated colours. */
  --ramp-1: #7f7cff;
  --ramp-2: #625fe8;
  --ramp-3: #4d4ac0;
  --ramp-4: #a2a2b8;
  --ramp-5: #7d7d92;
  --ramp-6: #5e5e70;
  --ramp-7: #474756;

  --grid-line: rgba(255, 255, 255, 0.1);
  --shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.9);
  --scrim: rgba(0, 0, 0, 0.72);
}

/* Light — warm rather than white, following anthropic.com. Same structure as
   before: the page is the mid tone and cards lift off it by being lighter. What
   changes is that nothing here is a pure white or a cool grey.

   Near-black on #ffffff is 19:1, which is a great deal of contrast to sit in
   front of for a working day, and it is the top end that causes the glare
   rather than the text itself. An ivory ground brings that to 17.5:1 and leaves
   every other tier alone.

   The neutrals are Anthropic's own ivory/cloud/slate swatches rather than warm
   greys mixed by hand, which drift green or pink partway down a ramp.

   Softening the page turned out to mean raising the floor, not lowering it:
   both of the pairs that failed WCAG AA here were in the old cool palette. The
   old green was 3.98:1 on white while being used for numbers that have to be
   read, and the old --text-4 was 2.79:1. Every pair in this block clears AA —
   see the ratios in the theme note in deploy/README.md. */
[data-theme="light"] {
  /* Tells the browser to render scrollbars, focus rings, autofill and the
     insides of native controls light. Without it they follow the OS, so this
     page had dark scrollbars down both sides for anyone running a dark
     desktop. */
  color-scheme: light;

  /* Softened, and given a step (CP, 2026-09-24: "this is killing my eyes").
     The page was #f7f7f7 and a card was ALSO #f7f7f7 -- one near-white sheet
     edge to edge, separated only by a border, which is the glare. Now the page
     sits a shade down and a panel sits a shade up, so the light theme has the
     same ladder the dark one has: the page is the tint and a card is white, so
     an edge is a change of shade rather than a hairline doing all the work.
     #f9f9fb is CP's own value (2026-09-24) -- cool, and light enough that white
     panels still read as panels because everything else steps down from them. */
  --bg: #f9f9fb;
  --surface: #ffffff;
  --surface-2: #f1f1f5;
  --elevated: #f4f4f8;
  --elevated-hi: #ffffff;

  /* Solid, not translucent. Anthropic fades a black at 10% for borders, which
     is softer on a marketing page, but a dense table stacks its borders and
     alpha lines double where they meet and read as smudges. */
  /* Two jobs, two weights.
     -------------------------------------------------------------------------
     --border draws the edges of things: card and panel outlines, and the
     borders on badges, pills, inputs and the segmented control. Those define
     where a region begins and are allowed to be seen — 4.61:1 on white.

     --border-soft is rhythm, not structure: the rule between table rows. It
     should be felt while reading down a column and never compete with the
     figures either side of it — 1.74:1.

     Both were previously so faint they were effectively undrawn (1.38:1 and
     1.26:1), which is more tiring rather than less: the eye keeps looking for
     an edge that was never rendered. */
  --border: #757575;
  --border-soft: #c4c4c4;

  /* One step past --border, never behind it. This is the hover and emphasis
     weight — pills, buttons, the icon button, nav search, checkboxes and the
     lead stat card all reach for it — so it has to be the strongest of the
     three or a hover reads as the border fading out. It briefly did: --border
     moved to #757575 and this was left at #c3c3c3, which inverted the ladder
     and made the emphasised card the faintest thing on the page. */
  --border-strong: #5e5e5e;

  /* Darker by a step each (CP, 2026-09-24), with the gaps kept so the four
     still read as four. --text-4 was 3.22 against the old page and failed AA at
     body size; every step here clears it on both the page and a panel. */
  --text: #000000;
  --text-2: #333333;
  --text-3: #4f4f4f;
  --text-4: #626262;

  /* One brand colour in both themes, by decision: the mark should not change
     hue when the theme does. The cost is contrast, since the teal is built for
     a dark ground and lands at 1.81:1 on white. That is fine for the logo,
     which is exempt and reads as a shape, and tolerable for the wordmark, but
     it does thin out the 2px active-nav underline. --iq-teal-ink is the
     AA-passing alternative (6.03:1) if that underline proves too faint in use. */
  --brand: #00dcfa;   /* the mark's own colour (CP, 2026-09-24), not the app's accent */
  --accent: var(--iq-indigo);
  --primary: #000000;
  --primary-hi: #3b3b3b;
  --primary-text: #ffffff;
  --primary-soft: rgba(0, 0, 0, 0.06);
  --primary-line: rgba(0, 0, 0, 0.24);

  /* Warmed to sit on ivory, and both now legible as body text, which the
     green was not before. */
  /* CP's value. It is 2.43:1 on this background where the green it replaces was
       6.11, and --up carries gain percentages at body size in the channel table,
       so this was raised and chosen anyway. Recorded rather than argued again.

       --up-soft moves with it. Leaving the tint derived from the old green would
       put a colder wash behind a warmer figure, which reads as a rendering fault
       rather than a decision. */
    --up: #25b58c;
  --up-soft: rgba(37, 181, 140, 0.11);
  /* 4.92:1 on the page and 5.14:1 on a card, against #25b58c's 2.43. Same hue,
     carried to a weight that can actually be read. --down needs no twin: it is
     already 6.04:1 here. */
  --up-text: #177a5e;
  --down: #a83729;
  --down-soft: rgba(168, 55, 41, 0.1);
  --flat: #8a8a8a;

  --warn: #785713;
  --warn-soft: rgba(120, 87, 19, 0.12);
  --warn-line: rgba(120, 87, 19, 0.38);

  /* Indigo stepping down into the warm greys, so a donut still reads as a
     ranked share and the tail belongs to the same page as everything else. */
  --ramp-1: #4e4aff;
  --ramp-2: #7370f0;
  --ramp-3: #9c9ae0;
  --ramp-4: #949494;
  --ramp-5: #b2b2b2;
  --ramp-6: #c3c3c3;
  --ramp-7: #dbdbdb;

  /* Warm black, so gridlines and shadows do not turn the cream grey where
     they fall across it. */
  --grid-line: rgba(0, 0, 0, 0.09);
  --shadow: 0 20px 40px -16px rgba(0, 0, 0, 0.13);
  --scrim: rgba(0, 0, 0, 0.48);
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Components below set `display`, which would otherwise beat the UA
   `[hidden] { display: none }` rule and leave hidden panels visible. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "tnum" 1, "cv11" 1;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.01em;
}

h1 {
  font-size: 24px;
}
h2 {
  font-size: 18px;
}
h3 {
  font-size: 16px;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
}

::selection {
  background: var(--accent);
  color: #fff;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Scrollbars */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
*::-webkit-scrollbar-track {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: var(--r-pill);
  border: 3px solid var(--bg);
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--border-strong);
}

/* --------------------------------------------------------------------------
   3. Typography utilities
   -------------------------------------------------------------------------- */

/* The signature arrow.markets micro-label */
.label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 300;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
  line-height: 1.3;
}

.label--xs {
  font-size: 10px;
  letter-spacing: 0.7px;
}

.label--strong {
  font-weight: 500;
  color: var(--text-2);
}

/* Relative timestamps read as numbers when uppercased ("4d ago" -> "4D AGO"),
   so opt them out of the label's text transform. */
.tt-none {
  text-transform: none;
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.2px;
}

.t-2 {
  color: var(--text-2);
}
.t-3 {
  color: var(--text-3);
}
.t-up {
  color: var(--up-text);
}
.t-down {
  color: var(--down);
}
.t-flat {
  color: var(--flat);
}
.t-primary {
  color: var(--accent);
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   4. Layout
   -------------------------------------------------------------------------- */

.shell {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 0 24px;
}

/* 32px sides, matching every A2-register mockup. It used to be 0, which made
   .shell's `padding: 0 24px` dead code — .page is .shell's only class-styled
   user and overrode it wholesale, so Pro content ran edge to edge inside the
   1560 shell. The rail escapes this padding deliberately; nothing else should. */
.page {
  padding: 28px 32px 80px;
}

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap);
  margin-bottom: 22px;
  flex-wrap: wrap;
}

.page-head__title {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.grid {
  display: grid;
  gap: var(--gap);
}

.grid--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid--5 {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* Three columns beside two, on the same column grid as .grid--5 above it — the
   channel profile's hold-time chart and its idea mix, sitting under a row of five
   stat cards. The two card edges land exactly on stat boundaries because this is
   the same five-column track with the same gap, spanned rather than redivided.

   Which is why it is not simply 3fr/2fr. That ratio is only the same number when
   the gaps happen to work out: three spanned columns are 3 tracks plus 2 internal
   gaps, and a 3fr column is 3/5 of the row including a gap it never had. The
   difference is a few pixels, and a few pixels is the whole point of lining up. */
/* Stretched rather than start-aligned, so the two cards are always the same height
   whatever is in them. Their contents are tuned to come out level — the composition
   bar is 41px for that reason — but a channel with nothing directional resolved shows
   a sentence where the accuracy bars would be, and no bar height can make a sentence
   as tall as two charts. Stretching makes the match structural instead of arithmetic,
   so it survives that case and any later change to either card. */
.grid--3-2 {
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: stretch;
}

.grid--3-2 > :first-child {
  grid-column: span 3;
}

.grid--3-2 > :last-child {
  grid-column: span 2;
}

.row {
  display: flex;
  align-items: center;
  gap: var(--gap);
}

.row--between {
  justify-content: space-between;
}

.row--tight {
  gap: 8px;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.spacer {
  flex: 1;
}

/* --------------------------------------------------------------------------
   5. Top navigation
   -------------------------------------------------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: var(--hair) solid var(--border-soft);
}

.topbar__inner {
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: 28px;
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 0 24px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  flex-shrink: 0;
}

.brand__mark {
  width: 28px;
  height: auto;
  color: var(--brand);
  display: block;
}

/* Lite's wordmark, adopted here so the two modes stop introducing the same
   product in two voices. Pro set it in mono, uppercase, 13px on +1.2px of
   tracking — a wordmark dressed as a data label, which is what a bar full of
   mono nav links made it read as. Lite sets it in the page's own sans at 17px
   and slightly tightened, so it reads as a name and not as a fifth control. */
.brand__name {
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.2px;
}

/* The teal IQ goes with it. The mark beside it is already teal, and Lite has
   never split the word — two teals in three centimetres was the brand being
   said twice. The em stays in the markup: it costs nothing and the split is
   there to take up again if the wordmark is ever set on its own. */
.brand__name em {
  font-style: normal;
  color: inherit;
}

.nav {
  display: flex;
  align-items: center;
  gap: 26px;
}

.nav__link {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 4px 0;
  position: relative;
  transition: color 0.15s var(--ease);
}

.nav__link:hover {
  color: var(--text-2);
}

/* Full-strength colour and weight, and nothing else. The teal rule that used to
   sit under the active item came off on 2026-09-11 (CP): weight already said
   which section you were in, and the line said it a second time in the one
   colour the interface otherwise reserves for a positive number.

   It also makes the three active states agree. .nav__link--group.is-current has
   always been weight alone -- its comment says the label carries what the
   underline would have carried -- and so has .nav-overlay__item.is-active on a
   phone. This was the only one drawing a rule, which meant the same state read
   differently depending on where you saw it. */
.nav__link.is-active {
  color: var(--text);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Nav group — a nav item that opens rather than goes somewhere
   --------------------------------------------------------------------------
   Manage is the only one. It deliberately does not take the active underline the
   others do: that mark means "you are here", and Manage is never where you are,
   it is three places you might be. The chevron says it opens, and without it a
   label that swallows a click reads as a broken link.
   -------------------------------------------------------------------------- */
.navgroup {
  position: relative;
}

.nav__link--group {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: var(--hair) solid transparent;
  border-radius: var(--r);
  padding: 5px 8px;
  margin: -5px -8px;
  cursor: pointer;
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Inside the section, so the label carries the same weight the underline would
   have carried, without claiming to be a destination. */
.nav__link--group.is-current {
  color: var(--text);
  font-weight: 600;
}

.nav__link--group svg {
  transition: transform 0.15s var(--ease);
}

.nav__link--group[aria-expanded="true"] {
  border-color: var(--border);
  background: var(--elevated);
  color: var(--text);
}

.nav__link--group[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

.navgroup__menu {
  position: absolute;
  top: calc(100% + 14px);
  left: -8px;
  z-index: 60;
  /* Wider since the items carry a line of description, 2026-09-24. A reader
     opening Indexes is meeting the idea that the product has three of them, and
     the names on their own do not say what each one measures. */
  min-width: 262px;
  padding: 6px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.navgroup__item {
  display: block;
  padding: 10px 11px;
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
}

.navgroup__item:hover {
  background: var(--surface-2);
  color: var(--text-2);
}

/* What the index measures. Sans and sentence case against the item's mono
   uppercase, so the two lines read as a label and a sentence rather than as two
   competing labels. */
.navgroup__item small {
  display: block;
  margin-top: 3px;
  font-family: var(--font-sans);
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-4);
}

.navgroup__item:hover small {
  color: var(--text-3);
}

/* The same weight rule the nav uses, one level down. */
.navgroup__item.is-active {
  color: var(--text);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Narrow viewports — navigation moves to the bottom
   --------------------------------------------------------------------------
   Below 860px the top row was carrying the brand, four nav links, search, a mode
   switch, notifications and the account menu. It did not fit; it only stopped
   short of overflowing because the gap tightened to 16px.

   So navigation leaves the top entirely. The bar at the bottom holds the way in
   rather than the destinations, because one target within thumb reach beats four
   crushed ones, and the pill at the top names where you are — which is the thing
   the nav was communicating by being visible at all.
   -------------------------------------------------------------------------- */
.nav-here,
.navbar-bottom,
.nav-overlay {
  display: none;
}

@media (max-width: 860px) {
  .nav {
    display: none;
  }

  .nav-here {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    padding: 5px 9px;
    border: var(--hair) solid var(--border);
    border-radius: var(--r);
    background: var(--elevated);
    color: var(--text);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    cursor: pointer;
  }

  .navbar-bottom {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 70;
    display: flex;
    align-items: center;
    height: 56px;
    padding: 0 14px;
    border-top: var(--hair) solid var(--border);
    background: var(--bg);
  }

  .navbar-bottom__btn {
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: var(--hair) solid var(--border);
    border-radius: 10px;
    background: var(--elevated);
    color: var(--text);
    cursor: pointer;
  }

  /* So the last row of a page is not sitting under the bar. */
  .page {
    padding-bottom: 76px;
  }

  /* And whatever comes after the page. The footer follows <main> and carries
     its own inline 40px, less than the bar's 56px, so its last line sat 16px
     under the bar on every page. The body reserves the bar's height instead of
     each last element guessing it. Scoped with :has so a page that loads this
     sheet without the bar (Lite) gets no blank strip. */
  body:has(> .navbar-bottom) {
    padding-bottom: 56px;
  }

  .nav-overlay:not([hidden]) {
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    z-index: 90;
    padding: 0 18px;
    background: var(--bg);
    overflow-y: auto;
  }

  .nav-overlay__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 58px;
    flex-shrink: 0;
  }

  .nav-overlay__close {
    background: none;
    border: 0;
    color: var(--text-3);
    cursor: pointer;
    padding: 6px;
    margin: -6px;
  }

  .nav-overlay__nav {
    margin-top: 18px;
    display: flex;
    flex-direction: column;
  }

  .nav-overlay__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 13px 0;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: 19px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-3);
  }

  .nav-overlay__item.is-active,
  .nav-overlay__item.is-current {
    color: var(--text);
    font-weight: 600;
  }

  .nav-overlay__item svg {
    transition: transform 0.15s var(--ease);
    color: var(--text-4);
  }

  .nav-overlay__item[aria-expanded="true"] svg {
    transform: rotate(180deg);
  }

  .nav-overlay__sub {
    padding-left: 14px;
    margin: 0 0 6px;
    border-left: var(--hair) solid var(--border);
  }

  .nav-overlay__subitem {
    display: block;
    padding: 9px 0;
    font-family: var(--font-mono);
    font-size: 12.5px;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: var(--text-3);
  }

  .nav-overlay__subitem.is-active {
    color: var(--text);
    font-weight: 600;
  }
}

/* The overlay is a phone affordance. If the window grows while it is open --
   a rotation, or a desktop resize -- the real nav is back and this must not be
   left covering the page. */
@media (min-width: 861px) {
  .nav-overlay {
    display: none !important;
  }
}

/* Pro / Lite switch, present in both shells */
.modesw {
  display: inline-flex;
  align-items: stretch;
  height: var(--ctl-h);
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--surface-2);
}

.modesw__opt {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  padding: 0 10px;
  color: var(--text-3);
  transition: color 0.12s var(--ease), background 0.12s var(--ease);
}

.modesw__opt:hover {
  color: var(--text);
}

.modesw__opt.is-active {
  background: var(--elevated-hi);
  color: var(--text);
}

/* Compact variant for menu rows, matched to the 26px icon tile beside it. */
.modesw--sm {
  height: 26px;
}

.modesw--sm .modesw__opt {
  padding: 0 8px;
  font-size: 9px;
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 10px 20px;
  border-radius: var(--r);
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text-2);
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease),
    color 0.15s var(--ease), transform 0.1s var(--ease);
  white-space: nowrap;
}

.btn:hover {
  background: var(--primary-hi);
  border-color: var(--primary-hi);
  color: var(--text);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-text);
}

.btn--primary:hover {
  background: var(--elevated-hi);
  border-color: var(--border-strong);
  color: #fff;
}

.btn--ghost {
  background: transparent;
}

.btn--sm {
  padding: 7px 14px;
  font-size: 11px;
}

.btn--xs {
  padding: 5px 10px;
  font-size: 10px;
  letter-spacing: 0.5px;
}

.btn--lg {
  padding: 16px 40px;
  font-size: 14px;
  letter-spacing: 0.7px;
}

.btn--block {
  width: 100%;
}

/* Quiet until intent is shown. Red is reserved for losses; a destructive
   control only borrows it at the moment you reach for it. */
.btn--danger {
  color: var(--text-3);
  border-color: var(--border-soft);
}

.btn--danger:hover {
  background: var(--down-soft);
  border-color: var(--down);
  color: var(--down);
}

.btn[disabled],
.btn.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* A control waiting on the server. Disabled rather than merely dimmed, so a
   second click cannot send a second write while the first is in flight. */
.is-busy {
  opacity: 0.55;
  cursor: progress;
  pointer-events: none;
}

/* A row edited but not yet saved. The marker is deliberately conspicuous: the
   whole failure this replaced was an interface that looked saved and was not. */
.tbl tbody tr.is-dirty > td:first-child {
  box-shadow: inset 2px 0 0 var(--primary);
}

.tbl tbody tr.is-dirty {
  background: var(--primary-soft);
}

.btn__icon {
  width: 14px;
  height: 14px;
  display: block;
}

/* Icon-only square button */
.icon-btn {
  width: var(--ctl-h);
  height: var(--ctl-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r);
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text-3);
  transition: all 0.15s var(--ease);
}

.icon-btn:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--elevated-hi);
}

.icon-btn svg {
  width: 15px;
  height: 15px;
}

/* --------------------------------------------------------------------------
   7. Segmented control (timeframe / hold-time switchers)
   -------------------------------------------------------------------------- */

.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  background: var(--surface-2);
  /* Nine hold times do not fit across a phone. Scrolling the control is the
     only option that keeps every one of them reachable and legible; wrapping
     it breaks the pill, and shrinking it makes the labels unreadable. */
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.seg::-webkit-scrollbar {
  display: none;
}

.seg__btn {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 6px 12px;
  border-radius: var(--r-sm);
  transition: all 0.15s var(--ease);
  white-space: nowrap;
}

/* An option that would lead to an empty view, kept visible rather than removed.
   Its count is the information: "closed early 0" says a channel never reversed
   itself, which is worth knowing and is exactly what vanishes if the option is
   hidden instead. Rendered as a span, not a disabled link, so there is nothing
   to click and nothing to tab to. */
.seg__btn.is-disabled {
  color: var(--text-4);
  cursor: default;
  opacity: 0.55;
}

.seg__btn:hover {
  color: var(--text-2);
}

.seg__btn.is-active {
  background: var(--elevated-hi);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border);
}

.seg--primary .seg__btn.is-active {
  background: var(--primary);
  color: var(--primary-text);
  box-shadow: none;
}

/* A page's primary view switch. Underlined tabs rather than a pill, so the
   kinds of activity read as places you go while a pill segment beside them
   still reads as a filter on whichever place you are in. */
.seg--tabs {
  display: flex;
  gap: 0;
  width: 100%;
  padding: 0;
  border: none;
  border-bottom: var(--hair) solid var(--border);
  border-radius: 0;
  background: transparent;
}

.seg--tabs .seg__btn {
  padding: 0 14px 10px;
  border-radius: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.seg--tabs .seg__btn:first-child {
  padding-left: 0;
}

.seg--tabs .seg__btn.is-active {
  background: transparent;
  color: var(--text);
  border-bottom-color: var(--accent);
  box-shadow: none;
}

@media (max-width: 700px) {
  .seg--tabs {
    overflow-x: auto;
    scrollbar-width: none;
  }
  .seg--tabs::-webkit-scrollbar {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   8. Cards
   -------------------------------------------------------------------------- */

/* Square, and only here.
   ---------------------------------------------------------------------------
   A card is a frame around tabular data — mono figures, hairline rules,
   right-aligned columns — and a rounded frame around that reads as a consumer
   app rather than an instrument. It also removes the nested-radius problem:
   cards hold tables with square corners and blocks at --r-sm, and a concentric
   inner radius would have to be the outer minus the padding, which nothing
   here does.

   The --r token is untouched and still 6px. Twenty other rules use it —
   buttons, pills, inputs, the segmented control — and those stay rounded;
   squaring them would read as unfinished rather than deliberate. The softer,
   rounder treatment belongs to the consumer surface, where it is doing the
   work of telling the two apart. */
.card {
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  position: relative;
}

.card--flush {
  overflow: hidden;
}

.card--pad {
  padding: var(--pad);
}

/* Wraps rather than spills. A head with a title and a control in it runs out of
   room on a narrow screen, and without this the control is pushed off the right
   of the card and takes the page's horizontal scroll with it. Only engages when
   the line will not fit, so nothing changes at the widths where it already
   fits. */
.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--gap);
  padding: 16px var(--pad);
  border-bottom: var(--hair) solid var(--border-soft);
  min-height: 56px;
}

.card__title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  font-weight: 700;
}

.card__body {
  padding: var(--pad);
}

.card__body--flush {
  padding: 0;
}

.card__foot {
  padding: 14px var(--pad);
  border-top: var(--hair) solid var(--border-soft);
}

/* Stat card — the hero metric block */
.stat {
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  padding: 18px var(--pad) 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  overflow: hidden;
}

.stat__label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.stat__value {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.8px;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.stat__value--sm {
  font-size: 20px;
}

.stat__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.stat__spark {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 42%;
  opacity: 0.65;
  pointer-events: none;
}

/* The lead metric earns emphasis from a rule and a stronger border, not
   from a tinted panel. */
.stat--accent {
  border-color: var(--border-strong);
}

.stat--accent::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
}

/* --------------------------------------------------------------------------
   8b. Stream cards
   --------------------------------------------------------------------------
   One shape for every source. The grid is auto-fill rather than a fixed count
   so the same markup serves the 1560 Pro width and a phone without a
   breakpoint; the minimum is 260px because below that the two-column know rows
   start wrapping and the card stops being scannable.
   -------------------------------------------------------------------------- */
.stream-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;

  /* Every row the height of the tallest, so cards match across the whole grid
     and not merely within a row. Safe now that the footer rail is always
     present: the cards carry the same skeleton, so this is a no-op until
     something genuinely varies. */
  grid-auto-rows: 1fr;
}

.scard {
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 15px 16px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
}

.scard__who { display: flex; align-items: center; gap: 9px; min-width: 0; }
/* Was a 24px saturated block, which made the SOURCE the loudest thing on a
   card about an idea. The colour still identifies the feed; it no longer
   competes with the symbol for the first look. */
.scard__tint { width: 3px; height: 26px; border-radius: 2px; flex: none; opacity: .85; }
.scard__id { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.scard__name { font-size: 13.5px; font-weight: 600; letter-spacing: -0.2px; color: var(--text); }
.scard__who:hover .scard__name { color: var(--primary); }

/* The channel's record, right-aligned against the name it belongs to. Tabular
   so a column of cards lines its digits up. */
.scard__rate {
  margin-left: auto;
  flex: none;
  font-size: 13px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.scard__who:hover .scard__rate { color: var(--text); }

.scard__what { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* The middle. Tinted rather than ruled, so it reads as a block of context
   rather than as another row of the card. */
.scard__know {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 9px 10px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.scard__knowrow { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.scard__knowrow .num { font-size: 12.5px; color: var(--text-2); }

/* Pinned to the bottom so cards in a row end level whatever the middle holds. */
.scard__how {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: auto;
  padding-top: 2px;
}
.scard__how .num { font-size: 16px; }

/* A fixed rail, not min-height. The four states hold different things — two
   buttons, or a dot and a sentence — and the cards have to end level, so the
   height is set and the contents centre inside it. */
.scard__foot {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 42px;
  box-sizing: border-box;
  margin: 4px -16px -15px;
  padding: 0 16px;
  border-top: var(--hair) solid var(--border-soft);
}
.scard__foot--ask { background: var(--surface-2); justify-content: space-between; }

/* Rendered on cards with no event to report, and deliberately empty. The rail
   is what makes every card end at the same height and puts the divider in the
   same place on all of them; leaving it out made cards with nothing to say a
   different shape from cards with something to say, which read as a card
   missing a piece rather than a quiet one. Structure repeats, wording does
   not — an empty rule is not the same as printing "manual approval" twenty
   times. */
.scard__foot--none { border-top-color: var(--border-soft); }
.scard__dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.scard__footlabel { font-size: 12.5px; color: var(--text-3); line-height: 1.4; }
/* The size being approved, named before the button that approves it. */
.scard__size { font-size: 12px; color: var(--text-2); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.scard__foot .approve-actions { display: flex; gap: 6px; flex: none; }
.scard__foot .btn { box-sizing: border-box; }

/* The grid's own header, now that there is no card to hang one on. */
.stream-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.stream-note { margin-bottom: 12px; }

.badge--sym { font-size: 12px; padding: 4px 7px; color: var(--text); }

/* --------------------------------------------------------------------------
   9. Badges, pills, chips
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: var(--r-sm);
  border: var(--hair) solid var(--border);
  color: var(--text-3);
  background: var(--surface-2);
  white-space: nowrap;
}

.badge--up {
  color: var(--up-text);
  border-color: transparent;
  background: var(--up-soft);
}

.badge--down {
  color: var(--down);
  border-color: transparent;
  background: var(--down-soft);
}

.badge--primary {
  color: var(--accent);
  border-color: transparent;
  background: var(--primary-soft);
}

/* Operational states need a third and fourth reading beyond up and down. `warn`
   is "late or degraded, not yet broken"; `muted` is "no signal at all", which is
   deliberately not red — a job that has never run is usually unconfigured rather
   than failing, and colouring it as an error sends the reader to the wrong place. */
.badge--warn {
  color: var(--warn);
  border-color: transparent;
  background: var(--warn-soft);
}

.badge--muted {
  color: var(--text-3);
  border-color: var(--border);
  background: transparent;
}

.badge--solid {
  background: var(--primary);
  color: var(--primary-text);
  border-color: var(--primary);
}

.badge--src {
  background: transparent;
  color: var(--text-3);
  letter-spacing: 0.8px;
}

/* Channel type, and only when it is not a person. It sits beside the source
   badge and reads as a peer of it, but carries a border because it is the rarer
   of the two and marks an exception — see iq_channel_type_badge(). Still not
   coloured: like the source, it is a fact about the channel rather than a
   signal, and colour here would compete with P&L. */
.badge--kind {
  background: transparent;
  color: var(--text-2);
  border-color: var(--border-2, var(--border));
  letter-spacing: 0.8px;
}

.badge__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  border: var(--hair) solid var(--border);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-3);
  transition: all 0.15s var(--ease);
}

.pill:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.pill--xs {
  padding: 3px 9px;
  font-size: 10px;
  cursor: pointer;
}

/* Follow state: quiet, with the check only shown once following */
.btn__check {
  display: none;
  align-items: center;
  color: var(--accent);
}

.btn.is-following {
  color: var(--text-3);
  border-color: var(--border-soft);
  background: transparent;
}

.btn.is-following:hover {
  color: var(--text);
  border-color: var(--border);
}

.btn.is-following .btn__check {
  display: inline-flex;
}

.pill.is-active {
  background: var(--primary-soft);
  border-color: var(--primary-line);
  color: var(--primary);
}

/* Live indicator */
.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--up);
  position: relative;
  flex-shrink: 0;
}

.live-dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--up);
  opacity: 0.5;
  animation: pulse 2.4s var(--ease) infinite;
}

@keyframes pulse {
  0% {
    transform: scale(0.7);
    opacity: 0.6;
  }
  70% {
    transform: scale(1.5);
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   10. Avatars
   -------------------------------------------------------------------------- */

/* --h is set per channel; lightness is chosen per theme so initials keep
   contrast on both a black and a white surface. */
/* Neutral by design. Colour in this interface is reserved for signal —
   P&L direction and primary actions — not for identity. */
.avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--r-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.4px;
  flex-shrink: 0;
  text-transform: uppercase;
  overflow: hidden;
  color: var(--text-3);
  background: var(--surface-2);
  border: var(--hair) solid var(--border);
}

.avatar--lg {
  width: 48px;
  height: 48px;
  font-size: 15px;
}

.avatar--sm {
  width: 26px;
  height: 26px;
  font-size: 9.5px;
}

/* The account avatar is a top-bar control, so it matches their height rather
   than the inline avatars used in feeds and the palette. */
.avatar--ctl {
  width: var(--ctl-h);
  height: var(--ctl-h);
  font-size: 10px;
}

/* --------------------------------------------------------------------------
   10b. Account menu — everything account-level, out of the primary nav
   -------------------------------------------------------------------------- */

.acct {
  position: relative;
}

.acct__btn {
  display: block;
  border-radius: var(--r-sm);
}

.acct__btn:hover .avatar,
.acct__btn[aria-expanded="true"] .avatar {
  color: var(--text);
  border-color: var(--border-strong);
}

.acct__menu {
  position: absolute;
  top: calc(100% + 9px);
  right: 0;
  z-index: 60;
  width: 264px;
  padding: 5px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.acct__head {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 10px 12px;
}

.acct__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: 13px;
}

.acct__group {
  padding-top: 5px;
  border-top: var(--hair) solid var(--border-soft);
}

.acct__item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  color: var(--text-2);
  transition: background 0.12s var(--ease);
}

.acct__item:hover {
  background: var(--elevated);
  color: var(--text);
}

/* Sign out has to POST, so it is a button in a form rather than a link. These
   rules make it sit in the menu as though it were one of the anchors. */
.acct .signout {
  display: block;
}

.acct__item--btn {
  width: 100%;
  font: inherit;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
}

/* A menu row holding a control rather than a link. Same rhythm as .acct__item
   but inert, so hovering the row doesn't imply the whole thing is clickable. */
.acct__row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  color: var(--text-2);
}

.acct__row .modesw {
  margin-left: auto;
}

.acct__icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  color: var(--text-3);
  background: var(--surface-2);
  border: var(--hair) solid var(--border-soft);
}

.acct__item:hover .acct__icon {
  color: var(--text-2);
}

/* Connection health. The only place green means "live" rather than "up". */
.acct__dot {
  width: 6px;
  height: 6px;
  margin-left: auto;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--up);
}

/* --------------------------------------------------------------------------
   11. Tables
   -------------------------------------------------------------------------- */

.table-wrap {
  overflow-x: auto;
}

/* A table that scrolls inside its own card instead of down the page.
   
   For the channel idea log, which lists every idea a channel has published rather
   than the most recent sixty. Some channels have thousands, and a table that long in
   normal flow buries everything under it — the point of holding it in a pane is that
   the profile above stays on screen while the log is read.

   The header pins itself with no extra rule: .tbl thead th is already
   position: sticky; top: 0, which resolves against the nearest scrolling ancestor.
   In normal flow that is the page, which is why the same declaration sticks headers
   to the viewport on every other table; here it becomes this box.

   The height is in vh rather than a row count so it fits the window it is read in,
   and floors at 320px so a short laptop window still shows a usable pane. */
.table-wrap--pane {
  overflow-y: auto;
  max-height: max(320px, 62vh);
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

/* Sticky cells are transparent by default and the rows scroll under them. The
   header already carries --surface-2; this closes the hairline gap that a
   1px border on a fractional device pixel leaves above it. */
.table-wrap--pane .tbl thead th {
  box-shadow: 0 var(--hair) 0 var(--border);
}

.tbl {
  width: 100%;
  font-size: 13px;
}

.tbl thead th {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
  text-align: right;
  /* 13px, matching tbody td. At 14 the label missed its own column by a pixel. */
  padding: 11px 13px;
  border-bottom: var(--hair) solid var(--border);
  white-space: nowrap;
  background: var(--surface-2);
  position: sticky;
  top: 0;
  z-index: 2;
}

.tbl thead th:first-child {
  text-align: left;
  padding-left: var(--pad);
}

.tbl thead th:last-child {
  padding-right: var(--pad);
}

.tbl th.is-sortable {
  cursor: pointer;
  user-select: none;
  transition: color 0.15s var(--ease);
}

.tbl th.is-sortable:hover {
  color: var(--text);
}

/* Out of flow, because an invisible arrow was still taking its width.
   opacity:0 hides a glyph but keeps its box, so every sortable header's label
   sat 9px left of the column it labels — the arrow (6.1px) plus its margin —
   while the data below stayed flush right. Absolute positioning inside the
   already-sticky th puts it at the same place on screen and none in the flow. */
.tbl th .sort-arrow {
  position: absolute;
  opacity: 0;
  margin-left: 3px;
  font-size: 9px;
}

/* The heading itself is the link, since 2026-09-09 — the sort is served, not
   done in the DOM, so it has to be a real navigation. It inherits the th's type
   and colour so the row still reads as a header rather than a row of links, and
   fills the cell so the pointer and the hit area agree. */
.tbl thead th > a.th__sort {
  display: block;
  color: inherit;
  text-decoration: none;
  font: inherit;
  letter-spacing: inherit;
}

.tbl thead th > a.th__sort:focus-visible {
  outline: 2px solid var(--primary-line);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Dim on every sortable heading, solid on the one in force. Before this the
   arrow appeared only on the active column, so nothing marked the other five as
   sortable and nothing distinguished them from the four that are not — the
   reader had to hover each one to find out. A always-visible mark is what makes
   "these sort, those do not" legible without a sentence explaining it. */
.tbl th.is-sortable .sort-arrow {
  opacity: 0.32;
}

.tbl th.is-sortable:hover .sort-arrow {
  opacity: 0.7;
}

.tbl th.is-sorted {
  color: var(--text);
}

.tbl th.is-sorted .sort-arrow {
  opacity: 1;
}

.tbl tbody td {
  padding: 11px 13px;
  border-bottom: var(--hair) solid var(--border-soft);
  text-align: right;
  vertical-align: middle;
  white-space: nowrap;
}

.tbl tbody td:first-child {
  text-align: left;
  padding-left: var(--pad);
}

.tbl tbody td:last-child {
  padding-right: var(--pad);
}

/* A cell holding a sentence rather than a figure.
   
   nowrap above is right for the columns this table is mostly made of — "45s ago"
   and "305.1s" are single tokens and breaking one in half reads as corruption —
   and wrong for any cell carrying an explanation. Without this the engine detail
   column rendered "3 inserted, 36475 updates, 1 renamed, 4 delisted" as one long
   clipped line that ran under the controls beside it.

   Named rather than patched inline because this is the third cell to want it: the
   two drawer variants below reset white-space for the same reason and predate it.
   
   Left-aligned as part of the same decision. The default is right, which suits a
   column of numbers being compared down the page; prose read from the right edge
   has a ragged left margin and no two rows start in the same place.

   break-word for the unbroken tokens that turn up in an error message — a path or
   a URL has nowhere to wrap and would push the column wide on its own.

   Qualified with .tbl tbody rather than written as a bare .cell--prose, which is
   what it was first and which did nothing at all. `.tbl tbody td` carries a class
   and two element names, so a lone class selector loses to it on specificity and
   both declarations below were silently discarded — the column kept its nowrap and
   its right alignment and looked exactly as broken as before the class existed.
   The inline style on the control cell beside it had been hiding the same trap,
   because an inline declaration wins without having to be specific. */
.tbl tbody td.cell--prose {
  white-space: normal;
  text-align: left;
  overflow-wrap: break-word;
  max-width: 280px;
}

.tbl tbody tr {
  transition: background 0.12s var(--ease);
}

.tbl tbody tr:hover {
  background: var(--surface-2);
}

.tbl tbody tr:last-child td {
  border-bottom: none;
}

.tbl--clickable tbody tr {
  cursor: pointer;
}

.tbl .cell-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.2px;
}

/* MarketIQ Score cell.

   Nothing of its own: it renders as `cell-num t-2`, the same as Measured beside
   it, and that is deliberate. It carried a 20px numeral over a 68px bar, which
   made it the widest column in the table while stating one number twice — the
   bar's width WAS the score, drawn. Then it briefly carried the reliability band
   as a tone on the numeral, which dimmed developing scores and muted thin ones;
   since most channels are one or the other, the whole column read as washed out
   next to Measured, i.e. as low contrast rather than as low confidence.

   The band lives in the cell's tooltip. Note it is still never coloured in the
   signed sense, unlike the return and edge columns: those are quantities where
   green and red mean something, while a score is a rank among peers, and a red 30
   would tell a reader the channel lost money, which it may not have. */

/* Second column now, so it no longer inherits the first-child left alignment. */
.tbl th.cell-left,
.tbl tbody td.cell-left {
  text-align: left;
}

/* The track record band, under the score rather than beside it. Reported apart
   from the score because it predicts a channel not collapsing rather than it
   earning more — see migration 059 — and a reader scanning for a safe channel is
   asking a different question from one scanning for a strong one. */
.score__band {
  display: block;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-4);
  margin-top: 1px;
}

.score__band--established {
  color: var(--up-text);
}

.score__band--limited {
  color: var(--text-4);
}

/* Channel identity cell */
.chan {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}

.chan__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.chan__name {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.1px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.chan__sub {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.5px;
  color: var(--text-3);
  text-transform: uppercase;
}

/* The hold time a Best row is measured at, under its accuracy rate. The same
   small print as .chan__sub, and on the same line as it.

   Both cells are centred in the row, so their second lines only meet if the two
   stacks are the same height and built the same way. The channel stack is the
   name's line (13px at the table's 1.45), a 2px gap, then the small print. This
   mirrors it part for part: the rate is put on the name's line height rather
   than its own, then the same 2px. When the badge under the name carried 3px
   of padding this carried it too; both gave it up on 2026-10-02. */
.acc--at .cell-num {
  display: block;
  line-height: 18.85px;   /* .chan__name's line: 13px x 1.45 */
}

.acc__at {
  display: block;
  margin-top: 2px;        /* .chan__body's gap, and Lite's .lacc__of */
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.5px;
  color: var(--text-3);
  text-transform: uppercase;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   12. Trend dots (last 5 ideas)
   -------------------------------------------------------------------------- */

.trend {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.trend__dot {
  width: 6px;
  height: 14px;
  border-radius: 2px;
  background: var(--flat);
  opacity: 0.35;
}

.trend__dot--up {
  background: var(--up);
  opacity: 1;
}

.trend__dot--down {
  background: var(--down);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   13. Meters & progress
   -------------------------------------------------------------------------- */

.meter {
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--border-soft);
  overflow: hidden;
  width: 100%;
}

.meter__fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-pill);
  transition: width 0.4s var(--ease);
}

.meter__fill--up {
  background: var(--up);
}
.meter__fill--down {
  background: var(--down);
}

.meter--tall {
  height: 6px;
}

/* A bar for work whose total is not known yet — the reading phase of a backtest,
   where the denominator does not exist until the query returns.

   It slides rather than filling, because a bar that sits still at 35% is read as
   a bar that is stuck at 35%, and one that fills to a percentage nobody counted
   is a made-up number. This one claims only that something is happening.

   The fill's own width transition is turned off for the duration: the two
   animations fight, and the result is a bar that stutters. */
.meter[data-progress-indeterminate] {
  position: relative;
  overflow: hidden;
}

.meter[data-progress-indeterminate] .meter__fill {
  transition: none;
  animation: iq-meter-slide 1.4s var(--ease) infinite;
}

@keyframes iq-meter-slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(340%);
  }
}

/* Respects a reader who has asked for less motion: the bar stops sliding and
   sits there, which is the same information without the movement. */
@media (prefers-reduced-motion: reduce) {
  .meter[data-progress-indeterminate] .meter__fill {
    animation: none;
  }
}

/* .acc / .acc__bar / .acc__fill were the accuracy scan bar beside the rate in
   the channel tables. Removed with the markup on 2026-09-09 — see
   iq_accuracy_cell() in lib/ui.php for why. The cell is now a plain .cell-num
   and takes the column's own right alignment, like MarketIQ Score beside it. */

/* --------------------------------------------------------------------------
   14. Allocation strip
   -------------------------------------------------------------------------- */

.alloc-strip {
  display: flex;
  height: 10px;
  border-radius: var(--r-sm);
  overflow: hidden;
  gap: 2px;
  background: var(--surface-2);
}

.alloc-strip__seg {
  height: 100%;
  transition: opacity 0.15s var(--ease);
  min-width: 2px;
}

.alloc-strip__seg:hover {
  opacity: 0.75;
}

.alloc-legend {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.alloc-legend__row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
}

.alloc-legend__swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   15. Forms
   -------------------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.input,
.select {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--r);
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.3px;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.input::placeholder {
  color: var(--text-4);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  font-size: 11px;
}

.input:focus,
.select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface-2);
}

.select {
  appearance: none;
  text-transform: uppercase;
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 15px) 50%, calc(100% - 11px) 50%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
  padding-right: 30px;
}

.input--search {
  padding-left: 34px;
  text-transform: none;
}

/* Inline in a row of controls rather than stacked in a form, so it matches the
   height of .btn--sm beside it. Width is left to the caller: these sit in flex
   rows where 100% would push the buttons off the line. */
.input--sm {
  width: auto;
  padding: 6px 9px;
  font-size: 11.5px;
}

/* Same idea for a select, with the arrow kept clear of the text. The extra
   right padding is .select's, reduced in step with the rest. */
.select--sm {
  width: auto;
  padding: 6px 26px 6px 9px;
  font-size: 11.5px;
  background-position: calc(100% - 13px) 50%, calc(100% - 9px) 50%;
}

.search-wrap {
  position: relative;
  flex: 1 1 240px;
  min-width: 180px;
}

.search-wrap svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  color: var(--text-4);
  pointer-events: none;
}

/* Money input with $ prefix */
.money {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.money__sym {
  position: absolute;
  left: 11px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  pointer-events: none;
}

.money .input {
  padding-left: 24px;
  text-align: right;
  width: 118px;
}

.money--block {
  display: flex;
}

.money--block .input {
  width: 100%;
  text-align: left;
  font-size: 15px;
  padding-top: 10px;
  padding-bottom: 10px;
}

.money--block .money__sym {
  font-size: 15px;
}

/* Range slider */
.range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--border);
  outline: none;
  cursor: pointer;
}

.range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid var(--bg);
  cursor: grab;
  box-shadow: 0 0 0 1px var(--primary);
}

.range::-webkit-slider-thumb:active {
  cursor: grabbing;
}

.range::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid var(--bg);
  cursor: grab;
}

/* Toggle switch */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 38px;
  height: 21px;
  flex-shrink: 0;
}

/* Full-size transparent input keeps the control keyboard- and
   screen-reader-accessible while the track below provides the visuals. */
.switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.switch__track {
  position: absolute;
  inset: 0;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: var(--hair) solid var(--border);
  transition: all 0.18s var(--ease);
  cursor: pointer;
}

.switch__track::before {
  content: "";
  position: absolute;
  width: 15px;
  height: 15px;
  left: 2px;
  top: 2px;
  border-radius: 50%;
  background: var(--text-3);
  transition: all 0.18s var(--ease);
}

.switch input:checked + .switch__track {
  background: var(--primary);
  border-color: var(--primary);
}

.switch input:checked + .switch__track::before {
  transform: translateX(17px);
  background: #fff;
}

.switch input:focus-visible + .switch__track {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Checkbox */
.check {
  width: 16px;
  height: 16px;
  border-radius: var(--r-sm);
  border: var(--hair) solid var(--border-strong);
  background: var(--elevated);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: all 0.15s var(--ease);
}

.check.is-checked {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-text);
}

/* --------------------------------------------------------------------------
   16. Charts (hand-rolled inline SVG, plus two laid out in the document)
   -------------------------------------------------------------------------- */

/* Composition bar — one population split into shares.

   The fill is a run of hairline strokes rather than a solid block: at arm's
   length it reads as one band, and close up the texture separates the segments
   without spending another colour on them. Laid out with flex rather than
   drawn, so the dividers under the bar line up with the segment edges above it
   at any width, and the figures are real text at their real size. */

/* A stroke and its key sit in the same column, so the two are the same width
   without anything having to measure text and keep them in step. Columns are
   set per instance: minmax(min-content, <share>fr), which is the share of the
   row unless the key needs more, in which case the other columns pay for it.

   Not a flex row. Flex would size the two rows independently, and the moment a
   narrow key was bumped up to its text width its stroke would stay narrow and
   the two would drift apart — which is the problem this replaced. */
.barcode {
  display: grid;
  column-gap: 0;
  row-gap: 12px;
}

.barcode__seg {
  grid-row: 1;
  height: var(--barcode-h, 96px);
  /* The Calls column's tick (2px ink, 2px gap), as every barcode draws it
     since 2026-10-05; the border that guaranteed ink on a sliver is not needed
     when the tick starts at 0. */
  background-image: repeating-linear-gradient(
    90deg,
    var(--seg) 0 2px,
    transparent 2px 4px
  );
}

.barcode__key {
  grid-row: 2;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-left: 10px;
  border-left: var(--hair) solid var(--seg);
  white-space: nowrap;
}

.barcode__label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}

.barcode__value {
  font-size: 21px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.2px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.barcode__note {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-4);
  white-space: nowrap;
}

/* The same bar with its keys as rows underneath instead of under their own
   segment. For narrow columns: nothing has to line up, so no segment is widened
   to fit its label and the strokes keep their true proportions. iq_share_bars()
   explains when to reach for this rather than the aligned layout. */
.sharebar {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.sharebar__keys {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.barcode__row {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Colour is the only thing tying a row to its segment now that position cannot,
   so it is a filled dot rather than the hairline border the aligned keys use —
   at 10px a 1px rule beside text is easy to miss. */
.barcode__dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--seg);
}

/* Pushed right, so the three percentages form a column that can be compared
   down rather than read one at a time. */
.barcode__row-value {
  margin-left: auto;
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* A step brighter than a note elsewhere. In the aligned layout the note is a
   third line under a figure and its job is to be available; here it is the count
   the percentage beside it was taken over, which is the thing a reader checks when
   a share looks surprising, and --text-4 on this background is too faint to read
   at 10px. */
.barcode__row .barcode__note {
  flex: 0 0 auto;
  min-width: 44px;
  text-align: right;
  color: var(--text-3);
}

/* Return by hold time. Same stroke language as .barcode, but this is a
   magnitude chart: the columns are the same ideas measured at different exits,
   so they are independent of each other and sum to nothing. */

/* Scrolls rather than crushes. Nine columns need about 470px between them to
   print a figure under each, and below that the choice is a chart you can
   scroll or one whose labels overlap. It used to overlap: at 360px every one
   of the nine values was wider than the column holding it. */
.hbars {
  position: relative;
  display: flex;
  gap: 5px;
  align-items: stretch;
  overflow-x: auto;
  scrollbar-width: thin;
}

.hbars__col {
  flex: 1 0 auto;
  min-width: 52px;
  color: inherit;
  text-decoration: none;
}

.hbars__plot {
  display: flex;
  flex-direction: column;
  height: var(--hbars-h, 150px);
}

/* Half, for the accuracy view: the line a channel has to clear to be worth
   more than a coin. Drawn once across the chart instead of once per column. */
.hbars__ref {
  position: absolute;
  left: 0;
  right: 0;
  border-top: var(--hair) dashed var(--grid-line);
  pointer-events: none;
}

.hbars__ref span {
  position: absolute;
  right: 0;
  top: -14px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 1px;
  color: var(--text-4);
  background: var(--surface);
  padding-left: 5px;
}

.hbars__up {
  display: flex;
  align-items: flex-end;
}

/* The zero line. A border rather than an element of its own, so it follows the
   split between the two halves and still draws when one of them is empty. */
.hbars__dn {
  display: flex;
  align-items: flex-start;
  border-top: var(--hair) solid var(--grid-line);
}

.hbars__up > i,
.hbars__dn > i {
  display: block;
  width: 100%;
  min-height: 2px;
  /* The Calls column's tick (2px ink, 2px gap), as every barcode draws it
     since 2026-10-05. */
  background-image: repeating-linear-gradient(
    90deg,
    var(--seg) 0 2px,
    transparent 2px 4px
  );
}

/* The tick ties the figures to the column above them. Without it they read as
   a separate row of numbers floating under a wide bar. */
.hbars__foot {
  margin-top: 9px;
  padding-left: 8px;
  border-left: var(--hair) solid var(--grid-line);
  overflow: hidden;
}

.hbars__value {
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.hbars__key {
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1px;
  color: var(--text-4);
  white-space: nowrap;
}

/* How many ideas the figure above is averaged over. Dim, because it is a
   caveat rather than a result — until it is small enough to change how the
   result should be read, at which point it stops being dim. */
.hbars__n {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-4);
  white-space: nowrap;
}

.hbars__n--thin {
  color: var(--warn);
}

.hbars__col--on .hbars__foot {
  border-left-color: var(--text-3);
}

.hbars__col--on .hbars__key {
  color: var(--text-2);
}

/* Independent rates, one per row, each read against a tick at half. Horizontal
   because two of them do not earn a plot — see iq_rate_bars(). The fill is the
   stroke pattern the composition bars use, so a card carrying both reads as one
   chart in two parts rather than two charts. */
.ratebar {
  display: grid;
  gap: 9px;
}

/* Deliberately the same row as .barcode__row: flex, 8px gaps, and the percentage and
   count in the same two trailing columns at the same sizes. The two blocks sit in one
   card and describe the same three categories, so their labels start at the same x and
   their figures form a single column down the card. */
.ratebar__row {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Where the composition bar's colour dot goes. Empty here — see iq_rate_bars() — but
   the same 8px, so the labels below line up with the labels above. */
.ratebar__gutter {
  flex: 0 0 auto;
  width: 8px;
}

/* Fixed, unlike .barcode__label, which can take its natural width because nothing
   follows it until an auto margin. Here a track follows, and a natural width would
   start the Long and Short tracks at different places. */
.ratebar__label {
  flex: 0 0 46px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ratebar__track {
  flex: 1 1 auto;
  position: relative;
  display: block;
  height: 22px;
  border-bottom: var(--hair) solid var(--grid-line);
}

.ratebar__fill {
  display: block;
  height: 100%;
  min-width: 2px;
  /* The Calls column's tick (2px ink, 2px gap), as every barcode draws it
     since 2026-10-05. */
  background-image: repeating-linear-gradient(
    90deg,
    var(--seg) 0 2px,
    transparent 2px 4px
  );
}

/* Half. Drawn per track rather than once across the chart, because the tracks are
   grid cells and a single line would have to be positioned against a column it is
   not inside.

   Stands above the fill and overhangs it. At --grid-line and the exact height of the
   track, this disappeared behind a bar that crossed it — a 1px dashed line lost among
   1px strokes 5px apart — which put it out of sight on precisely the bars that clear
   50%, the ones it exists to mark. Brighter, and overhanging by 3px at each end so
   part of it is always against the card rather than against a fill. */
.ratebar__tick {
  position: absolute;
  left: 50%;
  top: -3px;
  bottom: -3px;
  z-index: 1;
  border-left: var(--hair) dashed var(--text-4);
  pointer-events: none;
}

/* Matches .barcode__row-value, so a percentage in this block sits in the same column
   at the same size as a share in the block above. */
.ratebar__value {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* And this matches .barcode__row .barcode__note, down to the 44px that keeps the
   counts right-aligned against each other whatever their digits. Bright enough to
   read, because it is the denominator a reader checks when a rate looks surprising —
   until it is small enough to change how the rate should be read at all. */
.ratebar__n {
  flex: 0 0 auto;
  min-width: 44px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-3);
  white-space: nowrap;
}

.ratebar__n--thin {
  color: var(--warn);
}

.ratebar__note {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.4px;
  color: var(--text-4);
}

.chart {
  width: 100%;
  display: block;
  overflow: visible;
}

/* vector-effect on every stroked element in the chart, not only the two lines
   that had it. The plot is stretched horizontally to fill the card, so without
   it a vertical grid line is drawn half again as thick as a horizontal one and
   a dash pattern is pulled apart along x -- which is what made the dotted grid
   read as sparse dashes on a wide screen. Held to real widths, the pattern is
   the same everywhere and at every card width. */
.chart__grid {
  stroke: var(--grid-line);
  stroke-width: 1;
  stroke-dasharray: 1 5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.chart__ref,
.chart__cross {
  vector-effect: non-scaling-stroke;
}

/* Axis labels, value tag and markers, laid over the plot rather than drawn in
   it. See the note in iq_area_chart(): anything with proportions of its own
   cannot live inside a preserveAspectRatio="none" viewBox. Positions come from
   the renderer as a percentage across and a pixel down. */
.chart-ax {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  color: var(--text-4);
  letter-spacing: 0.5px;
  white-space: nowrap;
  pointer-events: none;
}

/* Right-anchored rather than placed by the renderer -- see the note beside the
   emit. The plot's own right gutter is 6.4% of the card, which is 93px at the
   shell maximum and 23px on a phone, so no single offset both clears the line
   on a wide card and stays inside a narrow one. */
.chart-ax--y {
  right: 1.2%;
  transform: translateY(-50%);
}

.chart-ax--x {
  bottom: 2px;
  transform: translateX(-50%);
}

/* The first label would hang off the left edge if it were centred on its
   point, the way the SVG version anchored it to the start. */
.chart-ax--start {
  transform: none;
}

.chart-dot {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* display rather than opacity: an idle dot still has a box, and this one sits
   at left:0 with a -50% translate until the pointer first moves, which hangs it
   4px off the left of the card and widens the page. It also wants to appear
   instantly under the cursor rather than fade in. */
.chart-dot--cross {
  display: none;
  width: 8px;
  height: 8px;
  border: 2px solid var(--bg);
}

.chart-dot--cross.is-on {
  display: block;
}

/* Right-anchored on the same column as the value axis, which is where it sat
   when the renderer placed it: the tag is the axis reading at the live value,
   so it covers the label it lands on rather than sitting beside it. The 5px
   pulls its padding back so its text aligns with the labels above and below. */
.chart-tag {
  position: absolute;
  right: calc(1.2% - 5px);
  transform: translateY(-50%);
  padding: 2px 5px;
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.3px;
  line-height: 1;
  color: var(--bg);
  white-space: nowrap;
  pointer-events: none;
}

/* After both base rules, not before them: these carry the same specificity, so
   source order decides, and the first version of this block sat above
   .chart-tag and lost to it. The tag sits flush to the card edge and the labels
   clear it by the tag's own horizontal padding, so the two still read as one
   right-aligned column without either hanging off a narrow card. */
@media (max-width: 700px) {
  .chart-ax--y {
    right: 7px;
  }

  .chart-tag {
    right: 2px;
  }
}

.chart__line {
  fill: none;
  stroke-width: 1.75;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The benchmark, drawn over a portfolio or an equity curve.
   --text-2 rather than the --text-4 this held for as long as nothing passed a
   benchmark to draw. That tier is the dimmest in the palette, #4a4a4a on the dark
   ground, and it was chosen when this rule was speculative; the first chart to
   actually use it put a 1.25px dashed #4a4a4a line over a filled area and the S&P
   500 was legible only if you knew to look for it. A benchmark is a data series and
   has to be readable to the same standard as the number beside it, which is what
   --text-2 is for.
   Subordinate to the primary line by being dashed and neutral rather than by being
   dim: the other line is a saturated mint or red at 2px, so there is no ambiguity
   about which is the subject even at equal brightness.
   4 3 rather than 3 3 because at this width an even pattern reads as dotted, and
   dotted reads as a projection or a threshold rather than as a second series. */
.chart__bench {
  fill: none;
  stroke: var(--text-2);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}

/* ---------------------------------------------------------------------------
   Price chart — see iq_price_chart()

   The SVG holds the line and nothing else. Everything with a word in it, and
   every dashed rule, is an element positioned in percentages over the plot, so
   that stretching one viewBox across any card width cannot melt the type or
   stretch a dash pattern.
   --------------------------------------------------------------------------- */

.pchart {
  --pchart-gutter: 64px;
  width: 100%;
  /* Queried against, because what decides whether seven dates fit is the width
     of the chart and not the width of the window. The same chart is rendered
     full-bleed on the idea page and inside an overlay on the channel profile. */
  container-type: inline-size;
}

.pchart__plot {
  position: relative;
  height: var(--pchart-h, 320px);
  margin-right: var(--pchart-gutter);
}

.pchart__svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.pchart__line {
  fill: none;
  stroke-width: 1.75;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.pchart__grid,
.pchart__vgrid {
  position: absolute;
  pointer-events: none;
}

.pchart__grid {
  left: 0;
  right: 0;
  border-top: var(--hair) dashed var(--grid-line);
}

.pchart__vgrid {
  top: 0;
  bottom: 0;
  border-left: var(--hair) dashed var(--grid-line);
}

/* The holding window: from the idea to the exit being measured. Barely there
   on purpose — it is the backdrop to the line, and a window covering most of
   the chart must not read as the subject of it. */
.pchart__band {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--primary);
  opacity: 0.07;
  pointer-events: none;
}

/* The price when the idea was published. Everything else is read against it. */
.pchart__ref {
  position: absolute;
  left: 0;
  right: 0;
  border-top: var(--hair) dashed var(--text-4);
  pointer-events: none;
}

.pchart__pill {
  position: absolute;
  transform: translateY(-50%);
  padding: 3px 7px;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.3px;
  white-space: nowrap;
  pointer-events: none;
}

.pchart__pill--ref {
  left: 0;
  background: var(--bg);
  border: var(--hair) solid var(--border);
  color: var(--text-1);
}

/* Sits in the axis gutter, where it cannot collide with the entry pill. */
.pchart__pill--last {
  left: 100%;
  margin-left: -3px;
  color: var(--bg);
  background: var(--text-2);
}

.pchart__pill--last.is-winning {
  background: var(--up);
}

.pchart__pill--last.is-losing {
  background: var(--down);
}

.pchart__mark {
  position: absolute;
  top: 0;
  bottom: 0;
  pointer-events: none;
}

.pchart__mark--idea {
  border-left: var(--hair) solid var(--primary);
}

.pchart__mark--exit {
  border-left: var(--hair) dashed var(--text-3);
}

.pchart__dot {
  position: absolute;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--primary);
  box-shadow: 0 0 0 2px var(--surface);
}

.pchart__mark--exit .pchart__dot {
  background: var(--text-3);
}

.pchart__flag {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(-50%);
  padding: 2px 5px;
  border-radius: 3px;
  background: var(--primary);
  color: var(--primary-text);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.6px;
  white-space: nowrap;
}

.pchart__mark--exit .pchart__flag {
  background: var(--text-3);
  color: var(--bg);
}

.pchart__flag--start {
  transform: none;
}

.pchart__flag--end {
  transform: translateX(-100%);
}

.pchart__tick {
  position: absolute;
  left: 100%;
  padding-left: 9px;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-4);
  letter-spacing: 0.4px;
  white-space: nowrap;
}

.pchart__times {
  position: relative;
  height: 16px;
  margin: 8px var(--pchart-gutter) 0 0;
}

.pchart__time {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-4);
  letter-spacing: 0.4px;
  white-space: nowrap;
}

.pchart__time--start {
  transform: none;
}

.pchart__time--end {
  transform: translateX(-100%);
}

/* Seven dates do not fit in 300px. Every other one does, and the grid keeps
   the positions of the ones dropped, so the axis still reads as regular.
   Narrower still and the year goes too — every label carries the same one. */
@container (max-width: 640px) {
  .pchart {
    --pchart-gutter: 54px;
  }

  .pchart__time:nth-child(even) {
    display: none;
  }
}

@container (max-width: 420px) {
  .pchart__yr {
    display: none;
  }
}

/* Browsers without container queries. Coarser — the viewport is only a proxy
   for the width of the chart — but it fails towards legible. */
@supports not (container-type: inline-size) {
  @media (max-width: 700px) {
    .pchart {
      --pchart-gutter: 54px;
    }

    .pchart__time:nth-child(even) {
      display: none;
    }
  }
}

.spark {
  display: block;
  overflow: visible;
}

.spark__line {
  fill: none;
  stroke-width: 1.4;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Chart hover crosshair */
.chart-host {
  position: relative;
}

.chart-tip {
  position: absolute;
  pointer-events: none;
  background: var(--elevated);
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  padding: 8px 11px;
  font-family: var(--font-mono);
  font-size: 11px;
  box-shadow: var(--shadow);
  opacity: 0;
  transition: opacity 0.12s var(--ease);
  z-index: 5;
  white-space: nowrap;
}

.chart-tip.is-on {
  opacity: 1;
}

.chart-tip__date {
  color: var(--text-3);
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  margin-bottom: 3px;
}

.chart-tip__val {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* The benchmark's value at the crosshair, under the portfolio's own. Smaller and
   set apart by a rule, because it is the reference and not the subject: the eye
   should land on what the account did and find the comparison underneath. */
.chart-tip__bench {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  margin-top: 4px;
  padding-top: 4px;
  border-top: var(--hair) solid var(--border);
  white-space: nowrap;
}

/* Names for the two lines, above the chart.
   Only rendered when a benchmark is drawn — see iq_area_chart. A single line
   needs no key, and one that says "Account value" over an obvious account value
   is furniture. */
.chart-legend {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-bottom: 8px;
  font-size: 11px;
  color: var(--text-3);
}

.chart-legend__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* A short bar rather than a dot, so the dashed key can read as dashed. A 3px dot
   cannot show a stroke pattern, which is the only thing distinguishing the two
   lines on the chart itself. */
.chart-legend__key {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 1px;
}

/* Matched to .chart__bench above, pattern and colour both, so the key is a sample
   of the line rather than an approximation of it. */
.chart-legend__key--bench {
  background: repeating-linear-gradient(
    to right,
    var(--text-2) 0 4px,
    transparent 4px 7px
  );
}

/* Donut */
.donut-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.donut-center {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   17. Idea stream
   -------------------------------------------------------------------------- */

.stream {
  display: flex;
  flex-direction: column;
}

/* An anchor now that each row opens its idea, so the link colour and underline
   have to be reset — the row's own text colours are set per element inside it. */
.stream__item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px var(--pad);
  border-bottom: var(--hair) solid var(--border-soft);
  transition: background 0.12s var(--ease);
  color: inherit;
  text-decoration: none;
}

.stream__item:hover {
  background: var(--surface-2);
}

.stream__item:last-child {
  border-bottom: none;
}

.stream__dir {
  width: 26px;
  height: 26px;
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-size: 12px;
}

.stream__dir--long {
  background: var(--up-soft);
  color: var(--up-text);
}

.stream__dir--short {
  background: var(--down-soft);
  color: var(--down);
}

.stream__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stream__sym {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.3px;
}

.stream__meta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.5px;
  color: var(--text-3);
  text-transform: uppercase;
}

.stream__right {
  text-align: right;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-end;
}

/* A queued entry is work, so the row itself is not a link — the symbol is.
   Buttons sit in the same right column the timestamp used to occupy alone. */
.stream__item--approve .stream__body {
  color: inherit;
  text-decoration: none;
}

.stream__item--approve .stream__body:hover .stream__sym {
  color: var(--primary);
}

.approve-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
  justify-content: flex-end;
}

/* --------------------------------------------------------------------------
   18. Channel cards (leaderboard grid)
   -------------------------------------------------------------------------- */

.chan-card {
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  padding: var(--pad);
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: border-color 0.15s var(--ease);
}

/* Border only — cards holding numbers should not move under the cursor. */
.chan-card:hover {
  border-color: var(--border-strong);
}

.chan-card__metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  padding-top: 14px;
  border-top: var(--hair) solid var(--border-soft);
}

.chan-card__metric {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* --------------------------------------------------------------------------
   19. Empty / misc states
   -------------------------------------------------------------------------- */

.empty {
  padding: 56px 20px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.empty__icon {
  width: 34px;
  height: 34px;
  color: var(--text-4);
}

.divider {
  height: var(--hair);
  background: var(--border-soft);
  border: none;
  margin: 0;
}

.notice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-panel);
  border: var(--hair) solid var(--border);
  background: var(--surface-2);
  font-size: 12px;
  color: var(--text-2);
}

.notice--primary {
  border-color: var(--primary-line);
  background: var(--primary-soft);
}

/* For a notice about something that is wrong rather than merely worth knowing:
   a service that is down, a figure that cannot be shown. Tinted with the
   down/loss colour, which is the one the eye already reads as "attend to this"
   everywhere else on the page. */
.notice--warn {
  border-color: var(--down);
  background: var(--down-soft);
  color: var(--text);
}

.notice--warn svg {
  color: var(--down);
}

.notice svg {
  flex-shrink: 0;
  color: var(--text-3);
}

/* Connection list — one row per external venue, linked or not. */
.conn__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-top: var(--hair) solid var(--border-soft);
}

.conn__row:first-child {
  border-top: none;
}

/* What a live connection is actually allowed to do. Sits under its row rather
   than in it, because the scope of granted access deserves a full line. */
.conn__perm {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 18px 12px 60px;
  font-size: 11.5px;
  color: var(--text-3);
}

.conn__perm svg {
  flex-shrink: 0;
  color: var(--text-4);
}

@media (max-width: 700px) {
  .conn__row {
    flex-wrap: wrap;
  }
  .conn__perm {
    padding-left: 18px;
  }
  .hide-sm {
    display: none;
  }
}

/* Key/value facts. Labels stay quiet so the values carry the row. */
.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 9px 16px;
  font-size: 13px;
}

.kv dt {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
  align-self: center;
}

.kv dd {
  text-align: right;
  color: var(--text);
}

/* Banner strip at very top, like arrow's maintenance bar */
.strip {
  background: var(--surface-2);
  border-bottom: var(--hair) solid var(--border-soft);
  text-align: center;
  padding: 6px 16px;
}

/* --------------------------------------------------------------------------
   19a. Activity page — day groups, feed rows, tape, split bar
   -------------------------------------------------------------------------- */

.split {
  display: flex;
  gap: 2px;
  height: 5px;
  margin-top: 9px;
  border-radius: 999px;
  overflow: hidden;
}

.split__long {
  background: var(--up);
  border-radius: 999px 0 0 999px;
}

.split__short {
  background: var(--down);
  border-radius: 0 999px 999px 0;
}

.tape {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  margin-bottom: 14px;
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--r-panel);
  background: var(--surface-2);
  overflow-x: auto;
  scrollbar-width: none;
}

.tape::-webkit-scrollbar {
  display: none;
}

.tape__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  background: var(--elevated);
  border: var(--hair) solid var(--border-soft);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.4px;
}

.day {
  margin-bottom: 18px;
}

/* Sticks under the topbar so you always know which day you are reading */
.day__head {
  position: sticky;
  top: var(--nav-h);
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0 8px;
  background: var(--bg);
}

.day__label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--text-2);
  flex-shrink: 0;
}

.day__rule {
  flex: 1;
  height: var(--hair);
  background: var(--border-soft);
}

.feed {
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  overflow: hidden;
}

.feed__row {
  display: grid;
  grid-template-columns: 48px 24px minmax(120px, 1fr) minmax(150px, 1.2fr) 84px 70px minmax(118px, auto) 74px;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  border-bottom: var(--hair) solid var(--border-soft);
  color: inherit;
  transition: background 0.12s var(--ease);
}

.feed__row:last-child {
  border-bottom: none;
}

.feed__row:hover {
  background: var(--elevated);
}

.feed__row--approve {
  grid-template-columns: 48px 24px minmax(120px, 1fr) minmax(140px, 1.2fr) 84px 24px minmax(70px, auto) auto;
}

.feed__row--approve .feed__sym {
  color: inherit;
  text-decoration: none;
}

.feed__row--approve .feed__sym:hover strong {
  color: var(--primary);
}

.feed__time {
  font-size: 11px;
  color: var(--text-4);
  white-space: nowrap;
}

.feed__dir {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-sm);
}

.feed__dir--long {
  color: var(--up-text);
  background: var(--up-soft);
}

.feed__dir--short {
  color: var(--down);
  background: var(--down-soft);
}

/* Trade side and account events are facts, not directional calls, so their
   glyph stays neutral and the number on the right carries the only colour.
   It shares the geometry of .feed__dir so mixed rows stay on one axis. */
.feed__kind {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-sm);
  color: var(--text-3);
  background: var(--surface-2);
  border: var(--hair) solid var(--border-soft);
}

.feed__sym {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.feed__sym strong {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.3px;
}

.feed__chan {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.feed__entry {
  text-align: right;
  font-size: 12px;
  color: var(--text-2);
}

/* Notional under a fill price, so the money column carries both without
   borrowing a column that means something else on other row kinds. */
.feed__sub {
  display: block;
  margin-top: 1px;
  font-size: 10px;
  color: var(--text-4);
}

/* Account events are a sentence, not a set of measurements. */
.feed__event {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.feed__conf {
  display: flex;
  align-items: center;
  gap: 7px;
}

/* Share count. Occupies the same slot as a conviction bar on idea rows, but
   it is a number, so it right-aligns with the other numbers in its column. */
.feed__size {
  text-align: right;
  font-size: 12px;
  color: var(--text-2);
}

.feed__tags {
  display: flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
}

.feed__chg {
  text-align: right;
  font-size: 13px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 3px;
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  white-space: nowrap;
}

.chip--open {
  color: var(--primary);
  border-color: var(--primary-line);
  background: var(--primary-soft);
}

/* Auto-traded is a fact about how the order was placed, not a warning. */
.chip--auto {
  color: var(--text-3);
  border-color: var(--border-soft);
  background: transparent;
}

/* The tag chip, the row of chips under a channel name, and the remove-this-tag
   × inside a chip all used to live here. All three are gone: a channel's group
   is now the Desk column on Manage and the inline control on a profile, neither
   of which is a chip, and a single-valued desk has nothing to remove — moving a
   channel off a desk is choosing None. */

/* The channel's desk on its profile, edited in place.
   Styled as text with a hover state rather than as a button, because the desk is
   read far more often than it is changed and a bordered control next to Allocate
   and Follow would claim to be a third action of the same weight. */
.deskinline {
  display: inline-flex;
  align-items: center;
}

.deskinline__btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 190px;
  padding: 4px 7px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.deskinline__btn > span {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.deskinline__btn:hover {
  background: var(--surface-2);
  color: var(--text-1);
}

/* On no desk. Quieter than a desk name, so the control reads as an invitation
   rather than as though the channel were already seated somewhere. */
.deskinline__btn.is-empty {
  color: var(--text-4);
}

.deskinline__input {
  width: 150px;
}

/* ---------------------------------------------------------------------------
   Rules indicator — what the auto-trader may do, in one table cell
   ---------------------------------------------------------------------------
   Three marks and a cap, in the width a read-only number used to take. The
   letters carry the meaning and the arrows carry the direction, so a column of
   these can be scanned for "which of my channels will act on a short" without
   reading any of them individually.
   ------------------------------------------------------------------------ */
.rules {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 4px 7px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  cursor: pointer;
  transition: background 0.12s ease;
}

.rules:hover,
.rules:focus-visible {
  background: var(--surface-2);
}

.rules__marks {
  display: inline-flex;
  gap: 5px;
}

.rules__mark {
  display: inline-flex;
  align-items: baseline;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-1);
}

/* The instruction, tight against the letter it belongs to — "L↑" reads as one
   token, "L ↑" reads as two. */
.rules__mark > i {
  font-style: normal;
  font-size: 12px;
  line-height: 1;
  margin-left: 1px;
}

/* A direction the trader ignores. Dimmed rather than hidden: which directions a
   channel publishes and which it is allowed to trade are different facts, and
   an absent mark would conflate them. */
.rules__mark.is-off {
  color: var(--text-4);
  font-weight: 500;
}

.rules__cap {
  font-size: 10px;
  color: var(--text-3);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   The row expander and the drawer it opens
   ------------------------------------------------------------------------ */

/* Stands where the bulk-selection checkbox used to. Quiet until the row is
   hovered: a column of carets down a 152-row table is visual noise for
   something used a few times a session, but it has to be findable without
   hunting, so it fades in with the row rather than only on its own hover. */
.expander {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  /* --text-2, not --text-4. At --text-4 this was the faintest token in the
     palette — #4a4a4a on the dark theme, #8a8a8a on the light one — drawn at
     11px with no background and no border, which made the one control that opens
     a row nearly invisible in both. A disclosure arrow nobody can see is a
     drawer nobody knows is there. */
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

/* Full strength on the row under the cursor, so the arrow you are about to
   click is unambiguous even in a list of twenty. */
tr:hover > td > .expander {
  color: var(--text);
}

.expander:hover,
.expander:focus-visible {
  background: var(--surface-2);
  color: var(--text-1);
}

/* Turns a quarter clockwise when its row is open, so the arrow points at the
   drawer it produced rather than merely changing colour. */
.expander__caret {
  /* 13px rather than 11px. The glyph is a small triangle to begin with, so a
     couple of points is the difference between a mark and an arrow. */
  font-size: 13px;
  line-height: 1;
  transition: transform 0.14s ease;
}

.expander[aria-expanded='true'] .expander__caret {
  transform: rotate(90deg);
}

.expander[aria-expanded='true'] {
  color: var(--text-1);
}

/* The expanded row and its drawer, marked as one thing.

   A background tint alone was not enough: the tint that reads as "expanded"
   would be --surface-2, which is exactly what :hover already uses, so an open
   row looked identical to whichever row the pointer happened to be over. The
   accent rule down the left edge says it instead, and carries down the drawer
   so the two read as a single block — which matters once the drawer is tall
   enough to push its own row towards the top of the viewport. */
.tbl tbody tr.is-expanded {
  background: var(--primary-soft);
}

.tbl tbody tr.is-expanded > td:first-child {
  box-shadow: inset 2px 0 0 var(--primary);
}

/* No border-top, because the expanded row above already has one and two
   hairlines in a row read as an empty band.

   white-space is reset because .tbl tbody td sets nowrap — right for a cell
   holding a number, wrong for a cell holding two sentences of explanation,
   which ran off the side of the table as one clipped line. */
.drawer > td {
  background: var(--primary-soft);
  padding: 0;
  box-shadow: inset 2px 0 0 var(--primary);
  white-space: normal;
}

/* Two columns: the three direction rules on the left, the position cap and its
   explanations on the right. Stacking them would make the drawer taller than
   the rows it is meant to sit among, which is what a modal was doing wrong in
   the first place.

   Capped rather than spanning the table. These are settings, not data, and a
   label-and-input pair stretched across 1430px puts the two so far apart that
   the pairing stops being visible — the reason a form is a column and a table
   is not. */
/* One column as of 2026-09-02. It was two — minmax(240px,1fr) twice — which put
   the rules editor's two sections side by side and squeezed each column below
   the width of the controls inside it, so a 230px select overlapped the label
   text of the row beside it. The sections are sequential questions, not parallel
   ones: what to do when it publishes, then how each position is taken. */
.drawer__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  padding: 20px 18px 18px;
  /* No cap. The artboard runs the rows the full width of the drawer, with the
     controls on the far right — capping it pulled them back into the middle. */
}

/* The other kind of drawer: a set of figures and a sentence, rather than a form.
   Not .drawer__body, which is a two-column grid built for the rules editor —
   putting a figures row and a paragraph into it lands the paragraph in column
   two, beside the figures instead of under them, which reads as a layout
   accident because it is one.

   A block rather than a grid, so its contents stack in the order they are
   written and a row of figures can wrap on its own terms. */
.drawer__facts {
  padding: 16px 18px 18px;
  max-width: 880px;
  white-space: normal;
}

.drawer__group {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}

/* Spans both columns so the buttons sit at the bottom right of the drawer
   rather than under whichever column happened to be taller. */
.drawer__foot {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 8px;
  padding-top: 4px;
}

@media (max-width: 900px) {
  .drawer__body {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Section header inside the allocation table when grouping is on. It is a real
   table row rather than a separate element so the columns stay aligned with the
   rows beneath it. */
.grouprow > td {
  background: var(--surface-2);
  border-top: var(--hair) solid var(--border);
  padding-top: 9px;
  padding-bottom: 9px;
}

.grouprow__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.grouprow__caret {
  transition: transform 0.15s ease;
  color: var(--text-4);
}

.grouprow.is-collapsed .grouprow__caret {
  transform: rotate(-90deg);
}

.grouprow__name {
  font-weight: 600;
  font-size: 12.5px;
}

/* The group's totals, pushed to the right so they line up as a column of their
   own down the table rather than trailing each name at a different offset. */
.grouprow__stats {
  margin-left: auto;
  display: flex;
  gap: 18px;
  align-items: baseline;
}

/* Row-kind label. It repeats on every row of the mixed view, so it says what
   it needs to and takes up no ink doing it. */
.chip--kind {
  padding-left: 0;
  padding-right: 0;
  border-color: transparent;
  background: transparent;
  color: var(--text-4);
}

/* Account rows have no price or size, so they get their own template on the
   same outer rails: the time, glyph and amount stay aligned with every other
   kind of row and the sentence takes the space in between. */
.feed__row--account {
  grid-template-columns: 48px 24px minmax(0, 1fr) minmax(118px, auto) 74px;
}

/* What survives a narrowing window is a priority decision. Price and size are
   the substance of a fill, so they outrank the conviction bar and the share
   count, which is anyway recoverable from value over price. */
@media (max-width: 1180px) {
  .feed__row {
    grid-template-columns: 46px 24px minmax(90px, 1fr) minmax(120px, 1.1fr) 84px minmax(100px, auto) 70px;
  }
  .feed__row--account {
    grid-template-columns: 46px 24px minmax(0, 1fr) minmax(118px, auto) 70px;
  }
  .feed__conf,
  .feed__size {
    display: none;
  }
}

@media (max-width: 700px) {
  .feed__row {
    grid-template-columns: 24px minmax(0, 1fr) 76px 70px;
    gap: 10px;
  }
  .feed__row--account {
    grid-template-columns: 24px minmax(0, 1fr) 70px;
  }
  .feed__time,
  .feed__tags,
  .feed__chan {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   19b. Overlays — modal primitive, command palette
   -------------------------------------------------------------------------- */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh 20px 20px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.16s var(--ease);
}

.scrim.is-open {
  opacity: 1;
  pointer-events: auto;
}

.modal {
  width: 100%;
  max-width: 480px;
  background: var(--surface);
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  transform: translateY(-10px);
  transition: transform 0.18s var(--ease);
  max-height: 76vh;
  display: flex;
  flex-direction: column;
}

.scrim.is-open .modal {
  transform: none;
}

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: 16px var(--pad);
  border-bottom: var(--hair) solid var(--border-soft);
}

.modal__body {
  padding: var(--pad);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* The idea overlay holds a whole page's worth of chart and tables, so it wants
   the width of a page rather than the width of a dialog, and it starts nearer
   the top of the viewport than a short form does. */
.scrim--wide {
  padding: 5vh 20px 20px;
}

.modal--idea {
  max-width: 1120px;
  max-height: 90vh;
}

/* The injected markup spaces its own cards, so the flex gap would double up. */
.modal--idea .modal__body {
  display: block;
  overscroll-behavior: contain;
}

.modal--idea .modal__body[aria-busy='true'] {
  min-height: 320px;
  opacity: 0.45;
}

@media (max-width: 640px) {
  .scrim--wide {
    padding: 0;
  }

  .modal--idea {
    max-width: none;
    max-height: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
  }
}

.modal__foot {
  padding: 14px var(--pad);
  border-top: var(--hair) solid var(--border-soft);
  display: flex;
  gap: 10px;
}

/* Command palette */
.modal--palette {
  max-width: 580px;
}

.palette__search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 16px;
  border-bottom: var(--hair) solid var(--border-soft);
  color: var(--text-4);
}

.palette__input {
  flex: 1;
  border: none;
  background: transparent;
  outline: none;
  padding: 17px 0;
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.2px;
  color: var(--text);
}

.palette__input::placeholder {
  color: var(--text-4);
}

/* The whole palette reads as focused; a ring on the input is noise */
.palette__input:focus-visible {
  outline: none;
}

.palette__list {
  overflow-y: auto;
  padding: 6px;
  min-height: 80px;
}

.palette__group {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-4);
  padding: 10px 12px 6px;
}

.palette__item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  cursor: pointer;
  width: 100%;
  text-align: left;
}

.palette__item.is-active {
  background: var(--elevated-hi);
  box-shadow: inset 0 0 0 1px var(--border);
}

.palette__glyph {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text-2);
}

.palette__item-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.palette__name {
  font-size: 13px;
  font-weight: 500;
}

.palette__meta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-3);
}

.palette__foot {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  border-top: var(--hair) solid var(--border-soft);
}

kbd {
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 3px;
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  color: var(--text-3);
}

/* Nav search trigger */
/* Unused: the search button was removed from the topbar. Cmd-K and / still open
   the palette, so these are kept against the button coming back rather than
   deleted and rewritten later. */
.nav-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--ctl-h);
  padding: 0 10px 0 11px;
  border-radius: var(--r);
  border: var(--hair) solid var(--border);
  background: var(--surface-2);
  color: var(--text-4);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  transition: all 0.15s var(--ease);
  min-width: 150px;
}

.nav-search:hover {
  border-color: var(--border-strong);
  color: var(--text-3);
}

.nav-search svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.nav-search__spacer {
  flex: 1;
}

/* Summary row inside the allocate modal */
.mini-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--border-soft);
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--r);
  overflow: hidden;
}

.mini-stat {
  background: var(--surface-2);
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* --------------------------------------------------------------------------
   20. Toast
   -------------------------------------------------------------------------- */

.toast-host {
  position: fixed;
  bottom: 22px;
  right: 22px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-end;
}

.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-radius: var(--r);
  border: var(--hair) solid var(--border);
  background: var(--elevated);
  box-shadow: var(--shadow);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.4px;
  animation: toast-in 0.25s var(--ease);
}

.toast--ok {
  /* A border, not type — so it keeps the fill token. It was caught by a
     search-and-replace for `color: var(--up);`, which `border-color:` contains
     as a substring; put back deliberately. */
  border-color: var(--up);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* --------------------------------------------------------------------------
   21. Responsive
   -------------------------------------------------------------------------- */

/* Collapse the search trigger to its icon before it squeezes the nav */
@media (max-width: 1060px) {
  .nav-search {
    min-width: 0;
  }
  .nav-search span,
  .nav-search kbd {
    display: none;
  }
}

@media (max-width: 1400px) {
  .grid--5 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  /* Follows the stats row down to three columns and splits 2 and 1, so the chart
     edge stays on a stat boundary instead of landing at three fifths of a row that
     is now divided in thirds. */
  .grid--3-2 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .grid--3-2 > :first-child {
    grid-column: span 2;
  }
  .grid--3-2 > :last-child {
    grid-column: span 1;
  }
}

@media (max-width: 1100px) {
  .grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 860px) {
  /* Sides only. The shorthand used to zero .page's top and bottom too -- this
     rule comes later at equal specificity and <main> is `shell page` -- so on a
     phone every Pro page sat flush under the sticky bar and its last row under
     the fixed bottom bar, and .page's own 76px phone padding never applied.
     Fixed 09/18; .rail's phone margin below changed with it. */
  .shell {
    padding-left: 16px;
    padding-right: 16px;
  }
  .topbar__inner {
    padding: 0 16px;
    gap: 16px;
  }
  .nav {
    gap: 16px;
  }
  .grid--2,
  .grid--3,
  .grid--4 {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Stacked here rather than following the stats row to two columns. Half of an
     860px window is about 400px, and nine hold-time columns need roughly 500px
     before .hbars gives up and scrolls — so the pair stops tracking the stats and
     each card takes the full width instead. */
  .grid--3-2 {
    grid-template-columns: minmax(0, 1fr);
  }
  .grid--3-2 > :first-child,
  .grid--3-2 > :last-child {
    grid-column: span 1;
  }
  /* Five is the idea page's row of outcome figures, and one per row puts the
     chart four screens down. They are short enough to pair up. */
  .grid--5 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .stat__value {
    font-size: 22px;
  }
  .grid--5 .stat__value {
    font-size: 18px;
  }
  .brand__name {
    display: none;
  }
}

@media (max-width: 860px) {
  .scrim {
    padding: 8vh 14px 14px;
  }
  .mini-stats {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 620px) {
  .nav__link {
    font-size: 11px;
  }
  .page-head {
    align-items: flex-start;
  }
  .nav {
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav::-webkit-scrollbar {
    display: none;
  }
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
   Sign in

   The only page outside the app shell, so it carries its own centred layout
   rather than inheriting the header and grid. Everything else is the same
   tokens the rest of the interface uses.
   ------------------------------------------------------------------------ */

.auth {
  min-height: 100vh;
  display: flex;
  background: var(--bg);
}

.auth__wrap {
  margin: auto;
  width: 100%;
  max-width: 380px;
  padding: 40px 24px;
}

.auth__card {
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  padding: 32px 28px 26px;
}

.auth__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 26px;
}

.auth__title {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.2px;
  margin: 0 0 5px;
  color: var(--text);
}

.auth__sub {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0 0 22px;
}

.auth__form {
  display: flex;
  flex-direction: column;
}

.auth__label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 6px;
}

.auth__input {
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text);
  background: var(--elevated);
  border: var(--hair) solid var(--border);
  border-radius: var(--r-sm);
  padding: 0 12px;
  height: 40px;
  width: 100%;
  margin-bottom: 16px;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.auth__input:focus {
  outline: none;
  border-color: var(--primary);
  background: var(--elevated-hi);
}

.auth__input:-webkit-autofill {
  /* Chrome paints its own near-white fill over autofilled inputs, which is
     unreadable on the dark theme. The inset shadow repaints the background
     without fighting the browser over background-color, which it ignores. */
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--elevated) inset;
  caret-color: var(--text);
}

.auth__submit {
  height: 40px;
  width: 100%;
  margin-top: 4px;
}

.auth__error,
.auth__note {
  font-size: 13px;
  line-height: 1.45;
  border-radius: var(--r-sm);
  padding: 10px 12px;
  margin-bottom: 18px;
  border: var(--hair) solid transparent;
}

.auth__error {
  color: var(--down);
  background: var(--down-soft);
  border-color: var(--down);
}

.auth__note {
  color: var(--text-2);
  background: var(--surface-2);
  border-color: var(--border);
}

.auth__foot {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-4);
  margin: 22px 0 0;
  padding-top: 18px;
  border-top: var(--hair) solid var(--border-soft);
}

/* Admin query console. The shared .input style uppercases placeholders and
   tightens tracking for short fields; a statement is neither. */
.admin-sql {
  min-height: 140px;
  resize: vertical;
  text-transform: none;
  letter-spacing: 0;
  font-size: 12.5px;
  line-height: 1.45;
}

.admin-sql::placeholder {
  text-transform: none;
  letter-spacing: 0.2px;
}

.admin-sql-preview {
  display: block;
  max-width: 52rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-result {
  max-height: 480px;
}

/* Pagination footer on a listing card. */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: 12px var(--pad);
  border-top: var(--hair) solid var(--border-soft);
}

/* ==========================================================================
   Dashboard — the A2 layout (2026-09-02).

   Five bordered KPI cards, a donut of exchange allocation and an Overview/Stream
   tab are gone. What replaces them follows one rule from the reference pass: a
   card holds a SECTION, not a figure. Figures live in a strip that shares one
   border; sections get the panel.
   ========================================================================== */

/* Screen-reader-only. The dashboard's h1 is not drawn — the nav names the page
   — but the document still needs one. */
.u-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The rail. Eight figures, one border, one row — where five cards each with
   their own border used to sit.

   It escapes .page's padding on three sides so it sits flush under the nav and
   spans the full shell, which is what A2 draws. The pull must MATCH .page's
   side padding exactly — an earlier version pulled -24px against a computed 0,
   which put the rail 24px past the viewport, clipped the first label and gave
   the page a horizontal scrollbar. */
.rail {
  display: flex;
  align-items: stretch;
  /* 30px below, not 16px. The rail is a full-bleed band with a rule under it, so
     it reads as a header rather than as the first card — and at 16px the next
     card sat close enough to look attached to it. Set on the component rather
     than per page: the rail does the same job on Dashboard, Portfolio, Channels
     and Manage, and a different gap on each is the kind of drift nobody ever
     goes back and reconciles. */
  margin: -28px -32px 30px;
  position: relative;
}

/* The band runs the full viewport, the cells stay in the shell.

   The FILL is painted with box-shadow rather than a positioned pseudo-element.
   Two vw-based attempts both left a real 5.5px horizontal scroll: `calc(50% -
   50vw)` counts the scrollbar in 50vw but not in the client width, so the band
   overshoots by half a scrollbar, and neither `overflow-x: clip` nor `hidden` on
   html absorbed it. A box-shadow takes no part in layout, so it cannot cause
   overflow or scrolling at all and the whole class of bug goes away.

   The RULES are real 1px elements, one per edge, each carrying its own full-bleed
   shadow in the border colour. They are not stacked shadows on .rail: a spread
   shadow paints a filled rectangle expanded outward, not a ring, so a second
   larger spread behind the first is hidden everywhere except 100vmax away. That
   is why the bottom rule was invisible — the band's own spread covered the strip
   the border was meant to show through, and no amount of clipping could reveal
   it without also revealing 100vmax of border colour.

   Each rule is only as wide as .rail (left/right 0), so neither adds scrollable
   overflow; its shadow does the reaching, and its own clip-path trims that
   shadow back to the rule's 1px height. Both sit inside the band's own box —
   top: 0 and bottom: 0 — so they stay within .rail's clip and need no expansion
   of it. */
.rail {
  background: var(--surface-2);

  /* SPREAD, not offset. `-100vmax 0 0` paints a COPY of the box that far to the
     left — and since the copy is only as wide as the rail (1560), an offset
     bigger than that width leaves an uncovered strip between the copy's right
     edge and the rail's left edge. At 1900px viewport that strip was exactly the
     170px of page background showing on each side.

     A spread grows the shadow outward from the box in every direction, so it
     always abuts. clip-path then trims it vertically — without that it would
     paint 100vmax above and below too and flood the page. */
  box-shadow: 0 0 0 100vmax var(--surface-2);
  clip-path: inset(0 -100vmax);
}

/* One rule per edge, the same colour and weight as the dividers between cells,
   so all three read as one set of hairlines rather than three near-misses. */
.rail::before,
.rail::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: var(--hair);
  background: var(--border);
  box-shadow: 0 0 0 100vmax var(--border);
  clip-path: inset(0 -100vmax);
  /* Above the cells' own left borders, so the corners meet cleanly. */
  z-index: 1;
}
.rail::before { top: 0; }
.rail::after  { bottom: 0; }

.rail__cell {
  flex: 1;
  min-width: 0;
  padding: 9px 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rail__cell + .rail__cell { border-left: var(--hair) solid var(--border); }

.rail__value { font-size: 13px; }

/* The lead panel takes the width; the horizon tiles take a fixed column, so the
   chart does not shrink as the viewport does. */
.grid--lead { grid-template-columns: minmax(0, 1fr) 452px; }

.lead__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
}
/* 56px against the rail's 13px. The old page's largest figure was 26px and four
   others matched it, so nothing led. */
.lead__figure {
  font-size: 56px;
  font-weight: 500;
  letter-spacing: -2px;
  line-height: 1;
  margin-top: 10px;
}
.lead__delta { font-size: 14px; margin-top: 12px; }

/* A seven-figure value at 56px is ~410px of mono and does not break, so on a
   phone it pushed Portfolio 67px past the viewport. Scaled with the viewport
   there: 43px at 390, the full 56px again by 510. */
@media (max-width: 860px) {
  .lead__figure { font-size: clamp(32px, 11vw, 56px); letter-spacing: -1px; }
}

.seg--xs .seg__btn { font-size: 10px; padding: 4px 10px; }

/* A head inside a .card--pad, which already pads. Without zeroing all of it the
   title inherits .card__head's `padding: 16px var(--pad)` AND its 56px
   min-height on top of the card's own padding, and sits visibly lower than the
   label in the panel beside it. */
.card__head--bare {
  border-bottom: 0;
  padding: 0;
  min-height: 0;
}

/* Nine horizons as a 3x3 grid. They sat behind a dropdown, one at a time, which
   hid the only thing worth knowing about them — the shape across all nine. */
.hz__note {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-4);
  margin: 8px 0 16px;
}
.hz {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.hz__tile {
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  padding: 13px 12px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  position: relative;
}

/* The peek's tiles change what the panel is measuring, so there they are
   <button>s. Almost nothing is needed to make one look like the <div> beside
   it: the global reset near the top of this file already clears a button's
   background, border, padding and cursor and inherits its font and colour, so
   only the centring is left to undo.

   Nothing here may set a background. `button.hz__tile` is more specific than
   `.hz__tile--up`, so a background declared here would beat the tint and
   flatten the whole grid to one colour -- and the global reset's
   `button { background: none }` loses to the tints on specificity, which is
   exactly what should happen. */
button.hz__tile {
  text-align: left;
}
button.hz__tile:hover { border-color: var(--text-4); }
button.hz__tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button.hz__tile[aria-pressed="true"] { cursor: default; }
/* The horizon a score is about, under the score in the channel table. Sized and
   toned like the rank marker on the other side of the number, so the cell reads
   as one figure with two small annotations rather than as three values. */

.hz__pct { font-size: 17px; }
/* --up-soft / --down-soft are the tints the badges already use, and they carry
   a light-theme value too. Borders stay --border rather than a tinted edge:
   nine tinted boxes each with a tinted border is a lot of chrome for nine
   numbers, and the fill already separates them. */
.hz__tile--up   { background: var(--up-soft); }
.hz__tile--up   .hz__pct { color: var(--up-text); }
.hz__tile--down { background: var(--down-soft); }
.hz__tile--down .hz__pct { color: var(--down); }

/* A horizon never traded. Dimmed rather than absent from the grid: the empty
   slots are part of the shape, and a grid that reflows to fill them would say
   you had traded a different set of horizons than you have. */
.hz__tile--none { opacity: 0.42; }
.hz__tile--none .hz__pct { color: var(--text-4); }

.hz__n {
  position: absolute;
  top: 11px;
  right: 12px;
  color: var(--text-4);
}

/* The activity table. */
.act__sym   { font-weight: 600; }
.act__event { font-size: 12px; color: var(--text-3); }
.act__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  display: inline-block;
  margin-right: 8px;
  vertical-align: middle;
}
.act__dot--up   { background: var(--up); }
.act__dot--down { background: var(--down); }

.methodology {
  margin-top: 26px;
  padding-top: 14px;
  border-top: var(--hair) solid var(--border);
  font-size: 11px;
  line-height: 1.9;
  color: var(--text-4);
  max-width: 110ch;
}

@media (max-width: 1200px) {
  .grid--lead { grid-template-columns: minmax(0, 1fr); }
  .rail { flex-wrap: wrap; }
  .rail__cell { flex: 1 1 25%; }
}

/* The rail's pull has to match .page's side padding exactly (see .rail), and
   below 860px that padding is 16px, not 32px. Pulling 32px there put the band
   16px past each edge of the viewport and gave the page a sideways scroll. The
   top pull stays -28px: .page keeps its 28px on a phone now, so the rail still
   sits flush under the bar. */
@media (max-width: 860px) {
  .rail { margin-left: -16px; margin-right: -16px; }
}

/* ==========================================================================
   Stream — the full account of what your channels did (2026-09-02).

   Split out of the dashboard's Overview/Stream tab. Ordered by what asks before
   what tells: approvals, then states that are true now, then the feed.
   ========================================================================== */

/* The Desk's four figures as cards (CP, 09/18), where the rail used to be. The
   rail pulled itself flush under the nav; cards sit inside the page padding like
   everything below them, 16px apart as the rest of the Desk is. */
.desk-stats {
  margin-bottom: 16px;
}

/* Paired rather than stacked on a phone, the same call .grid--5 makes: the
   figures are short, and one per row put the stream a screen further down. */
@media (max-width: 860px) {
  .desk-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.stream-title {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: -1px;
  line-height: 1;
  margin: 0 0 8px;
}
.stream-sub {
  font-size: 13px;
  color: var(--text-3);
  margin: 0 0 22px;
}

/* The one block on the page that asks rather than tells, and the only one that
   gets an accent. If everything is emphasised nothing is. */
.card--asks { border-left: 2px solid var(--iq-teal); }

.alert-card__kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 12px;
}
.alert-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-2);
  flex: none;
}
.alert-dot--warn { background: var(--down); }

.alert-card__title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 8px;
}
.alert-card__body {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-3);
  margin: 0 0 14px;
}

/* ==========================================================================
   Channels — direction distribution and entry points (2026-09-02).
   ========================================================================== */

/* One bar, four cuts, each a link that narrows the list below. The widths are
   the argument for naming the cuts: the short-biased segment is 1.7% of the
   index and is drawn at 1.7%, so the reason it needs a name is visible rather
   than asserted. */
.dirbar {
  display: flex;
  height: 34px;
  gap: 2px;
  margin-bottom: 12px;
}
.dirbar__seg {
  display: flex;
  align-items: center;
  padding: 0 12px;
  border-radius: var(--r-sm);
  overflow: hidden;
  min-width: 6px;
  transition: filter 0.15s ease;
}
.dirbar__seg:hover  { filter: brightness(1.35); }
.dirbar__seg.is-active { outline: 1px solid var(--text-2); outline-offset: 1px; }
.dirbar__label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  white-space: nowrap;
}

.dirbar__seg--long  { background: var(--up-soft);   color: var(--up-text); }
.dirbar__seg--both  { background: var(--surface-2); color: var(--text-2); }
.dirbar__seg--mixed { background: var(--surface-2); color: var(--text-3); }
.dirbar__seg--short { background: var(--down-soft); color: var(--down); }

.dirbar__note {
  display: flex;
  align-items: baseline;
  gap: 9px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-4);
  margin: 0;
}
.dirbar__chip {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: none;
  transform: translateY(1px);
}

/* An entry point narrows the list; it is not a destination. Styled as a card
   because it holds a section — a count, a claim and the reason — rather than a
   single figure. */
.entry { display: block; }
.entry:hover { border-color: var(--border-strong, var(--text-4)); }
.entry__icon {
  width: 30px;
  height: 30px;
  border-radius: var(--r);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--text-2);
}
.entry__icon.down { background: var(--down-soft); color: var(--down); }
.entry__n { font-size: 30px; letter-spacing: -1px; }
.entry__title { font-size: 15px; font-weight: 600; margin-bottom: 8px; }
.entry__body {
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-3);
  margin: 0 0 14px;
}

/* ==========================================================================
   Channels — the A2 register (2026-09-02).
   ========================================================================== */

.chead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

/* The counts that used to be a banded rail above the title. A sentence rather
   than a row of cells: there are three figures left once the duplicated ones
   are gone, and three figures do not need six borders and a full-bleed band to
   be found. Quieter than the subtitle it sits under, because it is a footnote
   to the page and not a second description of it. */
.chead__stats {
  margin: 9px 0 0;
  font-size: 12.5px;
  color: var(--text-3);
}

.chead__stats .num {
  color: var(--text-2);
}

.chead__sep {
  color: var(--text-4);
  padding: 0 5px;
}
/* Lite's search box (.litesearch in lite.css), on CP's word of 2026-10-02: a
   soft fill and a soft edge, so it reads as a field to type in and not as one
   more outlined pill beside the filters. */
.csearch {
  display: flex;
  align-items: center;
  gap: 9px;
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--r-pill);
  /* The filter pills' height and their type size (CP, 2026-10-02). At 40px
     with 13px text it stood taller than the title beside it and than every
     control under it; now the page has one control height. */
  box-sizing: border-box;
  height: 34px;           /* a pill: 9 + 15 + 9, plus its two hairlines */
  padding: 0 14px;
  min-width: 300px;
  background: var(--surface-2);
  color: var(--text-4);
}
.csearch input {
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-family: var(--font-sans);
  font-size: 12.5px;
  width: 100%;
  outline: none;
}
.csearch input::placeholder { color: var(--text-4); }
.csearch:focus-within { border-color: var(--border-strong); }

/* Six filters as pills. Native selects underneath — they bring their own
   keyboard handling and their own mobile picker, which a custom menu would have
   to reimplement and would get wrong. */
.fbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
/* width, not flex-basis: .fbar turns into a column below 860px, where a
   flex-basis of 100% would mean 100% of the HEIGHT. Full width puts the match
   count on its own line under the pills and left-aligned, which is where the
   channel index has always shown it -- eight pills fill that row, so it wrapped
   there by accident. Trending has three, and space-between threw the count to
   the far right of the bar. Same markup, two layouts, one of them unintended. */
.fbar__pills { display: flex; flex-wrap: wrap; gap: 9px; width: 100%; }
.fbar__count { white-space: nowrap; }

/* The bar as a sheet, below 860px (CP, 2026-09-24, after Lite's).

   The same element in both shapes rather than a second copy of six filters: on a
   phone it leaves the flow, sits on the bottom edge and slides up. Everything
   inside it -- the pills, their panels, the links they carry -- is the markup
   the desktop bar already had, so there is nothing to keep in step.

   The furniture below (the button, the scrim, the grab bar, the heading and the
   Show-N button) exists only in the sheet, and is display:none until the query
   that makes the sheet. */
.fbar__m,
.fscrim,
.fbar__head,
.fbar__done { display: none; }

@media (max-width: 860px) {
  .fbar__m {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 16px;
  }

  /* Lite's button, to the pixel (CP, 2026-09-24): a 44px pill with a quiet
     border, which fills in only once something is filtering. Pro's version was
     a 40px box with an indigo badge -- louder than the thing it opens, and a
     second shape for the same control across two surfaces. The count is Pro's
     addition and Lite's treatment: white pill, ground-coloured digits. */
  .fbtn {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-height: 44px;
    padding: 0 16px;
    border: var(--hair) solid var(--border-soft);
    border-radius: 22px;
    background: none;
    color: var(--text-2);
    font: 400 13.5px/1 var(--font-sans);
    cursor: pointer;
  }
  .fbtn.is-on { border-color: var(--border-strong); color: var(--text); }
  .fbtn__n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--text);
    color: var(--bg);
    font-family: var(--font-mono);
    font-size: 10.5px;
  }

  /* Under the sheet and over everything else, including the fixed bottom nav. */
  .fscrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 96;
    background: rgba(0, 0, 0, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s var(--ease);
  }
  .fscrim.is-open { opacity: 1; pointer-events: auto; }

  /* A column, not the row it is on a wide screen, and painted: the channels
     page strips .card of its border and fill (see [data-page="channels"] above),
     which is right for a table sitting on the page and wrong for a sheet over
     it. .view-channels outranks that rule and only exists on this page. */
  /* Lite's sheet, to its own numbers (CP, 2026-09-24): same fill, same 22px
     top corners, same shadow, same 82vh ceiling and the same 0.28s slide. The
     app's bottom bar is z-index 70 and this is 97, so the sheet covers it and
     needs no room reserved for it -- only for the home indicator. */
  .fbar {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    /* The bar wraps on a wide screen; a COLUMN that wraps breaks into columns,
       which put the Show button in a second one beside the pills. */
    flex-wrap: nowrap;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 97;
    margin: 0;
    max-height: 82vh;
    overflow-y: auto;
    padding: 12px 20px calc(20px + env(safe-area-inset-bottom));
    border-radius: 22px 22px 0 0;
    box-shadow: 0 -18px 44px rgba(0, 0, 0, 0.55);
    transform: translateY(104%);
    transition: transform 0.28s var(--ease);
  }
  .fbar.is-open { transform: translateY(0); }

  /* body., because [data-page="channels"] .card strips the fill and sits LATER
     in this file: same specificity would lose to it on order alone. */
  body.view-channels .fbar {
    background: var(--surface);
    border: 0;
    border-top: var(--hair) solid var(--border);
  }

  .fbar__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    margin-bottom: 22px;
    flex: none;
  }
  .fbar__head .row { align-items: baseline; }
  .fbar__grab {
    width: 38px;
    height: 4px;
    border-radius: 2px;
    background: var(--border);
    cursor: pointer;
  }
  .fbar__title { font-size: 19px; font-weight: 600; }
  .fbar__reset { font-size: 13px; color: var(--text-3); }

  /* Full width, thumb height, and it carries the number the sheet exists to
     show moving. */
  /* Lite's .bigbtn: white, dark type, full width. The indigo one was louder
     than the sheet it closes. */
  .fbar__done {
    display: block;
    width: 100%;
    /* A flex child shrinks when the sheet's content overflows it, and this one
       had squashed to 14px: it is the target the sheet exists to give a thumb. */
    flex: none;
    margin-top: 4px;
    padding: 15px 20px;
    border: 0;
    border-radius: 22px;
    background: var(--text);
    color: var(--bg);
    font: 500 14.5px/1 var(--font-sans);
    text-align: center;
    cursor: pointer;
  }
  .fbar__done:hover { opacity: 0.88; }

  /* Groups of chips, as Lite draws them, rather than eight closed menus.

     Nothing is rebuilt to do it: each filter already renders a panel holding its
     title and its options as links, so in the sheet the panel is simply opened
     in place -- static, unpainted, wrapping -- the collapsed pill is hidden, and
     the options take Lite's chip. A tap is still the same link carrying the same
     query, which is why the count under the reader's thumb stays honest. */
  .fbar__pills { flex-direction: column; align-items: stretch; gap: 20px; }
  .fbar .fdrop { position: static; }
  .fbar .fpill { display: none; }
  .fbar .fdrop__panel,
  .fbar .fdrop--wide .fdrop__panel {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    position: static;
    min-width: 0;
    max-height: none;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    overflow: visible;
  }
  .fbar .fdrop__title {
    flex: 0 0 100%;
    margin-bottom: 3px;
  }
  .fbar .fopt {
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 12px 17px;
    box-sizing: border-box;
    border: var(--hair) solid var(--border-soft);
    border-radius: 22px;
    color: var(--text-3);
    font-size: 13px;
  }
  .fbar .fopt:hover { background: none; border-color: var(--border); color: var(--text-2); }
  .fbar .fopt.is-on { border-color: var(--border-strong); color: var(--text); }
  /* A chip is its label. The radio mark, the explanatory line and the per-option
     count are the panel's furniture on a wide screen and noise in a thumb-sized
     list; the count the reader is actually watching is on the button below. */
  .fbar .fopt__mark,
  .fbar .fopt__sub,
  .fbar .fopt__right { display: none; }
  .fbar .fopt__body { display: block; }
  /* Said twice already: on the Filters button above the sheet, and on the button
     that closes it. */
  .fbar .fbar__count { display: none; }

  /* Sources is the one multi-select, so its options are real checkboxes with an
     Apply rather than links that navigate on each tap. Where :has() can carry
     the ticked state the box itself goes away and they read as Lite's chips;
     where it cannot, the box stays visible and the control still works, which
     is the right way round for a state a chip would otherwise only pretend to
     have. */
  @supports selector(:has(*)) {
    .fbar .fopt--check input[type="checkbox"] {
      position: absolute;
      width: 1px;
      height: 1px;
      opacity: 0;
      pointer-events: none;
    }
    .fbar .fopt--check:has(input:checked) { border-color: var(--border-strong); color: var(--text); }
    .fbar .fopt--check:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
  }

  /* Apply belongs to its group, under the chips it applies. */
  .fbar .fdrop__foot {
    flex: 0 0 100%;
    margin-top: 4px;
    padding: 0;
    border: 0;
  }
  .fbar .fdrop__foot .btn {
    width: 100%;
    min-height: 44px;
    border-radius: 22px;
    border: var(--hair) solid var(--border-soft);
    background: none;
    color: var(--text-2);
    font-size: 13px;
  }
}

/* The pill is a button now, not a wrapper around a <select>.

   Its padding used to live on `.fpill select`, so when the selects were replaced
   by disclosure buttons the pills lost all of it and their text sat flush against
   the border. The ::after chevron went the same way: it existed because a
   stripped select draws none, and a button carrying its own SVG caret ended up
   with two, overlapping. */
.fpill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px;
  /* The soft edge at rest, the firm one under the pointer (CP, 2026-10-02).
     They were the other way round, and a row of ten firm outlines read as ten
     things switched on. A filter that is set still takes the teal ring, so the
     ring is now the only strong edge in the row. */
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--r-pill);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
}
.fpill:hover { border-color: var(--border); }

/* The three most accurate, each given the room to make its whole case. */
.feat__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
}
.feat__name { font-size: 17px; font-weight: 600; display: block; }
.feat__acc  { font-size: 30px; letter-spacing: -1px; line-height: 1; }
.feat__figs {
  display: flex;
  gap: 26px;
  margin-bottom: 16px;
}
.feat__figs > span { display: flex; flex-direction: column; gap: 4px; }
.feat__figs .num { font-size: 15px; }
.feat__calls { display: flex; align-items: center; gap: 12px; }

/* The long/short barcode. Proportional ticks, not a filled bar: at this size a
   two-colour solid bar reads as a progress indicator. */
.calls { display: inline-flex; gap: 2px; align-items: center; }
.calls__t {
  width: 2px;
  height: 13px;
  background: var(--up);
  display: inline-block;
}
.calls__t--s { background: var(--down); }

/* Channel profile rail. One cell is a control rather than a figure — the desk
   is the only fact here you can change — so it drops the 13px numeral sizing
   and lets the select size itself. */
.rail__cell--set { justify-content: center; }
.rail__cell--set .select { width: auto; min-width: 130px; }

/* A record that has gone cold. Used on Last idea: a high accuracy with a last
   idea years ago is a different proposition from the same accuracy last week,
   and nothing else on the page says so. */
.rail__value.is-down { color: var(--down); }

/* A hold-time tile that is also a link — the channel profile's grid switches
   the horizon the rest of the page is measured at, so the tiles are the
   selector as well as the picture. */

/* The horizon the page is currently showing. Outlined rather than filled: the
   fill already encodes the sign, and a second fill would collide with it. */
.hz__tile.is-shown {
  outline: 1px solid var(--up);
  outline-offset: -1px;
}

/* ==========================================================================
   Manage — where the capital sits (2026-09-02).
   ========================================================================== */

.capbar {
  display: flex;
  height: 22px;
  gap: 2px;
  border-radius: var(--r-sm);
  overflow: hidden;
  margin-bottom: 14px;
}
.capbar__seg  { background: var(--iq-teal); }
.capbar__free { flex: 1; background: var(--border); }
.capbar__chip {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--iq-teal);
  flex: none;
}
.capbar__chip--free { background: var(--border); }

/* ==========================================================================
   Manage > Accounts — an account is a card, not a row (2026-09-02).

   Namespaced xacct, not acct. The header's account menu already owns .acct,
   .acct__head and six more — redefining them here put a bordered, rounded,
   padded box around the avatar in every page's nav, hanging below the bar.
   A collision like this is invisible until you look at a page that renders
   both, which is every page.

   The figures only mean anything together: value without cash and deployed does
   not say whether the account can fill another order. A row spreads them across
   a line and lets the eye read them as separate columns; a card groups them.
   ========================================================================== */

.conn {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: 16px;
  padding: 16px;
}
/* The open-an-account form spans the grid: it is not one of the accounts, it is
   how another one appears. */
.conn > .conn__perm { grid-column: 1 / -1; }

.xacct {
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
}
/* Closed accounts stay listed — their fills are still in the history — but they
   carry no balance and nothing here can act on them. */
.xacct--closed { opacity: 0.55; }

.xacct__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}
.xacct__name { font-size: 16px; font-weight: 600; display: block; }

.xacct__figs {
  display: flex;
  gap: 26px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.xacct__figs > span { display: flex; flex-direction: column; gap: 4px; }
.xacct__figs .num { font-size: 15px; }

.xacct__note {
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-4);
  margin: 0;
}

.xacct__foot {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-top: 16px;
  border-top: var(--hair) solid var(--border);
  margin-top: auto;
}

/* Venue chips and order-routing rows on Manage > Accounts. */
.venues { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 16px; }
.venue {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  border: var(--hair) solid var(--border);
  border-radius: 10px;
  padding: 11px 15px;
  opacity: 0.45;
}
.venue__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-4); flex: none; }
.venue__name { font-size: 12.5px; }

.routes { display: flex; flex-direction: column; }
.route {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 13px 0;
  border-bottom: var(--hair) solid var(--border);
}
.route__name  { font-size: 13.5px; width: 180px; min-width: 0; }
.route__desk  { width: 120px; }
.route__alloc { font-size: 12.5px; color: var(--text-2); width: 110px; }
.route__acct  { margin-left: auto; width: auto; min-width: 150px; }

/* Manage > Following — the filter toolbar. */
.ftools {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.ftools__pills { display: flex; flex-wrap: wrap; gap: 9px; }

/* A pill that is a link rather than a select — no chevron, and the active one
   inverts so the current cut is unmistakable in a row of four. */
.fpill--flat {
  padding: 9px 15px;
  font-size: 12.5px;
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.fpill--flat.is-on {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
}
.fpill--flat.is-on .t-4 { color: var(--bg); opacity: 0.6; }

/* ==========================================================================
   Manage > Following — one panel per desk (2026-09-02).

   Was a single table with client-side section rows. Desk is the organising fact
   on this page, and a panel says so structurally: the group's name and its total
   stay attached to its rows instead of scrolling away from them.
   ========================================================================== */

.dpanel { margin-bottom: 16px; }
.dpanel__head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 15px 18px;
  border-bottom: var(--hair) solid var(--border);
}
.dpanel__name  { font-size: 13.5px; font-weight: 600; }
.dpanel__total { margin-left: auto; font-size: 12px; color: var(--text-2); }

/* Beside the desk's total, which is what it is a run of. The head aligns on the
   baseline so its three pieces of type sit on one line; a button has no useful
   baseline, so this one is centred against them instead. */
.dpanel__test { align-self: center; margin-left: 14px; }

/* One grid, shared by the header line and every row, so the columns line up
   without either knowing the other's widths. */
.frow {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) 210px 92px 64px 74px 46px;
  align-items: center;
  gap: 14px;
  padding: 11px 18px;
  border-bottom: var(--hair) solid var(--border);
}
.frow:last-child { border-bottom: 0; }
.frow--head { padding-top: 9px; padding-bottom: 9px; }
.frow--head .label { display: block; }

.frow__chan { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.frow__alloc { display: flex; flex-direction: column; gap: 6px; }
.frow__allocline { display: flex; align-items: baseline; gap: 8px; }
.frow__marks { display: flex; justify-content: center; gap: 4px; }
.frow__cap   { text-align: right; font-size: 12px; color: var(--text-3); }
.frow__hold  { font-size: 11px; color: var(--text-3); }
.frow__auto  { display: flex; justify-content: flex-end; }

/* The figure is the input. No border until you go near it — a bordered box per
   row turns the column into a form, and this reads as a list until you edit it. */
.money--flat { display: inline-flex; align-items: baseline; }
.money--flat .input {
  border: 0;
  background: none;
  padding: 0;
  width: 78px;
  font-size: 12.5px;
  color: var(--text);
}
.money--flat:hover .input,
.money--flat .input:focus {
  outline: var(--hair) solid var(--border-soft);
  outline-offset: 3px;
  border-radius: 3px;
}
/* Chrome and Safari draw steppers on number inputs, which put a control in a
   cell that is meant to read as text. */
.money--flat .input::-webkit-outer-spin-button,
.money--flat .input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.money--flat .input { -moz-appearance: textfield; }

/* The drawer sits between rows now rather than inside a table cell. */
.drawer { border-bottom: var(--hair) solid var(--border); background: var(--surface-2); }
.drawer__inner { padding: 20px 18px 22px 46px; }

/* Followed, nothing allocated. Usually most of the list, so it collapses. */
/* The backtest ledger: positions and the ideas the run refused, in one table.
   The refusals were a separate fold until 2026-09-09; merged, they need only to
   be distinguishable from a position, not decorated. */
.tbl tr.led--skip > td {
  background: rgba(255, 181, 69, 0.045);
}

/* Deliberately NOT highlighted. Amber until 2026-09-09, which made REFUSED the
   loudest thing in a column of quiet HORIZON badges and read as a warning —
   CP: "it stands out too much". A refusal is not an alarm, it is one of the
   outcomes, and the row already carries a tint and a line of dashes where its
   numbers would be. The rule stays as the place to say so. */
.badge--skip {
  color: var(--text-3);
  border-color: var(--border);
}

/* Direction takes no colour and no emphasis. Green and red mean profit and loss
   everywhere in this interface and a short is not a loss; dimming long because
   it is the common case would make the column an opinion rather than a fact, so
   both read identically. CP, 2026-09-09. */
.led__dir {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.07em;
  color: var(--text-2);
}

.watchonly { margin-bottom: 16px; }
.watchonly > summary {
  display: flex;
  align-items: baseline;
  gap: 12px;
  cursor: pointer;
  list-style: none;
}
.watchonly > summary::-webkit-details-marker { display: none; }
.watchonly__why { color: var(--text-4); }
.watchonly__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 2px 16px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: var(--hair) solid var(--border);
}
.watchonly__item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 0;
  font-size: 12.5px;
  color: var(--text-2);
}
.watchonly__item:hover { color: var(--text); }

@media (max-width: 1100px) {
  .frow { grid-template-columns: 14px minmax(0, 1fr) 160px 80px 48px; }
  .frow > :nth-child(6) { display: none; }
}

/* ==========================================================================
   The rules drawer — Option B (CP, 2026-09-02).

   Chosen over writing the rule as one sentence: traditional labels carry no
   learning curve, and this is a form people edit under time pressure. Two named
   questions in order, one column, every control ending on the same right edge.

   What it replaces: two columns whose split carried no meaning, a section
   heading that truncated ("WHEN THIS CHANNEL PUBLISHES A…"), a dead right third,
   and two bordered notice boxes competing with the fields.
   ========================================================================== */

.drawer__section { margin-bottom: 26px; }
.drawer__section:last-of-type { margin-bottom: 18px; }

.drawer__q {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 4px;
}

/* The conditional explanation, demoted from a notice box. It describes the
   three controls under it, so it reads as their description rather than as a
   separate object beside them. */
.drawer__why {
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-4);
  margin: 0 0 6px;
  max-width: 92ch;
}
.drawer__why strong { color: var(--text-2); font-weight: 500; }

/* One row per setting. The label column is fixed so every control lands on the
   same edge — the old layout let each field find its own, which is what made a
   column of seven read as scattered. */
/* Label left, control hard right, across the drawer's full width — the approved
   artboard's geometry (1280px frame, 260px label, control pushed right by an
   auto margin, leaving a wide deliberate gap). I briefly read that gap as dead
   space and capped the body at 680px with a fixed control column; that was
   backwards. The separation is what lets the eye run down a column of labels on
   one side and a column of controls on the other. */
.drow {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: 11px 0;
  border-bottom: var(--hair) solid var(--border);
}
.drow:last-child { border-bottom: 0; }
/* Wider than the artboard's 260 because the real drawer is wider than the
   artboard frame; the subtitles then wrap in the same places the artboard does,
   which is where it wants them. */
.drow__label { width: 340px; flex: none; }
.drow__name  { font-size: 13.5px; display: block; }
.drow__sub {
  font-size: 11px;
  color: var(--text-4);
  display: block;
  margin-top: 3px;
  line-height: 1.5;
}
.drow__control { margin-left: auto; width: 230px; }
.drow__control--sm { width: 110px; }

/* The auto-trade timestamp, demoted from a notice to a footnote: it is a fact
   about when trading started, not something to act on. */
.drawer__note {
  font-size: 11px;
  line-height: 1.7;
  color: var(--text-4);
  margin: 0 0 4px;
}

@media (max-width: 900px) {
  .drow { flex-direction: column; align-items: stretch; gap: 8px; }
  .drow__label { width: auto; }
  .drow__control, .drow__control--sm { margin-left: 0; width: 100%; }
}

/* ==========================================================================
   Channel peek — open a channel without leaving the list (2026-09-02).

   From Manage, checking whether a channel deserves its allocation meant leaving
   the page, reading the profile and coming back to a list you had lost your
   place in. The decision is made on Manage; the evidence lived two navigations
   away. This brings the evidence to the decision.
   ========================================================================== */

.peek__scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  /* Dims the list without blacking it out. The point of the pattern is that you
     can still see the row you came from and the rows around it — that context is
     what the decision needs. */
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.24s var(--ease, ease);
}
.peek__scrim.is-open { opacity: 1; pointer-events: auto; }

.peek {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(560px, 100%);
  z-index: 65;
  background: var(--surface);
  border-left: var(--hair) solid var(--border-soft);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;

  /* Hidden rather than only translated, so a closed panel keeps its links out
     of the tab order. The visibility flip is delayed by the slide on the way
     out and immediate on the way in. */
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 0.26s var(--ease, ease), visibility 0s linear 0.26s;
}
.peek.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.26s var(--ease, ease), visibility 0s;
}

.peek__head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 22px 24px 18px;
  border-bottom: var(--hair) solid var(--border);
  flex: none;
}
.peek__name { font-size: 20px; font-weight: 600; letter-spacing: -0.4px; }
.peek__x {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  border: var(--hair) solid var(--border-soft);
  background: none;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
}
.peek__x:hover { color: var(--text); border-color: var(--border); }

/* The body scrolls; the name and the full-profile link do not, so you always
   know which channel you are reading and how to get the rest of it. */
.peek__body { padding: 20px 24px 24px; overflow-y: auto; flex: 1; }
.peek__body > .label { display: block; margin-bottom: 10px; }
.peek__body > .label:not(:first-child) { margin-top: 24px; }

.peek__lead { display: flex; align-items: baseline; gap: 16px; }
.peek__acc  { font-size: 40px; font-weight: 500; letter-spacing: -1.5px; line-height: 1; }
.peek__rw   { font-size: 12.5px; color: var(--text-3); }

/* The record grid: one mark per call, oldest first.
   Wrapping flex rather than a fixed column count, because the panel is
   min(560px, 100%) and a grid pinned to 37 columns would either overflow on a
   phone or waste half the width on a desktop. The marks size themselves and the
   row length follows the panel. */
.prec { margin-top: 18px; }

.prec__head {
  display: block;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: 9px;
}

.prec__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
}

/* Ticks, not dots. Circles on a 4.5px grid read as scattered rather than
   sequential — CP, 2026-09-10 — and they were the odd shape out in an interface
   that already draws a record as a barcode in two other places. Sized to CP's
   numbers, one notch narrower than .trendbar__t so a few hundred calls still fit
   the panel. */
.prec__d {
  width: 2px;
  height: 13px;
  flex: 0 0 auto;
}

.prec__d--right { background: var(--up); }
.prec__d--wrong { background: var(--down); }

/* Solid at reduced strength, NOT a hollow outline — the same treatment and the
   same reason as .trendbar__t--open, whose comment records the lesson: at six
   months most recent calls are still open, so drawing them hollow washed out
   the whole strip. The first version of this grid used a ring and repeated it. */
.prec__d--open  { background: var(--text-3); opacity: 0.62; }

.prec__sum {
  display: block;
  margin-top: 11px;
  font-family: var(--font-mono);
  font-size: 12px;
}

.prec__n--right { color: var(--up-text); }
.prec__n--wrong { color: var(--down); }
.prec__n--open  { color: var(--text-4); }
.prec__sep      { color: var(--text-4); margin: 0 5px; }

.peek__figs { display: flex; gap: 26px; margin-top: 16px; }
.peek__figs > span { display: flex; flex-direction: column; gap: 4px; }
.peek__figs .num { font-size: 15px; }

.peek__calls {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  border-top: var(--hair) solid var(--border);
  border-bottom: var(--hair) solid var(--border);
  margin-top: 18px;
}
.peek__calls p { font-size: 11.5px; line-height: 1.5; color: var(--text-4); margin: 0; }

.peek__hz { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
.peek__hz .hz__pct { font-size: 14px; }

.peek__idea {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-bottom: var(--hair) solid var(--border);
}
.peek__idea:last-child { border-bottom: 0; }
.peek__sym { font-size: 13px; font-weight: 600; width: 56px; }
.peek__idea .num { margin-left: auto; font-size: 12.5px; }

.peek__foot {
  display: flex;
  align-items: center;
  padding: 16px 24px;
  border-top: var(--hair) solid var(--border);
  flex: none;
}
.peek__foot .btn { margin-left: auto; }

.peek__empty { padding: 40px 24px; text-align: center; font-size: 12.5px; color: var(--text-4); }
.peek__loading { padding: 48px 24px; text-align: center; font-size: 12.5px; color: var(--text-4); }

/* A row that opens a peek should look like it does. */
[data-peek] { cursor: pointer; }

/* Locks the page behind an open peek. Lite has its own copy of this in lite.css;
   Pro had none, so the panel's scroll lock was silently doing nothing here. */
body.has-sheet { overflow: hidden; }

/* The Manage save bar. Sticky at the bottom so it stays reachable however far
   down the list you have edited — a save you have to scroll back up to find is
   a save people forget to press. */
.savebar {
  position: sticky;
  bottom: 16px;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px auto;
  width: fit-content;
  padding: 10px 12px 10px 18px;
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}
.savebar__what { font-size: 12.5px; color: var(--text-2); }
.savebar__what .num { color: var(--text); }

/* ==========================================================================
   Channels — filter dropdowns (2026-09-02).

   A native select can only offer a bare list of labels. Most of these choices
   need a count of what the cut would leave and a sentence saying what it means —
   for direction the numbers ARE the argument. So each pill opens a panel.
   ========================================================================== */

.fdrop { position: relative; }
.fpill__caret { opacity: 0.55; flex: none; }
.fpill.is-set { border-color: var(--iq-teal); color: var(--text); }
.fpill[aria-expanded="true"] { border-color: var(--text-4); }
.fpill[aria-expanded="true"] .fpill__caret { transform: rotate(180deg); }

.fdrop__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 50;
  min-width: 280px;
  padding: 14px;
  border: var(--hair) solid var(--border-soft);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.6);
  /* Long lists (nine hold times, eleven sources) scroll rather than running off
     the bottom of the window. */
  max-height: 70vh;
  overflow-y: auto;
}
.fdrop--wide .fdrop__panel { min-width: 480px; }

/* The last two pills open near the right edge, so their panels hang left. */
.fdrop:nth-last-child(-n+2) .fdrop__panel { left: auto; right: 0; }

.fdrop__title {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 6px;
}

.fopt {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 11px 4px;
  border-bottom: var(--hair) solid var(--border);
  cursor: pointer;
}
.fopt:last-of-type { border-bottom: 0; }
.fopt:hover { background: var(--surface-2); }

/* The mark, drawn rather than a native input: a radio and a checkbox in a dark
   panel need to agree with everything else here, and the platform ones do not. */
.fopt__mark {
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 2px;
  border: 1.5px solid var(--border-strong, var(--text-4));
  background: none;
}
.fopt__mark--radio { border-radius: 50%; }
.fopt__mark--check { border-radius: 4px; }
.fopt.is-on .fopt__mark {
  border-color: var(--iq-teal);
  background: var(--iq-teal);
  box-shadow: inset 0 0 0 3px var(--surface);
}

.fopt__body { min-width: 0; flex: 1; }
.fopt__name { font-size: 13px; display: block; }
.fopt__sub {
  font-size: 11px;
  color: var(--text-4);
  display: block;
  margin-top: 3px;
  line-height: 1.4;
}
.fopt__right { text-align: right; flex: none; }
.fopt__right .num { font-size: 13px; display: block; }
.fopt__rightsub {
  font-size: 10px;
  color: var(--text-4);
  display: block;
  margin-top: 3px;
}

/* Sources: real checkboxes, because a set of them is what the browser knows how
   to submit as u[]. */
.fopt--check input { width: 15px; height: 15px; flex: none; margin-top: 2px; accent-color: var(--iq-teal); }

.fdrop__foot {
  display: flex;
  justify-content: flex-end;
  padding-top: 12px;
  margin-top: 4px;
  border-top: var(--hair) solid var(--border);
}

.fdrop__exact { padding-top: 14px; margin-top: 6px; border-top: var(--hair) solid var(--border); }
.fdrop__slider { display: flex; align-items: center; gap: 12px; }
.fdrop__slider .range { flex: 1; }
.fdrop__slidernum { font-size: 12.5px; width: 42px; text-align: right; }
.fdrop__note {
  font-size: 11px;
  line-height: 1.6;
  color: var(--text-4);
  margin: 12px 0 0;
}

/* Attribution bar — anchored at the centre so sign is read from direction, not
   from a colour swatch. A bar that always grows rightwards makes a $1,560 loss
   the same shape as a $1,560 gain, which is the one thing this table exists to
   tell apart. Half-widths are computed in PHP against the largest absolute
   total, so the longest bar in either direction reaches an edge. */
.attrbar { position: relative; height: 8px; background: var(--surface-2);
           border-radius: 2px; overflow: hidden; }
.attrbar__mid { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px;
                background: var(--border-strong); z-index: 1; }
.attrbar__fill { position: absolute; left: 50%; top: 0; bottom: 0;
                 background: var(--up); border-radius: 0 2px 2px 0; }
.attrbar__fill--dn { left: auto; right: 50%; background: var(--down);
                     border-radius: 2px 0 0 2px; }

/* Concentration / time-to-exit rows. One grid so the symbol, bar and two
   figures line up down the column across both cards. */
.conc { display: grid; grid-template-columns: 62px 1fr 52px 68px;
        align-items: center; gap: 10px; padding: 5px 0; }
.conc + .conc { border-top: 1px solid var(--border); }
.conc__sym { font-weight: 600; font-size: 12px; }
.conc__pct { font-size: 12px; color: var(--text-2); text-align: right; }
.conc__val { font-size: 12px; text-align: right; }

/* Experiments — the ask panel and the hero run.
   Named x* to stay out of the way of the shared .card/.stat namespace, after
   Manage's account cards redefined .acct and boxed the avatar on every page. */
.xask { display: flex; align-items: flex-start; justify-content: space-between;
        gap: 24px; flex-wrap: wrap; margin-bottom: 16px; }
.xask__acts { flex-shrink: 0; }

/* The hero. One run, read as a question and its answer, so the question gets
   heading weight and the answer gets the largest type on the page. */
.xhero { margin-bottom: 22px; }
.xhero__q { font-size: 21px; font-weight: 600; line-height: 1.35;
            margin: 6px 0 18px; max-width: 46ch; }
.xhero__lead { align-items: baseline; gap: 14px; flex-wrap: wrap; }
.xhero__fig { font-size: 46px; font-weight: 600; line-height: 1;
              letter-spacing: -0.02em; }
.xhero__on { max-width: 34ch; }
.xhero__bench { margin: 12px 0 0; max-width: 60ch; }

/* The supporting figures, on a rule rather than in boxes: they qualify the
   headline above them and are not four independent facts. */
.xhero__stats { display: flex; flex-wrap: wrap; gap: 34px; align-items: flex-end;
                margin-top: 20px; padding-top: 16px;
                border-top: var(--hair) solid var(--border); }
.xhero__stats > div { display: flex; flex-direction: column; gap: 3px; }
.xhero__stats .num { font-size: 15px; }
.xhero__acts { margin-left: auto; }

@media (max-width: 720px) {
  .xhero__fig    { font-size: 36px; }
  .xhero__q      { font-size: 18px; }
  .xhero__stats  { gap: 20px 24px; }
  .xhero__acts   { margin-left: 0; }
  .xask__acts    { width: 100%; }
}

/* The save bar in the filter row rather than stuck to the bottom of the
   viewport. Everything that made it float has to be undone explicitly — sticky
   positioning, the auto left margin, the lifted shadow — because it still shares
   the .savebar pill shape and border, and a sticky element inside a flex row
   sits in the flow but keeps offsetting itself as you scroll. */
.savebar--inrow {
  position: static;
  bottom: auto;
  z-index: auto;
  margin: 0 0 0 auto;
  box-shadow: none;
  /* Matches the filter pills beside it so the row reads as one row. */
  padding: 6px 8px 6px 14px;
}
/* [hidden] is honoured by the reset, but .savebar is display:flex and a
   specificity tie would let the flex win. Stated so the row is genuinely empty
   of it until something is dirty. */
.savebar--inrow[hidden] { display: none !important; }

@media (max-width: 720px) {
  /* Below the pills rather than crushed against them. */
  .savebar--inrow { margin-left: 0; width: 100%; justify-content: flex-end; }
}

/* Trend barcode. Same ticks as .calls on purpose: the two columns sit beside each
   other and should be read with the same eye movement, one saying which way the
   channel bets and the other whether the recent bets are working. Slightly wider
   ticks because there are five rather than twelve, so the strip does not read as
   a fragment of the column next to it. */
.trendbar__t { width: 3px; height: 14px; }

/* An open idea is a live mark, not a result — its tick can flip tomorrow. It was
   drawn as a hollow outline at first, which was wrong for the job: at a 6MO hold
   time most recent ideas are still open, so a whole column of strips came out
   hollow and the thing meant to be read at a glance read as washed out. Solid,
   at slightly reduced strength — the green or red still lands first, and the
   count of open ones is in the tooltip where it does not cost legibility. */
.trendbar__t--open { opacity: 0.62; }

/* The allocation bar is a control, so it has to say so. It looked draggable from
   the day the row became one line tall and was not, which is the worst of both:
   an affordance that invites a gesture and then ignores it.

   The taller invisible hit area matters more than the cursor — a 4px track is a
   4px target, and a drag that misses reads as a broken control rather than a
   missed one. */
.frow__alloc .meter {
  cursor: ew-resize;
  position: relative;
  touch-action: none;          /* let a touch drag move the bar, not the page */
}
.frow__alloc .meter::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  bottom: -7px;
}
.frow__alloc .meter:hover .meter__fill,
.frow__alloc .meter.is-dragging .meter__fill {
  background: var(--accent);
}
.frow__alloc .meter.is-dragging { cursor: ew-resize; }

/* The saved count, in the save bar itself. The toast still fires, but the toast
   host is fixed to the bottom-right and the save control now sits up in the
   filter row — so the confirmation was appearing at the opposite corner of the
   screen from the button that caused it, which is how a successful save reads as
   nothing having happened. */
.savebar__done {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.4px;
  color: var(--up-text);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Column picker (Channels)

   The card head was a title and a note at opposite ends. The picker is a third
   thing and belongs with the note rather than between them, so the two share a
   group and space-between keeps working with two children instead of three.
   -------------------------------------------------------------------------- */

.card__tools {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* .fopt was written for an <a>. The picker's rows are buttons, because they
   change what this browser shows rather than where it goes, and a button
   carries the UA's own font, background, border and centred text into a panel
   that assumes none of them. */
button.fopt {
  width: 100%;
  background: none;
  border: 0;
  border-bottom: var(--hair) solid var(--border);
  font: inherit;
  color: inherit;
  text-align: left;
}
button.fopt:last-of-type { border-bottom: 0; }

/* No mark on the reset row: it is an action, not a state, and an empty checkbox
   beside it reads as a tenth column to switch off. Indented to the text column
   the marks create so it still lines up with the names above it. */
.fopt--reset .fopt__body { padding-left: 27px; }
.fopt--reset .fopt__name { color: var(--iq-teal); }

/* --------------------------------------------------------------------------
   Call cards — the dashboard stream

   One card per idea: who said it, what their record is, and what they said, in
   that order. The record between the name and the call is deliberate — it is
   the qualifier, and putting it after the call would read as a footnote to a
   decision already made.
   -------------------------------------------------------------------------- */

/* Separate cards, not a grid of cells. They were laid out with the 1px-gap
   hairline trick this codebase uses for tables -- container painted the border
   colour, cells painted the surface, the gap showing through as a rule. That is
   right for rows of one kind of thing, and wrong here: each of these is a
   different person saying a different thing, and merged cells read as one
   table rather than twelve calls. CP, 2026-09-11. */
.callgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
  gap: 12px;
  padding: 16px var(--pad) 18px;
  border-top: var(--hair) solid var(--border-soft);
}

.callcard {
  background: var(--surface);
  border: var(--hair) solid var(--border);
  border-radius: 10px;
  padding: 15px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 13px;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.12s var(--ease), background 0.12s var(--ease);
}
/* The border carries the hover, not the fill: these sit on the card's own
   surface, so a fill change is a much bigger move than it looks. */
.callcard:hover { border-color: var(--border-strong, var(--text-4)); background: var(--surface-2); }
.callcard:focus-visible { outline: 2px solid var(--iq-teal); outline-offset: 2px; }

.callcard__who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.callcard__whotext { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.callcard__name { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }

/* Three figures on one baseline. Sized down from the card's own scale so the
   record supports the call rather than competing with it. */
.callcard__rec {
  display: flex;
  gap: 18px;
  padding: 11px 0;
  border-top: var(--hair) solid var(--border-soft);
  border-bottom: var(--hair) solid var(--border-soft);
}
.callcard__rec--none { display: block; padding: 11px 0; }
.callcard__stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.callcard__stat .num { font-size: 15px; font-weight: 500; font-variant-numeric: tabular-nums; }

.callcard__call { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
/* The ticker is the largest thing on the card. It is what the reader is
   scanning for, and everything above it exists to qualify it. */
.callcard__sym {
  font-family: var(--font-mono);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* Following, simplified. The rules editor's grid was sized for six controls of
   very different widths; this is five figures and an action, so the columns are
   even and the numbers right-align into a scannable stack. */
.frow--simple {
  grid-template-columns: minmax(0, 2.2fr) repeat(4, minmax(0, 1fr)) auto;
  align-items: center;
}
.frow--simple .cell-num { font-variant-numeric: tabular-nums; text-align: right; }

/* How the call has gone since it was made. Sits at the foot of the card, under
   the call itself, because it is the consequence of the line above it — and it
   is the last thing read, which is where a verdict belongs. */
.callcard__since {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: auto;
  padding-top: 11px;
  border-top: var(--hair) solid var(--border-soft);
}
.callcard__since .num { font-size: 17px; font-weight: 500; font-variant-numeric: tabular-nums; }

/* Simulate sits at the right of the outcome row, so the last line of the card
   reads "how it has gone" then "do it yourself". Pushed over with margin-left
   rather than space-between, because the outcome is two elements and
   space-between would split those too. */
.callcard__sim { margin-left: auto; flex: none; }
.callcard__sim.is-done {
  border-color: var(--iq-teal);
  color: var(--iq-teal);
  opacity: 1;
}
/* The card is a div now, so the links inside it carry what it used to. */
a.callcard__who,
a.callcard__call { color: inherit; text-decoration: none; }
a.callcard__who:hover .callcard__name,
a.callcard__call:hover .callcard__sym { text-decoration: underline; text-underline-offset: 2px; }

/* The reader's own record, under their open watches. A strip rather than a
   second table: it is a summary until there is enough of it to deserve a page,
   and a table of resolved rows under a table of open ones reads as one list
   with two meanings. */
.watchrec { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.watchrec__n { font-size: 13px; color: var(--text-2); }
.watchrec__ticks { display: flex; gap: 3px; align-items: stretch; margin-left: auto; }
/* The same tick the channel record strips use — one pixel of ink every four —
   so a record reads the same whether it is a channel's or the reader's own. */
.watchrec__t { width: 1px; height: 14px; background: var(--up); flex: 0 0 auto; }
.watchrec__t--down { background: var(--down); }

/* A watched card's button is inert but not greyed: it reports a state the
   reader put there, so it reads as confirmation rather than as something
   switched off. */
.callcard__sim.is-done { pointer-events: none; }

/* A channel profile's tiles and panels lose their fill and keep their edge (CP,
   2026-09-24), the same move the Desk and the channels list made: an outline is
   enough to hold a group, and the page reads as one surface rather than as
   fourteen stacked ones.

   CP also struck the radius and the padding from lite.css's .ltile rule. Those
   are set again further down that file (lite.css:1362, the six-across layout),
   so removing them from the first rule changes nothing on screen -- the fill is
   the whole of the visible change. Squaring the tiles means editing that later
   rule instead. */
.view-channel .ltile,
.view-channel .lcard { background: none; }

/* The Desk's panels sit on the page rather than in boxes (CP, 2026-09-24), the
   same move the channels list made: Watching, the calls and the alert keep their
   heads, their rows and their day boxes, and give up the outline and the fill
   around all of it. Only stream.php -- Lite's pages carry no data-page, and the
   .stat tiles above are their own class, so they keep their edges. */
body[data-page="stream"] .card {
  border: 0;
  background: none;
}

/* The channels list only (CP, 2026-09-24): no rule under a row, and the figures
   a size up. .tbl is shared by ten pages -- admin's job tables, the idea log,
   manage -- so this hangs off the list's own body class rather than editing the
   base rule, the same way the badge below does. */
.view-channels .tbl tbody td { border-bottom: 0; }
/* 14px since 2026-10-02 (CP). It was 17px, "a size up", from 09-24; with the
   page tidied the figures were the loudest thing on it. */
.view-channels .tbl .cell-num { font-size: 14px; }

/* A stacked cell, spaced as Lite spaces its rate over "of 62" (.lacc__of in
   lite.css): the small print 2px under the line above it and nothing else
   between them (CP, 2026-10-02). The source badge under a channel's name had
   3px of padding above and below its text, which is what pushed the two lines
   apart; on this list it is plain type, so it gives the padding up. */
.view-channels .chan__sub .badge { padding-top: 0; padding-bottom: 0; }

/* The source badge on the channels list is a label, not a chip (CP,
   2026-09-23). A bordered pill under all fifty names drew fifty boxes to say
   what one word says, so on this page a badge loses its border, its fill and
   its side padding and sits as plain mono type under the name. The grey and the
   uppercase stay: it is still a different kind of thing from the name above it.

   The list only. A profile shows one badge beside one name, where a chip reads
   as a chip rather than as fifty of them, and $BODY_CLASS is how the two pages
   are told apart at all -- they share data-page="channels". */
.view-channels .badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  padding: 3px 0;
  border: 0;
  background: none;
  border-radius: var(--r-sm);
  white-space: nowrap;
}

/* Lite's own radius, for the page that borrows Lite's panels. lite.css declares
   --r-lite on `.lite` and Pro's body does not carry that class, so without this
   .lcard and the pager buttons draw square. Scoped to the wrapper rather than
   added to :root: it is Lite's token and this is the one Pro page using it. */
.chanlite { --r-lite: 22px; }

/* The channel page's header row (CP, 2026-09-23), where Lite's layout moved to
   Pro: the name and its one-line provenance on the left, the desk, follow and
   backtest controls on the right, wrapping under it on a narrow screen rather
   than squeezing the name. */
.chanhead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 22px;
  margin: 0 0 26px;
  flex-wrap: wrap;
}

/* Lite's panel lifts its border under the cursor because it was written for the
   Stream's cards, which open something. On the channel page it is a static
   panel, and a hover state on something that does not respond is a promise the
   page does not keep. */
body[data-page="channels"] .lcard:hover { border-color: var(--border-soft); }

/* The Desk's rows (CP, 09/18): the calls, replacing the card grid above (parked
   in stream.php), and Watching above them on the SAME tracks, so the two panels'
   columns line up -- CP found the mismatch "annoying to look at". Both read the
   same way across: who, the call, three figures, the result, the action. Every
   data column is sized to its content and packed left; the only flexible track
   is the last, and the button sits at its far end. The name, call and Watch
   pieces reuse the card's classes, so links, hover underline and the watched
   state behave as they did. No top rule: .card__head already draws one, and two
   would meet as a 2px line.

   Roles, not columns, name the cells, so the phone layout below can place them
   whichever panel they are in: crow__who, crow__call, crow__key (the one figure
   kept on a phone), crow__more (dropped on a phone), crow__since, crow__act. */
.crow {
  display: grid;
  grid-template-columns: 240px 240px 100px 90px 90px 80px minmax(0, 1fr);
  align-items: center;
  column-gap: 24px;
  padding: 11px var(--pad);
  border-bottom: var(--hair) solid var(--border-soft);
}
.crow:last-child { border-bottom: 0; }
.crow:not(.crow--head):hover { background: var(--surface-2); }
.crow--head { padding-top: 9px; padding-bottom: 9px; }
.crow .callcard__sym { font-size: 17px; }
.crow__r { text-align: right; white-space: nowrap; }
.crow__fig { font-size: 15px; font-weight: 500; font-variant-numeric: tabular-nums; }
/* The n beside the rate, in a fixed slot so the rates stay right-aligned
   whether a record is 2 calls or 2,000. */
.crow__n {
  display: inline-block;
  min-width: 24px;
  margin-left: 3px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-4);
}
.crow__since .num { font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; }
.crow__since .label { margin-left: 6px; }
.crow__norec { grid-column: span 3; }

/* Boxed rows (CP, 09/18): the calls grouped by day as Activity groups them, each
   day's calls in their own box inside the panel. The header row sits in a
   wrapper with a transparent border as wide as the boxes' real one, so it lines
   up with the rows inside them. */
.crows--boxed { padding: 0 var(--pad) var(--pad); }
.crows__cols,
.crows__box { border: var(--hair) solid transparent; }
.crows__box { border-color: var(--border); border-radius: var(--r-panel); overflow: hidden; }
.crows--boxed .crow--head { padding-top: 12px; padding-bottom: 2px; border-bottom: 0; }
/* Activity's day header sticks to the page under the bar; inside a panel it
   would slide over the rows on the page's ground, so here it stays put. */
.crows__day .day__head { position: static; background: none; padding: 12px 0 8px; }

/* Watching is not boxed -- CP did not want a table inside a table there -- so its
   rows run the panel's full width and are inset instead by exactly what a day
   box adds to a calls row: the panel padding plus the box border. The columns
   meet across the two panels at every width. */
.crows--flush .crow {
  padding-left: calc(2 * var(--pad) + var(--hair));
  padding-right: calc(2 * var(--pad) + var(--hair));
}
.crow__date { font-family: var(--font-mono); font-size: 13px; color: var(--text-3); }
.crow__who .avatar { flex: none; }
.crow__act { justify-self: end; }

/* Name and call give way before the figures do. */
@media (max-width: 1100px) {
  .crow {
    grid-template-columns: minmax(140px, 200px) minmax(150px, 210px) 96px 86px 86px 74px minmax(0, 1fr);
    column-gap: 16px;
  }
}

/* Phone: two lines a row. Who, the result and the button on top; the call and
   the key figure under. The rest drops -- the channel page has it. */
@media (max-width: 860px) {
  .crow {
    grid-template-columns: minmax(0, 1fr) auto auto;
    row-gap: 10px;
    column-gap: 12px;
  }
  .crow--head { display: none; }
  .crow > .crow__who   { grid-column: 1; grid-row: 1; }
  .crow > .crow__since { grid-column: 2; grid-row: 1; }
  .crow > .crow__act   { grid-column: 3; grid-row: 1; }
  .crow > .crow__call  { grid-column: 1; grid-row: 2; }
  .crow > .crow__key   { grid-column: 2 / 4; grid-row: 2; justify-self: end; }
  .crow > .crow__more  { display: none; }
}

/* The hold time control in the top bar takes the bar's shape, not the filter
   row's. .fpill is fully rounded because in a filter row it sits among other
   pills and the shape is what groups them; up here its neighbours are the mode
   switch, the bell and the account button, which are all --r on --ctl-h. A
   7000px radius beside three 6px ones reads as a control that wandered in from
   another page. Scoped to the bar so every other .fpill keeps its own shape. */
.topbar__inner .fpill {
  border-radius: var(--r);
  height: var(--ctl-h);
  padding: 0 12px;

  /* The avatar's palette exactly: --surface-2 on --border, grey text. A filter
     pill sits on --bg because in a filter row the page is the ground; up here
     the ground is the bar, and the neighbours -- the avatar, the bell, the mode
     switch -- are all filled rather than outlined against it. */
  background: var(--surface-2);
  color: var(--text-3);
  border-color: var(--border);
}

/* Same hover the account button gives its avatar. */
.topbar__inner .fpill:hover,
.topbar__inner .fpill[aria-expanded="true"] {
  color: var(--text);
  border-color: var(--border-strong);
}

/* Away from the default hold time, said in the avatar's own palette rather than
   with a teal ring. The signal is worth keeping -- the horizon decides what
   every figure on the page means, so a reader who has moved it should be able
   to see that at a glance -- but a brand-coloured outline on a bar of grey
   controls made it the loudest thing up there, and it is context, not an
   alert. Full-strength text against the grey is enough to read as "set". */
.topbar__inner .fpill.is-set {
  border-color: var(--border);
  color: var(--text);
}

/* ===========================================================================
   CHANNELS — the table as the page

   What makes Lite calm is not fewer numbers, it is fewer surfaces. Pro draws a
   panel and then draws the table inside it, so a reader counts two edges, two
   fills and two header bands before reaching a figure. These rules take the
   panel away and leave the table sitting on the page: the card keeps the job of
   grouping and spacing and gives up the job of looking like an object.

   Scoped by page name deliberately. .card, .card__head and .tbl thead th are
   shared by ten pages — admin alone has seventy-two cards — and the rest have
   not been read against this yet. A page joins the list as it is reviewed;
   finishing the job means deleting the block and editing the base rules.

   Trending and Patterns joined on 2026-09-25 (CP: "please make trending look
   the same as channels"). They were built on the channel index's markup, so
   they inherited its structure and not this, which is the whole reason they
   looked like a different product: same table, drawn inside a panel.
   =========================================================================== */
[data-page="channels"] .card,
[data-page="trending"] .card,
[data-page="patterns"] .card,
[data-page="pattern-feed"] .card,
[data-page="pattern-check"] .card {
  border: 0;
  background: transparent;
}

[data-page="channels"] .card__head,
[data-page="trending"] .card__head,
[data-page="patterns"] .card__head,
[data-page="pattern-feed"] .card__head,
[data-page="pattern-check"] .card__head {
  border-bottom: 0;
}

/* The ground, not `transparent`. These headings are position: sticky, and a
   sticky cell with nothing behind it lets the rows travel through the column
   labels — the same fact that put --surface-2 under .table-wrap--pane. Over a
   card that is now itself transparent, the page's own ground is indistinguish-
   able from no fill, so this reads as the removal it is meant to be while the
   fifty rows still pass behind the labels rather than through them. */
[data-page="channels"] .tbl thead th,
[data-page="trending"] .tbl thead th,
[data-page="patterns"] .tbl thead th,
[data-page="pattern-feed"] .tbl thead th,
[data-page="pattern-check"] .tbl thead th {
  background: var(--bg);
}

/* ===========================================================================
   The footer
   ===========================================================================
   CP, 2026-09-24, on the merged product: the tools live "at the bottom of the
   page" rather than in the bar. So the footer stops being one grey line and
   becomes the place the rest of the product is reachable from.

   Quiet on purpose. It sits under every page, including tables fifty rows long,
   and a footer that competes with the page it follows is a footer that gets
   scrolled past. The headings carry the only weight in it.
   =========================================================================== */
.sitefoot {
  margin-top: 40px;
  border-top: var(--hair) solid var(--border-soft);
}

.sitefoot__in {
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding: 30px 24px 44px;
}

/* auto-fit rather than a column count: two columns today, and a reading column
   and a company column are both waiting on pages to be written. Adding one
   should not be a change to this rule. */
.sitefoot__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(178px, 218px));
  gap: 24px 40px;
}

.sitefoot__col {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.sitefoot__h {
  margin: 0 0 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-3);
}

.sitefoot__l {
  font-size: 13.5px;
  color: var(--text-2);
  text-decoration: none;
}

.sitefoot__l:hover {
  color: var(--text);
}

.sitefoot__base {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  padding-top: 20px;
  border-top: var(--hair) solid var(--border-soft);
}

.sitefoot__note {
  font-size: 12.5px;
  color: var(--text-4);
}


/* ===========================================================================
   A row of controls, and a note under a table
   ===========================================================================
   The index pages added on 2026-09-25 carry two or three pills above their
   table rather than the channel page's six-filter bar. They wanted somewhere to
   sit, and .fbar__m was the wrong answer: that is the wrapper for the button
   that folds the filter bar into a sheet below 860px, and it is display:none
   above it -- so the controls were invisible on every desktop.
   =========================================================================== */
.ctlrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
}

/* What a table's figures are over, under the table. Sentence case: .label is
   mono uppercase with letter-spacing, which is right for a two-word column head
   and turns a three-line sentence into a wall. */
.tnote {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-4);
}


/* A company name is a proper noun, not a label.
   .chan__sub is mono uppercase because on the channel table it holds a source
   badge and a short tag. On Trending it holds "State Street SPDR S&P 500 ETF
   Trust", which uppercase turns into shouting and mono makes longer than the
   column. .tt-none cannot fix it -- same specificity, and .chan__sub is later
   in the file, so it wins. Scoped to the page instead. */
/* A ceiling on the name column, so .truncate can do its job. Inside an
   auto-sized table a nowrap cell grows to fit its text and an ellipsis never
   engages -- and a company name is not bounded: ADRs carry their legal
   description, "Woori Financial Group Inc. American Depositary Shares (each
   representing three (3) shares of Common Stock)", which at 542px pushed the
   Pattern Feed's table to 1,427px in a 1,203px column and hid its last three
   columns, including the one it is sorted by. The full name is on hover. */
.view-trending .chan__body,
.view-pattern-feed .chan__body {
  max-width: 260px;
}

.view-trending .chan__sub,
.view-pattern-feed .chan__sub {
  font-family: var(--font-sans);
  font-size: 11.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-4);
}

/* ===========================================================================
   A chart pattern on its bars (iq_pattern_chart, 2026-09-30)
   ===========================================================================
   The price chart's family, extended by what a pattern needs: the day's range,
   the shape's lines, its pivots and three levels. Everything takes a theme
   token, so the chart follows the reader into light mode -- the reason it is
   drawn here at all rather than borrowed from the public page, which is dark
   whatever the reader chose.
   =========================================================================== */
.pchart__line--range {
  stroke: var(--text-4);
  stroke-width: 1;
  opacity: 0.8;
}

/* The shape itself, in the ink colour: the one thing on this chart the reader
   came to see. */
.pchart__line--shape {
  stroke: var(--accent);
  stroke-width: 2;
}

.pchart__pivot {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--bg);
  border: 1.5px solid var(--accent);
  pointer-events: none;
}

.pchart__lvl {
  position: absolute;
  left: 0;
  right: 0;
  border-top: var(--hair) dashed var(--text-3);
  pointer-events: none;
}

/* Target and failure take the pattern's winning and losing colours, the same
   rule the price chart uses for a short: reaching the target is the pattern
   working, whichever way the price had to go to get there. */
.pchart__lvl--target  { border-top-color: var(--up); }
.pchart__lvl--invalid { border-top-color: var(--down); }

/* (The first version's in-plot level pills were retired on 09-30, when the
   levels moved to the price column as the public chart has them.) */

/* ===========================================================================
   The symbol page (symbol.php, 2026-09-30)
   ===========================================================================
   The price leads the head, the way the public page leads with it: CP asked on
   that page for "Now / Today as the primary tile", so the live figure is the
   big one and the reference close sits beside it in the quieter colour. */
.symhead__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 8px 0 2px;
}

.symhead__label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-3);
}

.symhead__now {
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.5px;
  color: var(--text);
}

/* The hold time in the top bar, marked in the by-hold table so the headline
   figure can be found in it at a glance. */
.view-symbol .tbl tr.is-current td {
  background: var(--surface-2);
}

/* ===========================================================================
   Pattern Feed — the grid view (2026-09-30)
   ===========================================================================
   CP: "add a grid view option that displays the chart / pattern like it was
   doing". The public feed's card, in the app's own surfaces: the same border,
   radius and fill as the Desk's stat cards, so it reads as this product and
   not as a panel lifted from another site. Deliberately NOT .card, which this
   page strips of border and fill so the table can sit on the page -- a grid of
   cards with no edges is a grid of nothing. */
.pgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  padding: 4px 0 8px;
}

.pcard {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  padding: 16px var(--pad) 16px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  color: var(--text-2);
  text-decoration: none;
}

.pcard:hover {
  border-color: var(--border-strong);
}

.pcard:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.pcard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.pcard__sym {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.pcard__shape {
  font-size: 13px;
  color: var(--text-2);
}

.pcard__chart {
  display: block;
  margin: 6px 0 8px;
}

.pcard__what {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.pcard__meta {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--text-3);
  overflow-wrap: anywhere;
}

/* The drawing. The box has the viewBox's own 3:1 ratio, so nothing is
   stretched and the pivots stay round. */
.pspark {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 1;
}

.pspark--empty {
  background: var(--surface-2);
  border-radius: var(--r-sm);
}

.pspark__band { fill: var(--accent); opacity: 0.1; }

.pspark__wick {
  fill: none;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.pspark__wick--up { stroke: var(--up); }
.pspark__wick--dn { stroke: var(--down); }
.pspark__body--up { fill: var(--up); }
.pspark__body--dn { fill: var(--down); }

.pspark__zig {
  fill: none;
  stroke: var(--accent);
  opacity: 0.7;
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.pspark__line {
  stroke: var(--accent);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
}

.pspark__pivot {
  fill: var(--surface);
  stroke: var(--accent);
  stroke-width: 1.4;
  vector-effect: non-scaling-stroke;
}

/* ===========================================================================
   Pattern Feed — the session strip and the two segmented controls (09-30)
   ===========================================================================
   CP, with a screenshot of the public feed: "can you do it like this instead?
   re: filters/dates". A scrolling strip of sessions with the day's count on
   each, a heading that names the session, and direction and status as pill
   segments. The active tile takes the accent, as it does there; the active
   segment is inverted ink, as it does there -- both from this theme's tokens,
   so the strip follows the reader into light mode. */
.pdays {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px 2px 10px;
  margin: 4px -2px 0;
  scrollbar-width: thin;
}

.pday {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 96px;
  padding: 11px 16px 10px;
  border: var(--hair) solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  text-decoration: none;
}

.pday:hover { border-color: var(--border-strong); }

.pday.is-on {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  background: var(--surface-2);
}

.pday__d {
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
}

.pday__n {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}

.pdays__rule {
  border: 0;
  border-top: var(--hair) solid var(--border-soft);
  margin: 6px 0 26px;
}

.phead {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px 20px;
  margin-bottom: 18px;
}

.phead__title {
  margin: 0;
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.6px;
  line-height: 1.1;
  color: var(--text);
}

.phead__sub {
  margin: 8px 0 0;
  font-size: 15px;
  color: var(--text-2);
}

.phead__segs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.pseg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: var(--hair) solid var(--border-soft);   /* as .fpill at rest */
  border-radius: 999px;
  background: var(--surface);
}

.pseg__opt {
  /* 4px of track each side plus this comes to the height of a filter pill
     (9px + 12.5px/1.2 + 9px), so the segments and the pills on the same row
     share one baseline. 7px read as a taller, heavier control beside them. */
  padding: 5px 14px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  /* Stated, because the mono face's default line box is taller than the
     sans pill's: without this the segments came to 38px beside 35px pills,
     three pixels the eye reads as a different control. */
  line-height: 1.2;
  color: var(--text-2);
  text-decoration: none;
  white-space: nowrap;
}

.pseg__opt:hover { color: var(--text); }

.pseg__opt.is-on {
  background: var(--primary);
  color: var(--primary-text);
}


/* ---------------------------------------------------------------------------
   The pattern page's chart on candles (CP, 2026-09-30: "why did we move from
   candles to line charts?"). The accent carries the shape -- span, lines,
   pivots -- as the public chart does, and the same token on the grid cards, so
   a card and the page it opens draw the pattern the same way.
   --------------------------------------------------------------------------- */
.pchart__band--shape {
  background: var(--accent);
  opacity: 0.1;
}

.pchart__shape {
  stroke: var(--accent);
  stroke-width: 1.8;
}

/* Rules from where the pattern ends to the right edge. The break level solid in
   ink; the target and the failure level in the pattern's winning and losing
   colours, dashed and dotted so they are told apart without colour too. */
.pchart__lvl--break   { border-top: 1.5px solid var(--text); }
.pchart__lvl--target  { border-top: var(--hair) dashed var(--up); }
.pchart__lvl--invalid { border-top: var(--hair) dotted var(--down); }

/* Their prices in the price column, over the round-number ticks. The ground
   behind them masks a tick they land on rather than printing two numbers in
   one place. */
.pchart__lvlpx {
  position: absolute;
  left: 100%;
  padding: 1px 0 1px 9px;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
  background: var(--bg);
  z-index: 1;
}
.pchart__lvlpx--break   { color: var(--text); }
.pchart__lvlpx--target  { color: var(--up); }
.pchart__lvlpx--invalid { color: var(--down); }

.pchart__volume {
  height: 56px;
  margin: 8px var(--pchart-gutter) 0 0;
}

.pchart__volbar {
  fill: var(--text-4);
  opacity: 0.6;
}

/* A month label starts at the month, as the public chart has it. */
.pchart__time--month { transform: none; }


/* ---------------------------------------------------------------------------
   Pattern Feed: one control row (CP, 2026-09-30)
   ---------------------------------------------------------------------------
   The segments that narrow the list sit left, the pills that decide how it is
   listed sit right, and every control on the row is the same pill shape and
   height. The heading above the row is reading only. Below 860px the row
   wraps: filters on their own line, then the pills. */
.pctl {
  align-items: center;
  gap: 12px 20px;
  padding-left: 0;
  padding-right: 0;
}

.pctl__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/* A control row followed directly by column heads wants a little air: the
   card head lost its rule when this page joined the de-boxed set. */
.view-pattern-feed .card__head.pctl {
  padding-bottom: 6px;
}

/* On a phone the row wraps into lines; the pills then start from the left
   like the segments above them, rather than hugging the right edge on their
   own line. */
@media (max-width: 860px) {
  .pctl .card__tools { justify-content: flex-start; }
}

/* No controls beside the heading any more: the sentence gets the width. */
.view-pattern-feed .phead__text {
  max-width: 72ch;
}


/* ===========================================================================
   Pattern check (pattern-check.php, 2026-09-30)
   ===========================================================================
   The form is one row in the feed's pill language: every control 35px, round,
   the same border, the button the app's primary. No labels above the fields;
   each control's value says what it is, and the ticker box has a placeholder.
   Below 640px the row wraps and the button takes the full width. */
.pcheck {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 4px 0 0;
}

.pcheck__sym {
  box-sizing: border-box;
  width: 220px;
  min-width: 0;
  height: 35px;
  padding: 0 16px;
  background: var(--surface);
}
.pcheck__sym input { text-transform: uppercase; }

.pcheck__sel,
.pcheck__date {
  box-sizing: border-box;
  height: 35px;
  padding: 0 14px;
  border: var(--hair) solid var(--border-soft);   /* as .fpill at rest */
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text);
  font: 500 12.5px/1.2 var(--font-mono);
  cursor: pointer;
}
.pcheck__sel { padding-right: 30px; }
.pcheck__sel:hover, .pcheck__date:hover { border-color: var(--border); }
.pcheck__sel:focus-visible, .pcheck__date:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.pcheck__go { height: 35px; }
.pcheck__go:disabled { opacity: .55; cursor: progress; }

.pcheck__msg {
  min-height: 20px;
  margin: 12px 0 0;
  font-size: 13.5px;
  color: var(--text-3);
}
.pcheck__msg.is-err { color: var(--down); }

.pcheck__res { margin-top: 26px; }

/* The overview: the year's bars with every pattern shaded and numbered. Drawn
   by the page's script into an SVG that scales with the card; every colour is
   a class so the chart follows the theme. */
.pcheck__chart { padding: 10px 6px 4px; overflow-x: auto; }
.pcheck__chart svg { display: block; width: 100%; height: auto; min-width: 560px; }
.ov__grid { stroke: var(--grid-line); }
.ov__ax { font: 500 11px var(--font-mono); fill: var(--text-4); }
.ov__up { stroke: var(--up); fill: var(--up); }
.ov__dn { stroke: var(--down); fill: var(--down); }
.ov__span { fill: var(--accent); opacity: .12; }
.ov__num { cursor: pointer; }
.ov__num circle { fill: var(--bg); stroke: var(--accent); stroke-width: 1.4; }
.ov__num text { font: 600 10px var(--font-mono); fill: var(--text); }
.ov__num:hover circle, .ov__num:focus circle { fill: var(--accent); }
.ov__num:hover text, .ov__num:focus text { fill: #ffffff; }
.ov__num:focus { outline: none; }

/* The results, one per pattern, newest first: the same surface as the feed's
   grid cards, opening the same modal. */
.pcheck__list {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  gap: 10px;
}

.pcheck__item button {
  box-sizing: border-box;
  width: 100%;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  gap: 3px 14px;
  align-items: center;
  padding: 14px var(--pad);
  border: var(--hair) solid var(--border);
  border-radius: var(--r-panel);
  background: var(--surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.pcheck__item button:hover { border-color: var(--border-strong); }
.pcheck__item button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.pcheck__n {
  grid-row: 1 / 3;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: var(--hair) solid var(--accent);
  display: grid;
  place-items: center;
  font: 600 11px var(--font-mono);
  color: var(--text);
}
.pcheck__t { font-size: 15px; font-weight: 600; color: var(--text); }
.pcheck__badges { grid-column: 3; grid-row: 1 / 3; }
.pcheck__p { grid-column: 2; font-size: 13px; color: var(--text-3); }

.pcheck__none {
  padding: 18px var(--pad);
  color: var(--text-3);
  border: var(--hair) dashed var(--border-strong);
  border-radius: var(--r-panel);
}

@media (max-width: 640px) {
  .pcheck__sym { width: 100%; }
  .pcheck__go { width: 100%; }
  .pcheck__badges { grid-column: 2; grid-row: auto; }
}


/* ===========================================================================
   Channels, tidied (CP, 2026-10-02)
   ===========================================================================
   Three bands became one. The page had a title block, a padded filter panel
   and a heading band over the table, each with its own left edge. Now the title
   and the filters share the page's edge, one line of small print says the hold
   time and the count, and the table follows it directly. */

/* The title and the search on one line. */
.chead--bar {
  align-items: center;
  margin-bottom: 37px;    /* 26 + 11 (CP, 2026-10-02): the title needed more air over the filters */
}
.chead--bar .stream-title { margin: 0; }
@media (max-width: 640px) {
  .chead--bar .csearch { width: 100%; }
}

/* The filters start at the page's edge, under the title. Above 860px only:
   below it the bar is the bottom sheet and keeps its padding. */
@media (min-width: 861px) {
  [data-page="channels"] .fbar.card--pad,
  [data-page="trending"] .fbar.card--pad { padding: 0; }
}

/* One line of small print between the filters and the table: what the list is
   measured at, how many rows match, and anything the reader should know about
   what they asked for. It keeps the table's inset, so it lines up with the
   first column's text; the column picker, where a page puts it here, ends on
   the table's right edge. */
.cstat {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 20px;
  padding: 0 var(--pad);
  margin: 0 0 6px;
}

.cstat__text {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 20px;
}

.cstat__sep {
  margin: 0 5px;
  color: var(--text-4);
}

/* Below 860px the Filters button already shows the count beside it. */
@media (max-width: 860px) {
  .cstat__count--dup { display: none; }
}

/* The column picker wearing a column head's type instead of a pill's outline.
   It is a filter pill in the markup, so the same script opens it. Two places:
   as the head of a column that has no label of its own (Channels' follow
   column), and at the right end of the small print above (Trending). */
.colpick .fdrop { display: inline-block; }
th.colpick { overflow: visible; }

.colpick .fpill {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: inherit;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-3);
}
.colpick .fpill:hover,
.colpick .fpill[aria-expanded="true"],
.colpick .fpill.is-set { color: var(--text); }
.colpick .fpill:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: 3px;
}

/* The panel is a child of the head, and would inherit its capitals. */
.colpick .fdrop__panel {
  left: auto;
  right: 0;
  top: calc(100% + 10px);
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  white-space: normal;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text);
}

/* .table-wrap scrolls sideways, which makes it clip downwards too. On a list of
   two or three rows the open panel is taller than the table, so the wrap makes
   room for it while it is open. */
.table-wrap:has(.colpick .fpill[aria-expanded="true"]) { min-height: 520px; }

/* The settings icon a pill carries in place of its caret. */
.fpill__icon {
  display: inline-flex;
  flex: none;
  opacity: 0.7;
}
.fpill__icon svg { display: block; }
.fpill:hover .fpill__icon,
.fpill[aria-expanded="true"] .fpill__icon { opacity: 1; }
