/* ============================================================
   Investment 2.0 — Components
   ------------------------------------------------------------
   Semantic component classes only. Every value is a token.
   New pattern? Add the class here + a macro in templates/ui/
   + an entry on /styleguide — in that order.
   ============================================================ */

/* ---- reset-ish base ---------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }
body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  color: var(--color-text);
  background: var(--color-bg);
}
h1, h2, h3, p { margin: 0; }
/* The boards tighten their display type optically, and the application did not.
   `Main.dc.html` carries `h1, h2, h3 { letter-spacing: -0.01em }` against the
   app's `normal` — measured, a seventh of a pixel on a pane title. Invisible in
   any one element and the reason a page of them did not read like the board. */
h1, h2, h3 { letter-spacing: -0.01em; }
/* A link is text, underlined, not a colour. Counted across the 47 artboards
   there is no blue anywhere in the canvas: the only blue-dominant value in the
   whole directory sits in GAPS.md, recording that the application uses one
   where the artboard is neutral. The canvas draws `a { color: var(--text);
   text-decoration: underline; text-decoration-color: var(--border-strong) }`,
   and this is that. Colour in a financial surface is reserved for direction
   and for attention; spending it on navigation makes both quieter.
   Owner decision, 11 September: drop the blue product-wide. */
a {
  color: var(--color-text);
  text-decoration: underline;
  text-decoration-color: var(--color-border-strong);
  text-underline-offset: var(--underline-offset);
}
a:hover { text-decoration-color: var(--color-text); }
:focus-visible {
  outline: var(--outline-w) solid var(--color-focus);
  outline-offset: var(--outline-w);
  border-radius: var(--radius-sm);
}
/* tabindex="-1" marks a place focus is moved to (a dialog heading, a panel),
   never a control, so it draws no ring. */
[tabindex="-1"]:focus-visible { outline: none; }
img, svg, canvas { max-width: 100%; }
/* A pending row action takes the second selector: its label leaves the box so
   the spinner can stand where the words were, and stays in the accessibility
   tree so the button still answers to the name that was pressed. Same rule,
   not a copy of it — there is one way to be heard and not seen. */
.sr-only,
.btn--pending .btn__label {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: var(--border-w);
  height: var(--border-w);
  padding: 0;
  margin: calc(-1 * var(--border-w));
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- layout primitives ------------------------------------- */
/* The page's own margin, the same on both sides. It used to be --gutter,
   which is the gap between blocks: one value doing two jobs, and the frame
   read 16 left where the artboards draw 20. */
.container {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--margin);
}
.container--narrow { max-width: var(--content-narrow); }

/* vertical rhythm: children stacked with one consistent gap */
.stack   { display: flex; flex-direction: column; gap: var(--space-4); }
.stack--tight { gap: var(--space-2); }
.stack--loose { gap: var(--space-6); }

/* horizontal grouping that wraps */
.cluster { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* responsive card grid (dashboard tiles) */
.grid-tiles {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}


/* ---- the frame: a navigation column and a title bar --------
   Signal v2 replaces the horizontal header with a rail. Eight
   destinations fit down the side where five fitted across, each
   one named rather than reduced to an icon, and the count of
   what needs checking sits on the destination that answers it
   instead of on a page nobody has opened yet.

   Below the phone breakpoint neither the rail nor a top bar of
   links applies: the bottom section bar already carries
   navigation one-handed, and it keeps that job. */
.shell {
  display: grid;
  grid-template-columns: var(--nav-rail) minmax(0, 1fr);
  min-height: 100vh;
  /* The frame centres as one object on a display wider than it needs.
     Centring the content *inside* the shell is the thing to avoid: it
     detaches the page from the rail that navigates it and the bar that
     names it, leaving two left edges. Moving them together keeps them
     one thing and puts the spare width evenly on both sides. */
  max-width: calc(var(--content-wide) + var(--nav-rail));
  margin-inline: auto;
}
.shell__column { display: flex; flex-direction: column; min-width: 0; }
/* The access screens have no rail: one column, and the title bar carries
   only the theme control. */
.shell--bare { grid-template-columns: minmax(0, 1fr); }

/* The rail fills the viewport and stays there, so sign out sits bottom left
   on every page whatever the page's length. align-self keeps it off the grid
   row's full height — a rail as tall as a long ledger would put sign out
   thousands of pixels down — so the height is the viewport, not the page. */
.rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-self: start;
  position: sticky;
  inset-block-start: 0;
  height: 100vh;
  overflow-y: auto;
  padding: var(--space-4) var(--space-2);
  background: var(--color-surface);
  border-inline-end: var(--border-w) solid var(--color-border);
}
/* The mark is one self-contained SVG — tile, numeral and badge
   travel together — so this only places it. */
.rail__mark { display: block; margin-inline: auto; margin-block-end: var(--space-3); }

/* The badge reports the day: red down, green up, grey when there is
   no reading to give — a closed market, a stale valuation, or a page
   that never had a portfolio behind it.

   Colour is never the whole message. The macro writes the state into
   the mark's accessible name, so a screen reader hears "Inv.2 — down
   today" and does not have to see the dot at all.

   A mark with no data-state is the plain brand badge and keeps the
   pinned green, so nothing changes until a page says otherwise. The
   mark does not animate (see .rail__mark transition: none): the dot
   changes when the page does, without drawing the eye to itself. */
.rail__mark[data-state="up"]   { --brand-dot: var(--brand-dot-up); }
.rail__mark[data-state="down"] { --brand-dot: var(--brand-dot-down); }
.rail__mark[data-state="flat"] { --brand-dot: var(--brand-dot-flat); }
.rail__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--nav-rail-item);
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-size: var(--text-nav-compact);
  font-weight: 500;
  text-align: center;
  text-decoration: none;
}
.rail__item:hover {
  color: var(--color-text-secondary);
  background: var(--color-surface-subtle);
  text-decoration: none;
}
.rail__item[aria-current="page"] {
  color: var(--color-text);
  background: var(--color-surface-selected);
}
.rail__brand { display: block; text-decoration: none; }
/* Sign out is a form submit, not a link, because signing out is a write.
   It still has to sit in the column like every other destination. */
.rail form { margin: 0; }
.rail button.rail__item { width: 100%; border: 0; background: none; cursor: pointer; font-family: inherit; }
.rail__spacer { flex: 1 1 auto; min-height: var(--space-4); }
/* Amber on the destination that owns the work, not a page-level
   alarm: one number, and clicking it is the way to clear it. */
.rail__new {
  /* A mark, not a count, and the distinction is the one that was kept:
     the owner decided on 18 September 2026 that nothing waits on a reader
     as a standing queue, and a number is that queue said in one digit. A
     dot says only that there is something new to read.

     The colour is the counter's own, by the owner's instruction of
     19 September 2026. I had argued for the accent on the grounds that
     amber on this rail has only ever meant work outstanding; they want the
     mark to read the way the old badge read, and it is their rail. */
  position: absolute;
  inset-block-start: var(--space-1);
  inset-inline-end: var(--space-2);
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-warn);
}

.rail__count {
  position: absolute;
  inset-block-start: var(--space-1);
  inset-inline-end: var(--space-2);
  min-width: var(--space-4);
  padding-inline: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-warn);
  color: var(--color-surface);
  font-size: var(--text-nav-compact);
  font-weight: 700;
  line-height: var(--space-4);
  text-align: center;
}
/* The rail's own menu opens beside it rather than below. */
.rail .shell-menu > summary {
  flex-direction: column;
  gap: var(--space-1);
  min-height: var(--nav-rail-item);
  border-radius: var(--radius-sm);
  font-size: var(--text-nav-compact);
}
.rail .shell-menu > summary::after { content: none; }
.rail .shell-menu__items {
  inset-inline-start: 100%;
  inset-block-start: 0;
  margin-inline-start: var(--space-2);
}

.topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-block-size: var(--titlebar-height);
  padding: var(--space-3) var(--margin);
  background: var(--color-bg);
  border-block-end: var(--border-w) solid var(--color-border);
}
.topbar__title {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-inline-end: auto;
  min-width: 0;
}
.topbar__title h1 {
  font-size: var(--text-page-title);
  font-weight: 600;
  line-height: var(--leading-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar__meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar__actions { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
/* Bigger in the title bar than in a toolbar, as the boards draw it: the bar
   is the frame and its controls are hit targets, the toolbar's belong to the
   page and sit beside a segmented control the same height. */
.topbar__actions .iconbtn { width: var(--control-height); height: var(--control-height); }
.topbar__actions form { margin: 0; }

/* The approved phone bar gives the title a floor and the scope the spare
   width. Status belongs below that row; the reference subtitle is redundant
   with the page's labelled figures at this width. */
.topbar__context { display: contents; }
.topbar__back, .portfolio-scope .portfolio-scope__picker { display: none; }
.portfolio-scope { min-width: 0; }
@media (max-width: 48rem) {
  .topbar {
    display: grid;
    grid-template-columns: minmax(9ch, 1fr) minmax(0, auto) auto;
    grid-template-areas: "title scope actions" "context context context";
    column-gap: var(--space-2);
    row-gap: 0;
  }
  .topbar--back {
    grid-template-columns: auto minmax(9ch, 1fr) minmax(0, auto) auto;
    grid-template-areas: "back title scope actions" "context context context context";
  }
  .topbar__title { display: contents; }
  .topbar__title h1 { grid-area: title; min-width: 9ch; }
  .topbar__context { display: none; }
  .topbar__context--status {
    display: flex;
    grid-area: context;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-block-start: var(--space-2);
  }
  .topbar__meta { display: none; }
  .topbar__back { display: block; grid-area: back; }
  .topbar__back .btn { min-height: var(--touch-target); }
  .topbar__actions { grid-area: actions; }
  .topbar__actions .iconbtn {
    width: var(--touch-target);
    height: var(--touch-target);
  }
  .portfolio-scope { grid-area: scope; }
  .portfolio-scope .portfolio-switcher { display: none; }
  .portfolio-scope .portfolio-scope__picker { display: block; min-width: 0; }
  .portfolio-scope__picker > summary {
    display: flex;
    min-height: var(--touch-target);
    gap: var(--space-1);
    list-style: none;
  }
  .portfolio-scope__picker > summary::-webkit-details-marker { display: none; }
  .portfolio-scope__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topbar .portfolio-scope__picker { position: static; }
  .topbar .portfolio-scope__picker .row-menu__sheet {
    inset-inline-end: var(--margin);
    min-inline-size: min(13rem, calc(100vw - 2 * var(--margin)));
    max-inline-size: calc(100vw - 2 * var(--margin));
    overflow-wrap: anywhere;
  }
}

/* ---- shell ------------------------------------------------- */
.shell-menu--all { display: none; }
.shell-menu--all > summary {
  min-height: var(--control-height);
  padding-inline: var(--space-3);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-lg);
  line-height: 1;
}
.shell-menu--all .shell-menu__items { right: 0; left: auto; }
/* Destinations, like the rail's and the phone bar's: named, not underlined. */
.shell-menu__items > a { text-decoration: none; }
.shell-menu--all > summary::after { content: none; }
.shell-menu > summary {
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--outline-w) solid transparent;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
}
.shell-menu > summary::-webkit-details-marker { display: none; }
.shell-menu > summary::after {
  content: "▾";
  margin-inline-start: var(--space-1);
  font-size: var(--text-xs);
}
.shell-menu > summary:hover,
.shell-menu > summary:focus-visible {
  background: var(--color-surface-subtle);
  color: var(--color-text);
}
.shell-menu[data-nav-current="true"] > summary {
  border-bottom-color: var(--color-text);
  color: var(--color-text);
}
/* The artboards' work area is a column with one gutter between its blocks,
   not a block box whose children each bring their own margin. Stated here so
   anything placed directly in the work area is spaced by the layout rather
   than by whatever it was given elsewhere. */
.shell-main {
  display: flex;
  flex-direction: column;
  row-gap: var(--gutter);
  /* The top pays the same space the page's own rows pay each other, so the
     band under the title bar's rule reads as one more gap in the rhythm
     rather than as a wider margin. It was --space-6 against a --space-5 gap,
     one step more above the first row than between any two below it, which
     is visible as soon as the rule gives the eye an edge to measure from. */
  padding-block: var(--space-5) var(--space-8);
  /* The query container the column-priority rules read, named as the canvas
     names it. It is the work area rather than the viewport because the rail
     takes width off the work area without taking it off the window, and a
     column has to drop when the table runs out of room, not when the screen
     does. Nothing inside the work area is position: fixed — the title bar,
     the section bar and the rail are all siblings of it — so the layout
     containment this brings has nothing to capture. */
  container-type: inline-size;
  container-name: work;
}
/* Inside the frame the rail sets the page's left edge, so the content
   aligns to it rather than centring itself. A centred box under a title
   bar that starts at the rail gives the page two different left edges,
   and the wider the screen the further apart they drift. On an ultrawide
   display the heading sat a third of the viewport left of everything it
   described. The cap stays,
   because a table is worth widening and a paragraph is not. */
.shell__column .container {
  margin-inline: 0;
  max-width: var(--content-wide);
}
.shell__column .container--narrow { max-width: var(--content-narrow); margin-inline: auto; }
/* Auto margins otherwise shrink the access card's flex parent to its text. */
.shell__column .container--narrow:has(.workbench--access) { width: 100%; }

/* ---- page header ------------------------------------------- */
.page-header { display: flex; align-items: end; justify-content: space-between; gap: var(--space-4); }
.page-header h1 { font-size: var(--text-xl); line-height: var(--leading-tight); }
.page-header__meta { color: var(--color-text-muted); font-size: var(--text-sm); }

/* ---- card -------------------------------------------------- */
.card {
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
}
.card__title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-3);
}

/* ---- stat tile (Mode A) ------------------------------------ */
.stat { display: flex; flex-direction: column; gap: var(--space-1); }
.stat__label { font-size: var(--text-xs); color: var(--color-text-muted); }
.stat__value { font-size: var(--text-2xl); font-weight: 700; line-height: var(--leading-tight); font-variant-numeric: tabular-nums; }
.stat__delta { font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.definition__summary {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: var(--space-1);
  cursor: help;
}
.definition__body {
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  padding-top: var(--space-1);
}
.definition--compact .definition__body { max-width: var(--content-narrow); }

/* ---- money & deltas ---------------------------------------- */
/* Always render an explicit sign in the template — colour is
   never the only signal (accessibility + printing). */
/* The board's rule is `.num { font-variant-numeric: tabular-nums;
   letter-spacing: -0.01em }`, and `.num` is on every number the product
   prints. See `.table th.num, .table td.num` below for why the tracking has to
   be repeated there. */
.num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; letter-spacing: -0.01em; }
.gain { color: var(--color-gain); }
.loss { color: var(--color-loss); }
.warn { color: var(--color-warn); }

/* ---- table ------------------------------------------------- */
.table-wrap {
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;  /* tables scroll, pages never do */
  container-type: inline-size;
  container-name: work;
}
/* A page whose content is shorter than the frame used to leave the slack under
   its last pane as dead space: the shell is at least the viewport tall, the
   scroll window was a flat 34rem, and on a tall display that meant eight rows
   of forty-one above a band of nothing.

   The slack goes to the pane that can spend it, and only as far as it can
   spend it -- see .pane--fill .table-wrap--scroll-window at the foot of this
   block. Opt-in, by a modifier the page sets, because the chain from the frame
   to the table runs through .container and .stack, which every page shares --
   making it unconditional would move every layout to fix one. A page that does
   not ask keeps today's behaviour exactly.

   34rem stops being the ceiling: a screenful is. */
.shell__column:has(.workbench--fill) .shell-main,
.shell__column:has(.workbench--fill) .shell-main > .container,
.shell__column:has(.workbench--fill) .shell-main > .container > .stack {
  flex: 1;
  min-block-size: 0;
}
.shell__column:has(.workbench--fill) .shell-main > .container {
  display: flex;
  flex-direction: column;
}
/* A page that fills the frame ends at its last pane. The work area's tail --
   the space every other page leaves under its final block -- is what left the
   Positions pane's sums floating above a band of background at the end of the
   scroll: the page had already said everything it had to say and was still
   painting. Below 48rem that tail is not spare space but the clearance for the
   fixed section bar, so the phone rule below keeps it. */
.shell__column:has(.workbench--fill) .shell-main { padding-block-end: 0; }
.workbench--fill { flex: 1; min-block-size: 0; }
/* The pane is not a child of the workbench: it sits in a full-width row of the
   split grid. Measured rather than assumed -- the first version of this stopped
   at the grid and changed nothing, because a grid child does not inherit a flex
   parent's slack. So the grid takes the slack as a flex child, gives it to its
   last row, and that row passes it down. */
.workbench--fill > .workbench-layout { flex: 1; min-block-size: 0; }
.workbench--fill > .workbench-layout--split { grid-template-rows: auto 1fr; }
.workbench-layout__wide:has(> .pane--fill) {
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}
.workbench--fill .pane--fill,
.workbench--fill > .pane--fill {
  /* The pane takes the slack its table can use and no more. Growing it
     unconditionally moved the dead band from under the pane to inside it,
     which is worse: a page that ends early reads as a page that ended, and a
     bordered pane with a footing floating two hundred pixels above its own
     bottom edge reads as broken. */
  flex: 0 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}
.pane--fill > .pane__body {
  flex: 1;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}
.pane--fill .table-wrap--scroll-window {
  /* The ceiling rises from a flat 34rem to a screenful, and that is the whole
     of what filling buys: a long table on a tall display shows seventeen rows
     instead of eight, and still scrolls inside.

     What it must not do is grow past its own last row. It did -- `flex: 1`
     with a 34rem floor -- and the footing then stood in mid-air over a band of
     nothing: 90 pixels under six positions in a window 900 tall, 197 in one
     1400 tall, and 633 under three rows on Performance. A table shorter than
     the window is the case the fill chain never had, because the pane it hands
     the slack to cannot spend it.

     Sizing to content costs nothing where it used to work: with more rows than
     the window holds, the ceiling governs either way, and the pane, the window
     and the page measure to the pixel what they did before. */
  flex: 0 1 auto;
  max-block-size: var(--table-window-cap);
  min-block-size: 0;
}

.table-wrap--scroll-window {
  max-block-size: var(--table-window-height);
  overflow-y: auto;
  scrollbar-gutter: stable;
  scrollbar-color: var(--color-border-strong) transparent;
  scrollbar-width: thin;
}
.table-wrap--scroll-window::-webkit-scrollbar {
  width: var(--scrollbar-size);
  height: var(--scrollbar-size);
}
.table-wrap--scroll-window::-webkit-scrollbar-track { background: transparent; }
.table-wrap--scroll-window::-webkit-scrollbar-thumb {
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
}
.table { width: 100%; border-collapse: collapse; font-size: var(--text-table); }
.table th, .table td {
  padding: var(--space-3) var(--margin);
  border-bottom: var(--border-w) solid var(--color-border);
  text-align: left;
}
/* A column heading hangs off the row below it rather than sitting in a band
   of its own: no space above, and the rule under it is the row's. */
.table thead th { padding-block: 0 var(--space-column-head); }
/* One of the two things the artboards still set in capitals — measured over
   all 46 boards, a column heading and an eyebrow are the whole list. So this
   survived the pass that took sentence case through the rest of the sheet.
   Its size is its own: tallied across Main, Ledger and SoldHoldings a column
   heading is 11/500, a step below the 12 the tile labels take. */
.table th {
  white-space: nowrap;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  font-weight: 500;
}
/* The tracking is repeated here, not inherited from `.num`. Two rules below
   reset `letter-spacing: normal` to undo the capitals a column heading carries
   — `.table tbody th` and `.table tfoot > tr > *` — and both out-specify a bare
   `.num`, so the figures in a row heading and in every footing would have
   silently kept `normal` while the rest of the page tightened. Two classes beat
   one class and two elements. */
.table th.num, .table td.num { text-align: right; letter-spacing: -0.01em; }
.table tbody th {
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
}
.table tbody tr:hover { background: var(--color-surface-subtle); }
.table--dense th,
.table--dense td { padding: var(--space-2) var(--margin); }
.pane__body--flush .table > * > tr > :first-child { padding-inline-start: var(--margin); }
.pane__body--flush .table > * > tr > :last-child { padding-inline-end: var(--margin); }
.table--fixed { table-layout: fixed; }
.table--activity-ledger th,
.table--activity-ledger td { white-space: nowrap; }
.table--activity-ledger td:first-child { color: var(--color-text-secondary); }
.table--fixed.table--activity-ledger td:not(.num) { white-space: normal; overflow-wrap: anywhere; }
/* The columns are sized by what they hold. Equal shares gave a date and a
   one-word type as much room as the instrument name, which then broke over
   four lines while the action column stood empty. */
@media (min-width: 48.0625rem) {
  .table--fixed.table--activity-ledger thead th:nth-child(1) { width: 8.5rem; }
  .table--fixed.table--activity-ledger thead th:nth-child(2) { width: 8rem; }
  .table--fixed.table--activity-ledger thead th:nth-child(4),
  .table--fixed.table--activity-ledger thead th:nth-child(7) { width: 9%; }
  .table--fixed.table--activity-ledger thead th:nth-child(5),
  .table--fixed.table--activity-ledger thead th:nth-child(6) { width: 12%; }
  .table--fixed.table--activity-ledger thead th:nth-child(8) { width: 4.5rem; }
}

/* ---- an entry log: one entry over two lines ------------------
   Five columns of one line each is what put the amount off the right edge of
   the pane it lives in -- an instrument name is as long as its issuer chose,
   and the figure the list exists to confirm was the thing that went. Two
   lines instead: the identity over what the entry was, the costs, and the
   amount. It then fits any width the pane is given, and the width it is given
   goes on what the row already knows rather than on padding. */
.table--entry-log th[scope="row"] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-3);
}
.table--entry-log th[scope="row"] > .instrument-identity { flex: 1 1 auto; min-inline-size: 0; }
/* The note sits under the identity it belongs to, indented past the mark so
   the three lines of a row read as one block. */
.entry-log__note {
  flex: 1 0 100%;
  padding-inline-start: calc(var(--identity-mark) + var(--space-2) + var(--space-2));
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.table--entry-log td:not(.num) { white-space: normal; }
.table--entry-log .instrument-identity__meta { font-variant-numeric: tabular-nums; }
.table--entry-log .num { white-space: nowrap; font-variant-numeric: tabular-nums; }
/* One date a day, rather than the same date down twenty rows. Entries that
   share an afternoon then read as an afternoon. */
.entry-log__day > th {
  padding-block: var(--space-1) 0;
  color: var(--color-text-muted);
  font-size: var(--text-micro);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: transparent;
  border-block-end: 0;
}
.entry-log__day + tr > * { border-block-start: 0; }
/* What the entry cost, beside what it was for. The amount is gross, so
   without these the row cannot say what left the account. */
.table--entry-log td.entry-log__costs { inline-size: 1%; text-align: end; }
.entry-log__cost {
  display: block;
  white-space: nowrap;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
/* The same figure in the portfolio's own money. Entries in five currencies
   have no common measure without it, and every total elsewhere is in one. */
.entry-log__base {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
.table--sticky thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--color-surface);
  border-bottom-color: var(--color-border-strong);
}
/* The symmetric rule for a footing. A table that scrolls inside a window keeps
   its headings in view; the row that sums it has to stay in view for the same
   reason, or the sums are only found by scrolling to the end of 41 rows. */
.table--sticky tfoot > tr > * {
  position: sticky;
  bottom: 0;
  /* Above the sticky headings, not level with them: a row scrolling past has
     to go behind the sums, and a tie is resolved by document order, which
     puts the heading on top and lets the row show through beside it. */
  z-index: 2;
  background: var(--color-surface);
  /* The cells are the whole footing. Sticking them lifts them out of their
     row's painted box -- the row element stays at its document position,
     which on a scrolled table is far below the window, so a background on
     the row paints nothing where the footing actually is. Every part of the
     footing's appearance therefore belongs on the cells.

     The band has to be opaque over its whole height, and its own border is
     what stops that: with the borders collapsed a cell's background reaches
     only the inner edge of the collapsed border, so the border's own band
     stays unpainted and the row sliding behind it reads through the seam.
     Dropping the border is what closes it -- no clip value helps, because
     every one of them paints less rather than more. The edge the footing
     actually needs is the one above it, drawn below. */
  border-bottom: 0;
}
/* The top edge is what stops a row sliding behind the sums from reading as one
   of them. Without it the half of a row still above the footing runs straight
   into the totals with nothing between, which is what "the body bleeds through
   under the footer" describes.

   Neither obvious way to draw it works on this cell. A border belongs to the
   table under border-collapse rather than to the cell, so it scrolls away with
   the table; and a box-shadow is not painted on a cell at all while the
   borders are collapsed. What is left is a box of its own, anchored to the
   cell -- which is a positioned element already, because it is sticky. */
.table--sticky tfoot > tr > *::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: var(--border-w);
  background: var(--color-border-strong);
}
.table--data td.num,
.table--data th.num { font-family: var(--font-data); }
/* Narrow-rail tables: row headers may wrap, and identity metadata (symbol,
   MIC, ISIN) breaks anywhere rather than widening the table past its pane. */
.table--wrap tbody th { white-space: normal; }
.table--wrap .table__meta { overflow-wrap: anywhere; }
.table--wrap td:last-child:not([colspan]) { white-space: nowrap; }
.table--responsive tbody th { white-space: normal; overflow-wrap: break-word; }
.table--responsive td.num { white-space: nowrap; }
/* --- column priority ----------------------------------------
   The artboards hide columns by width, not by stacking them. A column carries
   a priority and drops out of the table as the work area narrows: p4 goes
   first, p3 next, and what is left is the reading the page exists for.
   Anything unmarked is p2 and never drops.

   The condition is the nearest table wrapper's width rather than the wider
   work area or viewport. A table inside a narrow pane must drop columns
   before its surrounding work area does. 47.5rem and 68.75rem are the
   canvas's own two thresholds from
   signal2/parts/head.html, in the unit this stylesheet is allowed to write.

   Below the p3 threshold both are gone before the phone reflow begins, so a
   row that becomes a card carries only the columns that survived: the two
   mechanisms compose rather than each paying for the same column. */
.p3, .p4 { display: none; animation: priority-column-in var(--t-base) var(--ease-out) backwards; }
@keyframes priority-column-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .p3, .p4 { animation: none; } }
@container work (min-width: 47.5rem) {
  /* table-cell is the value these cells started with. Inside the phone
     reflow's grid row a grid item is blockified anyway, which is what a
     card needs, so one restore serves both layouts. */
  .p3 { display: table-cell; }
}
@container work (min-width: 68.75rem) {
  .p4 { display: table-cell; }
}
/* The board's rows, not a tighter set: 12 down and the pane's gutter across.
   The identity cell carries two lines, so squeezing the row only made the
   second line touch the rule under it. */
.table--positions th,
.table--positions td {
  padding: var(--space-3) var(--margin);
  vertical-align: middle;
}
.table--positions .num,
.table--positions th:last-child,
.table--positions td:last-child { white-space: nowrap; }
.table--positions tbody tr:last-child > * { border-bottom: 0; }
/* Automatic table layout hands spare width to the widest text column, so
   on a large display the instrument name took roughly a third of the table
   for a name that needed a fifth of it, while the figures people actually
   scan stayed crammed against the right edge. Capping the identity column
   sends the slack to the numbers instead. It is a share rather than a
   fixed size so a narrow window is unaffected: below the cap the column
   simply takes what it needs. */
.table--positions th[data-sort-key="title"],
.table--positions th[scope="row"],
.table--positions th[scope="rowgroup"] { max-width: 34%; width: 34%; }

/* The column of instruments an article names, on a holding's headlines pane.
   Left to size itself it took a quarter of the table -- 308 of 1295 at 1440,
   on a holding whose articles name five to nine symbols each -- and it took
   that quarter out of the extract, which is what the reader is there for.
   Half of what it took, as a share so a narrow window is unaffected. The
   names inside are badges, and a badge does not wrap, so the cap moves a
   name onto the next line and never breaks one. */
.table th.names-col,
.table td.names-col { max-width: 12%; width: 12%; }
.table-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: inherit;
  text-decoration: none;
}
.table th.num .table-sort {
  width: 100%;
  justify-content: flex-end;
}
.table-sort__indicator { color: var(--color-text); }
.table tbody tr.is-selected { background: var(--color-surface-selected); }
.table tbody tr.is-breach > :first-child { border-left: var(--accent-bar-w) solid var(--color-warn); }
.table tbody tr.is-stale > :first-child { border-left: var(--accent-bar-w) solid var(--color-border-strong); }
/* A watch condition the reader set has been met (issue 1076). The accent, not the
   warning: amber is kept for data that is behind. */
.table tbody tr.is-met > :first-child { border-left: var(--accent-bar-w) solid var(--color-accent); }
/* A footing row heading is a `th`, so it inherited the capitals the rule above
   gives a column heading and read "6 OF 6 SHOWN". The canvas capitalises the
   heading of a column and nothing else: its footing says "9 of 41 shown". */
.table tfoot > tr > * {
  /* A footing cell pays exactly what a body cell pays, because the rule that
     closes a table is drawn per cell and has to end where the column does.
     This took 8 on every side, so a middle footing cell sat 12 inside its
     own column and the closing line stopped short of it on both edges. Only
     the outer two looked right, and only because the two `.pane__body--flush`
     rules beside `.table--dense` put them back at the margin -- which is why
     this survived a portal of tables: the edge a reader checks was the one
     case being corrected. */
  padding: var(--space-3) var(--margin);
  border-top: var(--outline-w) solid var(--color-border-strong);
  font-weight: 700;
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--text-table);
}
.table__primary { font-weight: 600; color: var(--color-text); }
.table__meta { display: block; color: var(--color-text-muted); font-size: var(--text-xs); }
/* The reader's own words rather than ours: a thesis note they typed, the
   expectation they wrote. The prose cap governs the copy this product
   authors, and it steps over this slot -- nobody can shorten what somebody
   else wrote, and truncating it silently would hide their own text from
   them. The wrapping is therefore stated here rather than inherited from a
   wrapping table, because the cap is no longer guarding this width and a
   typed sentence with no spaces should break rather than push the column. */
.table__meta--data { overflow-wrap: anywhere; }
/* The same, on the one strip foot that names a holding rather than
   stating a fact this product wrote: a company name has no break
   opportunity a tile this narrow can rely on. */
.summary-item__meta--data { overflow-wrap: anywhere; }

/* ---- instrument identity ---------------------------------- */
.instrument-identity {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
}
/* Always drawn, never empty: the tile and its two characters are the mark,
   and a logo is laid over them when one loads. An image that 404s therefore
   falls back to what the boards draw rather than to a hole in the column. */
.instrument-identity__logo {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--identity-mark);
  height: var(--identity-mark);
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--color-surface-selected);
  color: var(--color-text-secondary);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* No background of its own. An image that cannot load paints nothing, so the
   tile's own letters show through instead of a grey hole down the column —
   which is the whole reason the letters are there. A logo that does load is
   opaque and square, and covers them. */
/* A subject with no logo to fall back to: the slot keeps its size and its
   place in the row, and draws a figure instead of two letters of a name the
   database supplied. */
.instrument-identity__logo--glyph { color: var(--color-text-secondary); font-size: 0; }
.instrument-identity__logo--glyph .glyph { inline-size: 60%; block-size: 60%; }
.instrument-identity__mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The broker chooser: three ways in, chosen before there are any files.
   Tiles rather than a segmented strip because each carries a mark, and a mark
   in a segmented item makes a control that reads as a toolbar. */
.broker-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.broker-choice__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-3);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  text-decoration: none;
}
.broker-choice__item:hover { border-color: var(--color-border-strong); color: var(--color-text); }
/* The chosen one is stated, not merely tinted: aria-current is what a screen
   reader reads, and the border is what everyone else does. */
.broker-choice__item[aria-current="page"] {
  border-color: var(--color-accent);
  background: var(--color-surface-selected);
  color: var(--color-text);
}
.broker-choice__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--identity-mark);
  height: var(--identity-mark);
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--color-surface-selected);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* Same reasoning as the instrument mark: a logo that fails to load paints
   nothing and the letters underneath show through. */
.broker-choice__logo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.broker-choice__name { font-weight: 600; min-width: 0; }
.instrument-identity__copy {
  display: inline-flex;
  flex-direction: column;
  min-width: 0;
}
.instrument-identity__name { min-width: 0; }
.instrument-identity__meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
}
.instrument-identity--position .instrument-identity__logo {
  width: calc(var(--space-6) + var(--space-2));
  height: calc(var(--space-6) + var(--space-2));
  padding: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
}
/* A subject that is not a company keeps the slot and draws nothing in it, so
   the names below and above it stay in one column. Dropping the element
   instead moved "SIX Swiss Exchange" a mark's width left of every other row
   in Ahead — invisible in the markup and in the golden diff, and the first
   thing the render pair showed. Sizing is inherited from the rules above;
   only the surface goes.

   Both selectors, because the surface is set twice: once on the base and
   again, more specifically, by `--position`. A single-class modifier loses
   to the second and paints an empty grey disc — which the second render
   showed and which reads as a logo that failed to load, a worse row than the
   monogram this change exists to remove. */
.instrument-identity .instrument-identity__logo--empty,
.instrument-identity--position .instrument-identity__logo--empty {
  background: none;
}
.money-cell__primary,
.money-cell__secondary { display: block; }
.money-cell__primary { font-weight: 650; }
.money-cell__secondary { color: inherit; font-size: var(--text-xs); }

/* ---- buttons ----------------------------------------------- */
/* Every control in the artboards is a pill. The rounded rectangle the kit
   started from is the one shape the design language never draws, and it is
   what makes a ported page still read as the old product: the panes changed
   and the controls did not. Weight is 500, not 600 — only the primary
   button, which is the one thing on a screen a reader should find first,
   carries the heavier face. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: var(--space-2) var(--control-pad);
  font: inherit; font-size: var(--text-table); font-weight: 500;
  border-radius: var(--radius-pill);
  border: var(--border-w) solid transparent;
  text-decoration: none;
  cursor: pointer;
}
/* The workhorse of the artboards: no border, a well rather than an outline,
   and quieter than the text it sits beside. Export, Sold holdings, See all —
   doors, not decisions. */
.btn--quiet {
  background: var(--color-surface-subtle);
  color: var(--color-text-secondary);
  border-color: transparent;
}
.btn--quiet:hover { background: var(--color-surface-selected); color: var(--color-text); }
.btn--sm {
  min-height: var(--control-height-sm);
  padding-block: 0;
  padding-inline: var(--control-pad-sm);
  font-size: var(--text-xs);
}
.btn--primary { background: var(--color-text); color: var(--color-bg); font-weight: 600; }
.btn--primary:hover { background: var(--color-text-secondary); }
.btn--ghost {
  background: transparent; color: var(--color-text);
  border-color: var(--color-border-strong);
}
.btn--ghost:hover { background: var(--color-surface-subtle); }
.btn--danger { background: var(--color-loss); color: var(--color-accent-contrast); }
.btn--danger:hover { background: color-mix(in srgb, var(--color-loss) 85%, var(--color-text)); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---- progressive workbench refresh ----------------------- */
.workbench-refresh {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.workbench-refresh__action[aria-disabled="true"] {
  opacity: 0.5;
  cursor: progress;
  pointer-events: none;
}
.workbench-refresh__status {
  color: var(--color-text-muted);
  font-size: var(--text-micro);
}
/* Nothing happened yet, so nothing is said. Keeps the live region in the
   document for the announcement without reserving a line for it. */
.workbench-refresh__status:empty { display: none; }
/* A refresh with nothing to press is behaviour rather than a control, so it
   claims no box: display:contents drops the wrapper from layout while leaving
   both children in the document -- the poll keeps its timer, and the status
   region can still appear if a fetch fails. Not display:none, which would take
   the announcement out with it. */
.workbench-refresh--silent { display: contents; }
/* ...but dropping the wrapper promotes its children, and the poll then stood as
   a flex item of the workbench in its own right: no height, nothing to see, and
   a row gap on either side of it. That put two row gaps between the Overview's
   summary strip and its first pane where every other row on the page pays one.
   Out of flow it is not an item at all. Its static position, zero size and absence of
   paint mean nothing moves and nothing can be overlaid, and unlike display:none
   it stays rendered, which is what the timer runs on. */
.workbench-refresh--silent .workbench-refresh__poll { position: absolute; }

/* ---- import progress dialog ------------------------------- */
.import-progress {
  width: calc(100% - var(--space-6));
  max-width: var(--content-dialog);
  padding: var(--space-7) var(--space-6);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  text-align: center;
}
.import-progress::backdrop { background: var(--color-backdrop); }
.import-progress__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--space-8);
  height: var(--space-8);
  margin: 0 auto var(--space-5);
  color: var(--color-accent);
}
.import-progress__ring {
  position: absolute;
  inset: 0;
  border: var(--outline-w) solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: var(--radius-pill);
  animation: spin 1s linear infinite;
}
.import-progress__check { width: var(--space-7); height: var(--space-7); visibility: hidden; }
.import-progress__spark { position: absolute; visibility: hidden; color: var(--color-accent); }
.import-progress__spark:nth-of-type(1) { top: 0; left: 0; }
.import-progress__spark:nth-of-type(2) { top: 0; right: 0; color: var(--color-warn); }
.import-progress__spark:nth-of-type(3) { bottom: 0; left: 0; color: var(--color-gain); }
.import-progress__spark:nth-of-type(4) { bottom: 0; right: 0; }
.import-progress__title { margin: 0 0 var(--space-3); font-size: var(--text-lg); }
.import-progress__status { margin: 0 0 var(--space-4); color: var(--color-text-secondary); }
.import-progress__elapsed { margin: var(--space-3) 0 0; font-size: var(--text-sm); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
/* A session imports several statements one after another, so the dialog gained
   the one thing it could not say with a spinner: how far through. The elapsed
   clock says a session is alive; this says how much of it is left.

   It fills in steps, one per statement, so the transition is what makes it
   read as motion rather than as a value jumping. */
.import-progress__track {
  width: 100%;
  height: var(--progress-bar);
  margin-top: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
  overflow: hidden;
}
.import-progress__fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transition: width var(--t-slow) var(--ease-out);
}
.import-progress[data-state="success"] .import-progress__fill { background: var(--color-gain); }
.import-progress[data-state="error"] .import-progress__track { display: none; }
@media (prefers-reduced-motion: reduce) {
  .import-progress__fill { transition: none; }
}
.import-progress__actions { margin-top: var(--space-5); }
.import-progress__actions .toolbar__group { justify-content: center; }
.import-progress[data-state="success"] .import-progress__mark { color: var(--color-gain); }
.import-progress[data-state="success"] .import-progress__ring { border-color: var(--color-gain); animation: none; }
.import-progress[data-state="success"] .import-progress__check { visibility: visible; animation: import-progress-pop 450ms ease-out; }
.import-progress[data-state="success"] .import-progress__spark { visibility: visible; animation: import-progress-spark 850ms ease-out both; }
.import-progress[data-state="error"] .import-progress__mark,
.import-progress[data-state="error"] .import-progress__elapsed { display: none; }
/* One full turn belongs to no component: the import dialog's ring and the row
   action's mark are the same movement at different speeds, so they share the
   frames and keep their own durations. */
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes import-progress-pop { from { transform: scale(.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes import-progress-spark { from { transform: scale(.4); opacity: 0; } 25% { opacity: 1; } to { transform: translateY(calc(-1 * var(--space-4))) scale(1.4); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .import-progress__ring,
  .import-progress[data-state="success"] .import-progress__check,
  .import-progress[data-state="success"] .import-progress__spark { animation: none; }
  .import-progress[data-state="success"] .import-progress__spark { visibility: hidden; }
}

/* ---- coverage strip ----------------------------------------
   What the ledger holds, year by year, and what it does not. It is the one
   component the import flow turns on: the answer on Imports (which years are
   empty), the report while files are staged (what arrived), and the closing
   statement after an import (what is still missing). Learned once, read three
   times.

   A year with nothing in it is NOT amber. Nobody has to decide anything about
   a year the account did not trade in, so it is drawn as an outline. Amber is
   reserved for the year that cannot import until a file arrives, which is the
   one thing here a person must act on. */
.coverage-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.coverage-year {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-width: var(--coverage-chip);
  padding: var(--space-3);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-subtle);
}
.coverage-year__year {
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.coverage-year__value {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
/* Nothing here yet: an outline, and the year itself goes quiet with it. */
.coverage-year--empty {
  background: transparent;
  border-style: dashed;
}
.coverage-year--empty .coverage-year__year { color: var(--color-text-muted); }
/* Waiting on a file. The one amber on the page it appears on. */
.coverage-year--open {
  background: var(--wash-warn);
  border-color: var(--line-warn);
}
.coverage-year--open .coverage-year__year,
.coverage-year--open .coverage-year__value { color: var(--color-warn); }

/* ---- import set --------------------------------------------
   One source and one destination portfolio, with its own coverage. A drop
   holding two brokers becomes two of these, so the page is a list of sets and
   a single-broker drop is the same page with one entry. The source is stated
   inside the block it describes rather than in a header above the page, which
   is what lets the second one cost a block instead of a screen. */
.import-set {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}
.import-set + .import-set {
  padding-top: var(--space-5);
  border-top: var(--border-w) solid var(--color-border);
}
.import-set__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.import-set__actions {
  display: flex;
  flex: 0 1 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-inline-start: auto;
}
/* A source's mark is its own, not an instrument's. instrument-identity__logo
   would have fitted the shape and then tried to fetch a Logo.dev image for a
   broker's ISIN, which there is not one of. */
.import-set__identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.import-set__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--identity-mark);
  height: var(--identity-mark);
  border-radius: var(--radius-sm);
  background: var(--color-surface-selected);
  color: var(--color-text);
  font-size: var(--text-micro);
  font-weight: 600;
}
.import-set__copy { display: flex; flex-direction: column; min-width: 0; }
.import-set__name { font-size: var(--text-sm); font-weight: 600; }
.import-set__meta { font-size: var(--text-xs); color: var(--color-text-muted); }

/* ---- file drop ----------------------------------------------
   One place for every file, of every year and every broker. The dashed edge
   is the only dashed border the language draws on a full-width block, and it
   is carrying the meaning: this is a place to put something, not a pane that
   holds something. */
.file-drop {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  border: var(--border-w) dashed var(--color-border-strong);
  border-radius: var(--radius-pane);
  background: var(--color-surface-subtle);
}
.file-drop__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--space-8);
  height: var(--space-8);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
}
.file-drop__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  margin-inline-end: auto;
}
.file-drop__title { font-size: var(--text-base); font-weight: 600; }
.file-drop__hint { font-size: var(--text-xs); color: var(--color-text-muted); }

/* ---- forms ------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.field__label { font-size: var(--text-sm); font-weight: 600; }
.field__required { color: var(--color-loss); }
.field__input {
  min-width: 0; max-width: 100%;
  font: inherit; color: var(--color-text);
  background: var(--color-surface);  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  height: var(--control-height);
  max-width: min(100%, var(--content-narrow));
}
.field__input[type="number"],
.field__input[type="date"] { max-width: min(100%, var(--workbench-rail)); }
.field__input[type="number"] { text-align: end; font-variant-numeric: tabular-nums; }
.field__input:focus { border-color: var(--color-focus); }
.field__input--textarea { height: auto; resize: vertical; }
.field__help  { font-size: var(--text-xs); color: var(--color-text-muted); }
.field__error { font-size: var(--text-xs); color: var(--color-loss); }
.field--invalid .field__input { border-color: var(--color-loss); }
.form-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  min-width: 0;
  max-width: var(--content-narrow);
  margin: 0;
  padding: 0;
  border: 0;
}
/* A section heading, and not another label. It sat at the label's own size
   and weight, which left a form of four groups reading as one long list; and
   it sat two pixels to the left of the first label under it, because a
   <legend> carries the browser's own inline padding. Every group in the
   product was off by that same two pixels, which is why none of them looked
   deliberate. The rule and the space above it are what make the section; the
   type is quieter than the labels rather than louder, which is the treatment
   the product already uses for a column head and for the search's own group
   headings. */
.form-fields > legend {
  padding-inline: 0;
  margin-block: var(--space-5) var(--space-3);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-w) solid var(--color-border);
  inline-size: 100%;
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
/* The first group opens the form, so it has nothing to be separated from. */
.stack > .form-fields:first-child > legend,
.pane__body > .form-fields:first-child > legend {
  margin-block-start: 0;
  padding-block-start: 0;
  border-block-start: 0;
}

/* Four columns, for a row of figures. A box is sized by the track rather than
   by its own content, so it stays the same box whatever the entry type asks
   for, and the row still measures the form: a type carrying two figures
   leaves the other two columns empty instead of stretching to fill them. */
.form-fields--figures {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2) var(--space-4);
  /* Aligned from the top, never the bottom: bottom alignment makes each
     field's position depend on what sits under its neighbours, so one help
     line lifts one box out of the row. */
  align-items: start;
}
.form-fields--figures > legend { grid-column: 1 / -1; }
.form-fields--figures .field__label { white-space: nowrap; }
.form-fields--figures .field__input,
.form-fields--figures .field__input[type="number"],
.form-fields--figures .field__input[type="date"] { inline-size: 100%; max-inline-size: 100%; }

/* One field taking the slack beside one sized to its answer. */
.form-fields--split { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: flex-start; }
.form-fields--split > legend { flex: 1 0 100%; }
.form-fields--split > .field:first-of-type,
.form-fields--split > *:not(legend):first-of-type { flex: 1 1 18rem; min-inline-size: 0; }
.form-fields--split > *:not(legend):last-of-type { flex: 0 1 auto; min-inline-size: 0; }

/* A help line whose only content is a span this entry type has switched off
   still occupies a line. The text-carrying case has no element child, so it
   is untouched. */
.field__help:empty,
.field__help:has(> [data-tx-field][hidden]:only-child) { display: none; }

/* A character budget, counted down. */
.field__count {
  display: block;
  text-align: end;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.form-fields .field__input { width: 100%; max-width: 100%; }
.form-fields + .toolbar__group { margin-top: var(--space-4); }
.alert + .toolbar__group { margin-top: var(--space-3); }
.check-field { display: flex; flex-direction: column; gap: var(--space-1); }

/* ---- chip-choice: one answer from a named few ---------------- */
/* Twelve entry types behind a menu is twelve things a reader cannot see, and
   the shape of the form under them changes with the answer. Drawn open, in
   named groups, the groups say why: the artboard's three are the three the
   server already enforces -- with an instrument and money, a corporate action
   with neither, and cash with no instrument -- so the control teaches the rule
   instead of leaving it to be met as a validation error.

   Radios and not buttons. A segmented control is buttons, and a button inside
   a form either submits it or does nothing without script; this has to carry
   an answer through a send that may happen with no script at all. The chip is
   the label, the input is under it, and :checked is what draws the pressed
   state -- so the keyboard gets a radio group for free. */
.chip-choice { display: flex; flex-direction: column; gap: var(--space-1); border: 0; padding: 0; margin: 0; }
.chip-choice__group { margin-block-start: var(--space-2); }
.chip-choice__group:first-of-type { margin-block-start: 0; }
.chip-choice__row { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.chip-choice__chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--segment-height);
  padding-inline: var(--control-pad-sm);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-pill);
  color: var(--color-text-muted);
  background: var(--color-surface-subtle);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
}
.chip-choice__chip:hover { color: var(--color-text); }
/* Off-screen rather than display:none: a hidden input is not focusable, and
   the arrow keys moving through the group are the whole reason this is a
   radio group. */
.chip-choice__input { position: absolute; opacity: 0; pointer-events: none; }
.chip-choice__chip:has(.chip-choice__input:checked) {
  color: var(--color-text);
  border-color: var(--color-border-strong);
  background: var(--color-surface-selected);
  font-weight: 600;
}
.chip-choice__chip:has(.chip-choice__input:focus-visible) {
  outline: var(--outline-w) solid var(--color-focus);
  outline-offset: var(--border-w);
}
.table-group > th, .table-group > td {
  background: var(--color-surface-subtle);
  border-block-start: var(--border-w) solid var(--color-border-strong);
  font-weight: 600;
}
.check-field__label {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--control-height);
  font-size: var(--text-sm); font-weight: 600;
}
.check-field__input {
  width: var(--space-4);
  height: var(--space-4);
  accent-color: var(--color-accent);
}
.check-field__input:focus-visible { outline: var(--outline-w) solid var(--color-focus); }
.check-field__help {
font-size: var(--text-xs); color: var(--color-text-muted);
}

/* ---- hint: a definition behind a mark ----------------------- */
/* Sized to the text beside it rather than to a control. It sits in a table
   cell next to a component's name, and something button-sized would make every
   row taller to hold it. Quiet until asked for: a mark that competed with the
   figure on the row would be the column it replaced, in less space. */
.hint {
  appearance: none;
  border: var(--border-w) solid var(--color-border);
  background: transparent;
  color: var(--color-text-muted);
  border-radius: 50%;
  inline-size: var(--hint-size);
  block-size: var(--hint-size);
  line-height: 1;
  font-size: var(--text-micro);
  cursor: pointer;
  margin-inline-start: var(--space-1);
  vertical-align: middle;
}
.hint:hover { color: var(--color-text); border-color: var(--color-border-strong); }
/* The panel is prose, so it is capped at a readable measure rather than
   stretched to whatever the row is wide. Positioned by the browser: the
   popover attribute puts it in the top layer, which is what stops a table's
   overflow clipping it. */
/* The panel floats over content it must not be read through, and it opens
   beside the button that asked for it.

   Both were broken and for different reasons. `--color-surface-raised` is
   declared nowhere in the product -- this was its only use -- so the
   background was invalid at computed-value time and resolved to transparent,
   which is why the table showed through it. And nothing positioned it: a
   popover with no anchor positioning is centred in the viewport by the
   browser, so pressing a "?" at the bottom of a table opened a panel at the
   top of the page, nowhere near the question. */
.hint__panel {
  max-inline-size: var(--hint-measure);
  padding: var(--space-3);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  /* The invoker is the implicit anchor: `popovertarget` establishes it, so
     nothing needs naming on the button. Below and inline-start, flipping
     above when there is no room -- a "?" on the last row of a table is the
     usual case.

     A browser without anchor positioning does NOT get the centred panel.
     This said it did, and that was wrong in a way the next two lines of this
     same rule explain: `inset: auto` is what releases the UA sheet's
     centring, and it is not gated on anchor positioning. So such a browser
     applies the release, ignores `position-area`, and is left with a panel
     the UA no longer centres and nothing else places -- which is the defect
     this rule exists to fix, permanently, rather than the state before it.

     Not fixed here, deliberately: nobody has been able to run such a browser
     to see where the panel actually lands, and shipping an untested
     `@supports` fallback on top of a measured fix trades one unverified path
     for another. issue 904 carries the one screenshot that would settle it. What
     is corrected is the claim, because a comment promising a case is handled
     is worse than no comment -- it stops the next reader looking. */
  /* The UA sheet gives every popover `inset: 0; margin: auto`, which centres
     it in the viewport and wins over the area below -- the property applies,
     and then the centring puts it somewhere else anyway. Releasing the inset
     is what lets the area place it. Found by measuring the open panel rather
     than reading the rule: position-area computed correctly and the panel was
     still most of a screen away from its button. */
  inset: auto;
  position-area: block-end span-inline-start;
  position-try-fallbacks: flip-block, flip-inline;
  margin: var(--space-2) 0 0 0;
}

/* ---- popover trigger and panel ------------------------------
   Shares `hint__panel`'s anchoring: the invoker is the implicit anchor
   through `popovertarget`, the UA sheet's centring inset is released so
   `position-area` can place it, and the fallbacks flip it when a row near
   the foot of a table has no room below.

   It shares the unhandled case too -- see the note on `.hint__panel`. A
   browser without anchor positioning releases the centring and places
   nothing, and that is unmeasured rather than handled (issue 904). */
.popover-trigger {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  border: var(--border-w) solid var(--color-border);
  background: var(--color-surface-subtle);
  color: var(--color-text-secondary);
  font: inherit; font-weight: 600;
  cursor: pointer;
}
.popover-trigger:hover, .popover-trigger:focus-visible {
  color: var(--color-text); border-color: var(--color-border-strong);
}
.popover-panel {
  max-inline-size: var(--hint-measure);
  padding: var(--space-3);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  inset: auto;
  position-area: block-end span-inline-start;
  position-try-fallbacks: flip-block, flip-inline;
  margin: var(--space-2) 0 0 0;
}
/* A panel of chips is a wrapping row; a menu is a column of full-width rows
   and takes no padding of its own, because each row carries it instead.
   Closed is restated for the reason `.row-menu__sheet` records: a bare
   `display` beats the UA sheet's hiding rule and draws every panel open. */
.popover-panel { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.popover-panel:not(:popover-open) { display: none; }

/* ---- badge ------------------------------------------------- */
/* A washed pill, as every board draws it. The outlined rectangle the kit
   started from put a hard tinted line around a word that is usually not the
   most important thing on the row; a wash says the same state and recedes. */
.badge {
  display: inline-flex;
  align-items: center;
  /* A badge carrying `units` has two flex children, and a flex container drops
     the whitespace-only text node between them -- so the label and the figure
     ran together as "Ex-dividend FriCHF 200". The macro has taken `units`
     since it was written and issue 823 is its first caller, so this markup
     had never been drawn: the check that would have caught it is a render,
     and there was nothing to render. */
  gap: var(--space-1);
  min-height: var(--badge-height);
  padding: 0 var(--badge-pad);
  border-radius: var(--radius-pill);
  border: var(--border-w) solid var(--color-border);
  background: var(--color-surface-subtle);
  color: var(--color-text-secondary);
  font-size: var(--text-micro); font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* The badge as a door. The build chip on Settings states the commit that is
   running and opens what that commit contains; the global rule would draw a
   rule through a pill, which reads as a mistake rather than as a link. So the
   affordance is the border and the text coming forward under the pointer,
   which is the answer every other bordered control here gives. */
/* A button wearing the badge opens what the pill stands for. */
button.badge { font-family: inherit; cursor: pointer; }
a.badge, button.badge {
  text-decoration: none;
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease;
}
a.badge:hover, a.badge:focus-visible, button.badge:hover, button.badge:focus-visible {
  border-color: var(--color-border-strong);
  color: var(--color-text);
}
@media (prefers-reduced-motion: reduce) { a.badge, button.badge { transition: none; } }
.badge--wrap { min-width: 16ch; max-width: 24ch; white-space: normal; overflow-wrap: anywhere; }
.badge--gain { background: var(--wash-gain); border-color: var(--line-gain); color: var(--color-gain); }
.badge--loss { background: var(--wash-loss); border-color: var(--line-loss); color: var(--color-loss); }
.badge--warn { background: var(--wash-warn); border-color: var(--line-warn); color: var(--color-warn); }
/* Its own hue rather than one of the three above: those three read as a
   verdict on the figures, and this one is a statement about who wrote the
   words. The reason the hue is the one it is sits with the tokens. */
.badge--ai   { background: var(--wash-ai);   border-color: var(--line-ai);   color: var(--color-ai); }
/* The mark inside it, at the pill's own size rather than the glyph's own.
   `flex: 0 0 auto` so the star keeps its shape when a long label wraps in
   the `wrap` variant; `currentColor` is inherited, so the mark is the same
   ink as the words and needs no second token. */
.badge--ai .glyph {
  width: var(--badge-glyph);
  height: var(--badge-glyph);
  flex: 0 0 auto;
}

/* ---- tag cell ---------------------------------------------- */
/* A holding's chips, then the field that adds one. Own tags are neutral and
   carry an x; the two the product writes take a hue from the series palette
   (never gain, loss or warn, which are verdicts, and never the AI pink) and a
   mark, so the kind is never told by colour alone. */
.tag-cell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  /* Wraps its chips rather than widening its column: a table that gains a
     Tags column must not take the width its instrument names read in. */
  max-width: 22ch;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--badge-height);
  margin: 0;
  padding: 0 var(--badge-pad);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}
form.tag-chip { padding-inline-end: var(--space-1); }
.tag-chip--watch {
  color: var(--chart-series-1);
  background: color-mix(in srgb, var(--chart-series-1) 13%, transparent);
  border-color: color-mix(in srgb, var(--chart-series-1) 40%, transparent);
}
.tag-chip--recipe {
  color: var(--chart-series-6);
  background: color-mix(in srgb, var(--chart-series-6) 13%, transparent);
  border-color: color-mix(in srgb, var(--chart-series-6) 40%, transparent);
}
.tag-chip .glyph {
  width: var(--badge-glyph);
  height: var(--badge-glyph);
  flex: 0 0 auto;
}
.tag-chip__remove {
  display: inline-grid;
  place-items: center;
  width: calc(var(--badge-height) - var(--space-1));
  height: calc(var(--badge-height) - var(--space-1));
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.tag-chip__remove .glyph { width: calc(var(--badge-glyph) * 0.85); height: calc(var(--badge-glyph) * 0.85); }
.tag-chip__remove:hover,
.tag-chip__remove:focus-visible { background: var(--color-border-strong); color: var(--color-text); }
.tag-cell__add { display: inline-flex; margin: 0; }
.tag-cell__input {
  width: 8ch;
  height: var(--badge-height);
  padding: 0 var(--badge-pad);
  border: var(--border-w) dashed var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-xs);
}
.tag-cell__input::placeholder { color: var(--color-text-muted); }
.tag-cell__input:focus {
  width: 17ch;
  border-style: solid;
  border-color: var(--color-focus);
  outline: none;
  background: var(--color-bg);
}
.tag-cell__none { color: var(--color-text-muted); }
/* Beside a name the cell is one line, so it takes the width the line has. */
.tag-cell--inline { max-width: none; }
/* The chip that opens the folded recipes is a button wearing the chip. */
button.tag-chip { font-family: inherit; cursor: pointer; }
/* Placed by tag-cell.js against the field, fixed rather than absolute, so a
   table's scroll box cannot clip it. */
.tag-cell__list {
  position: fixed;
  z-index: 30;
  min-width: 26ch;
  max-height: 16rem;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
}
.tag-cell__option {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  white-space: nowrap;
  cursor: pointer;
}
.tag-cell__option[aria-selected="true"] { background: var(--color-surface-selected); }
.tag-cell__option mark { background: none; color: inherit; font-weight: 700; }
.tag-cell__create { color: var(--color-accent); }
.tag-cell__option + .tag-cell__create { border-top: var(--border-w) solid var(--color-border); margin-top: var(--space-1); }

/* ---- alert ------------------------------------------------- */
.alert {
  border: var(--border-w) solid var(--color-border-strong);
  border-left-width: var(--accent-bar-w);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  background: var(--color-surface);
}
.alert--warn { border-left-color: var(--color-warn); }
.alert--error { border-left-color: var(--color-loss); }
.alert--ok { border-left-color: var(--color-gain); }

/* The stale-data marker stood here: a tinted chip with a warning triangle,
   drawn per figure. Retired on 16 September 2026 -- how old the stored close
   is is one fact about the page, and each page now states it once in its own
   notice, where a screen reader gets it as prose rather than as a suffix on
   every number. FR-22 is restated to match. Issue 212. */

/* ---- styleguide swatches (used only on /styleguide) --------- */
.swatch {
  display: inline-flex; align-items: end;
  width: 7.5rem; height: 3rem;
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  padding: var(--space-1);
}
.swatch--bg             { background: var(--color-bg); }
.swatch--surface        { background: var(--color-surface); }
.swatch--surface-subtle { background: var(--color-surface-subtle); }
.swatch--surface-selected { background: var(--color-surface-selected); }
.swatch--border         { background: var(--color-border); }
.swatch--accent         { background: var(--color-accent); color: var(--color-accent-contrast); }
.swatch--gain           { background: var(--color-gain); color: var(--color-accent-contrast); }
.swatch--loss           { background: var(--color-loss); color: var(--color-accent-contrast); }
.swatch--warn           { background: var(--color-warn); color: var(--color-accent-contrast); }

/* ---- chart container --------------------------------------- */
.chart {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  min-height: var(--chart-height);
}
.chart__status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-align: center;
}
.chart__status > .btn[hidden] { display: none; }
.chart__status-indicator {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: var(--color-accent);
  animation: chart-status-pulse 1.4s ease-out infinite;
}
.chart__unit {
  position: absolute;
  z-index: 2;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  color: var(--color-text-muted);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
@keyframes chart-status-pulse {
  50% { opacity: 0.35; transform: scale(0.72); }
}
@media (prefers-reduced-motion: reduce) {
  .chart__status-indicator { animation: none; }
}
.chart--compact { min-height: var(--chart-height-compact); }
.chart--tall { min-height: var(--chart-height-tall); }
.chart--activity {
  min-height: var(--chart-height-activity);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: stretch;
}
.chart-shell {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.chart-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
/* The far end of the range row. `justify-content: space-between` already
   separates two children; this pins the second there when the row wraps. */
.chart-toolbar__end { margin-inline-start: auto; }
.chart--donut {
  position: relative;
  display: grid;
  place-items: center;
}
.allocation-donut {
  width: 100%;
  height: var(--chart-height);
  overflow: visible;
}
/* The ring follows the height its chart was given, the way a plot does. A
   ring with a list under it is a tall column, and beside a shorter one it
   would otherwise leave the pane bottomed on whichever side ran longer. */
.chart--donut.chart--compact .allocation-donut { height: var(--chart-height-compact); }
.chart--donut.chart--tall .allocation-donut { height: var(--chart-height-tall); }
.allocation-donut__track {
  fill: none;
  stroke: var(--color-border);
}
.allocation-donut__segment {
  fill: none;
  cursor: pointer;
  transition: opacity var(--ease);
}
/* The outer ring: one arc per holding in its slice's colour. Neighbours
   alternate in strength so two holdings read as two, and the roll-up of the
   slivers is the faintest because it is the one arc that is not a holding. */
.allocation-donut__part--alternate { opacity: 0.55; }
.allocation-donut__part--rest { opacity: 0.3; }
/* One slice brought forward from its legend row or its own arc: the rest of
   the ring and the rest of the legend step back. */
.allocation-donut--focusing .allocation-donut__segment:not(.is-highlighted) { opacity: 0.2; }
.chart-keys:has(.is-highlighted) .chart-keys__row:not(.is-highlighted) { opacity: 0.45; }
/* The expanded ring: the pane's full width, and a label beside every arc it
   has room for instead of a legend. The labels take the theme's text colour;
   a slice's name sits on its own arc, outlined in the page colour so it
   reads on every swatch in either theme. */
.chart--donut .allocation-donut.allocation-donut--labelled {
  height: auto;
  aspect-ratio: 1160 / 500;
}
.allocation-donut__slice-label,
.allocation-donut__callout,
.allocation-donut__callout-meta {
  font-family: var(--font-ui);
  pointer-events: none;
}
.allocation-donut__slice-label {
  fill: var(--color-text);
  font-size: var(--text-base);
  font-weight: 700;
  text-anchor: middle;
  paint-order: stroke;
  stroke: var(--color-bg);
  stroke-width: 3;
}
.allocation-donut__callout {
  fill: var(--color-text);
  font-size: var(--text-base);
}
.allocation-donut__callout-meta {
  fill: var(--color-text-muted);
  font-size: var(--text-sm);
}
.allocation-donut__callout-line {
  fill: none;
  stroke: var(--color-border-strong);
  stroke-width: 1;
}
.allocation-donut__segment:hover {
  opacity: 0.72;
  outline: none;
}
.allocation-donut__segment:focus-visible {
  opacity: 0.72;
  outline: none;
  stroke-width: var(--space-6);
}
.allocation-donut__label,
.allocation-donut__value {
  fill: var(--color-text);
  text-anchor: middle;
  pointer-events: none;
}
/* Sized in the ring's own units, not in screen pixels. The donut is drawn on
   a 240-unit square and fitted to the chart's height, so a compact ring
   renders at four fifths scale and everything here is multiplied by that: the
   value used to be set at the secondary-UI step and reached the reader at
   about ten, one step under its own caption and under every row of the legend
   beneath it. The centre of a ring is the figure the ring exists to state --
   how much is classified, how much sits in the reference currency -- so it is
   set at the figure step the summary tiles use, and lands near the pane title
   once the ring has scaled it. The hole is 160 units across and the longest
   centre value here is five characters, so it has room to spare. */
.allocation-donut__label {
  fill: var(--color-text-muted);
  font-family: var(--font-ui);
  font-size: var(--text-base);
}
.allocation-donut__value {
  font-family: var(--font-data);
  font-size: var(--text-figure);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* A centre that states money rather than a share. The ring's hole is a fixed
   160 units and the figure step fits about ten characters in it, which a
   percentage or a count never reaches and an amount passes as soon as it
   carries a currency and a thousands mark: the week's contribution ring
   overflowed its hole by three at the figure step, measured. The page-title
   step fits thirteen, which covers an amount this product can produce.
   chart-factory picks between the two on the value's own length, so nothing
   here is tied to which page drew the ring. */
.allocation-donut__value--long { font-size: var(--text-xl); }
.chart-tooltip {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: calc(100% - var(--space-4));
  padding: var(--space-2) var(--space-3);
  border: var(--border-w) solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-xs);
  pointer-events: none;
  z-index: 4;
}
.chart-tooltip[hidden] { display: none; }
[data-tx-field][hidden] { display: none; }
.chart-tooltip__label { font-weight: 700; }
.chart-tooltip__value,
.chart-tooltip__percentage {
  color: var(--color-text-secondary);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}
/* Drawn beside the pane title, not above the plot, so it is sized as a key
   and not as a second line of prose: the artboard's micro size and gap. */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-tile-block);
  list-style: none;
  margin: 0;
  padding: 0;
  color: var(--color-text-muted);
  font-size: var(--text-micro);
}
.chart-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.chart-legend__key {
  display: inline-block;
  width: var(--chart-key-w);
  border-top: var(--outline-w) solid var(--chart-line);
}
/* The swatch is part of the line, not a caption about it, so it takes the same
   direction (issue 601). Set by chart-factory.js when it paints the series: a green
   key beside a red line is issue 597 in a smaller place, and the key is the copy
   nobody thinks to check because it never held a number. No modifier is the
   flat state, which inherits --chart-line above and needs no token. */
.chart-legend__key--gain { border-top-color: var(--chart-up); }
.chart-legend__key--loss { border-top-color: var(--chart-down); }
.chart-legend__key--benchmark {
  border-top-color: var(--chart-benchmark);
  border-top-style: dashed;
}
.chart-legend__key--partial { border-top-color: var(--color-warn); }
[data-chart-legend-partial][hidden] { display: none; }
/* Why the named second series is not on the axis. Quieter than the name it
   qualifies, and empty when the line is drawn — never a separate caption,
   so the name cannot be read without it. */
.chart-legend__note { color: var(--color-text-muted); }
/* The list beside a ring: what each slice is, and how much. A donut states
   its slices in tooltips, which is a fact only a pointer can reach — fine for
   an allocation a reader is browsing, not for a figure the page is reporting.
   So the amounts are a list, and the swatch is what ties a row to its slice.

   A row, not a table: a handful of these sit in a rail beside the ring, and a
   table would put its own rules through them. The value carries the sign
   colour and the redaction marker like any other amount. */
.chart-keys {
  display: flex;
  flex-direction: column;
  list-style: none;
  margin: 0;
  padding: 0;
}
.chart-keys__row {
  display: flex;
  /* Centred, not on the baseline: the swatch is a shape rather than a glyph,
     so a shared baseline sits it low and asks for a nudge in raw pixels. */
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
  border-block-start: var(--border-w) solid var(--color-border);
  font-size: var(--text-sm);
}
.chart-keys__row:first-child { border-block-start: 0; }
.chart-keys__swatch {
  flex: 0 0 auto;
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
}
/* An entry stated beside the ring without a slice in it keeps the column so
   its name lines up with the named ones, and shows nothing. */
.chart-keys__swatch--none { background: none; }
.chart-keys__swatch--1 { background: var(--chart-allocation-1); }
.chart-keys__swatch--2 { background: var(--chart-allocation-2); }
.chart-keys__swatch--3 { background: var(--chart-allocation-3); }
.chart-keys__swatch--4 { background: var(--chart-allocation-4); }
.chart-keys__swatch--5 { background: var(--chart-allocation-5); }
.chart-keys__swatch--6 { background: var(--chart-allocation-6); }
.chart-keys__swatch--tag-1 { background: var(--chart-tag-1); }
.chart-keys__swatch--tag-2 { background: var(--chart-tag-2); }
.chart-keys__swatch--tag-3 { background: var(--chart-tag-3); }
.chart-keys__swatch--tag-4 { background: var(--chart-tag-4); }
.chart-keys__swatch--tag-5 { background: var(--chart-tag-5); }

/* A cut of one whole with more values than a ring has readable slices. The
   ring beside it stops at six and rolls the rest up; this states every one.

   Why a bar as well as a figure: the figure is the answer and the bar is the
   ranking, and a list of eleven percentages read top to bottom is a list
   nobody ranks in their head. The bar is drawn from the same share the
   figure prints, never from a second measurement.

   Columns rather than a grid, because the order is the ranking: multi-column
   flows the list down the first column and then down the second, so the
   largest share is at the top of the page and the smallest at the bottom of
   it. A two-column grid fills left to right and puts the second-largest
   beside the largest, which reads as two lists rather than one. */
.share-list {
  columns: 2;
  column-gap: var(--space-6);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* A row is one thing: its name, its figure and its bar never split across
   the column break. */
.share-row {
  break-inside: avoid;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block: var(--space-2);
}
.share-row__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.share-row__name {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  min-width: 0;
  overflow-wrap: break-word;
}
.share-row__meta { color: var(--color-text-muted); }
.share-row__value {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.share-row__bar {
  display: block;
  width: 100%;
  height: var(--space-2);
  border-radius: var(--radius-chart);
  overflow: hidden;
  background: var(--color-surface-subtle);
}
.share-row__fill { fill: var(--color-border-strong); }
/* What the provider did not classify is a group like any other and keeps its
   row; it is drawn quieter so the eye ranks what is known first. */
.share-row--quiet .share-row__fill { fill: var(--color-text-muted); }

@media (max-width: 48rem) {
  /* The phone Overview leads with one figure and then the chart, as the
     phone artboard draws it: `body-mobile.html` puts a single kpi card
     above the value chart and everything else below it.

     The page shipped all six summary items first, so the chart began
     two thirds of the way down a phone screen -- the thing a reader opens
     the Overview for was below the fold, under four figures they had not
     asked for.
     The lead item keeps both of its readings (the value/return swap is
     one tile showing one of two faces), and the four that follow it are
     desktop's use of a wider strip. They are not lost: Today and the
     benchmark comparison are the lead item's own foot line. */
  .summary-item:not(.summary-item--primary) { display: none; }
  /* The lead is the one tile a phone draws, and on Return the lead is Today --
     the same decision the row order carries, arriving where a phone can see
     it. A row reordered on the desktop while the phone kept drawing the old
     lead would be one decision rendered two ways (issue 789). */
  body[data-headline="return"] .summary-item--primary.as-return { display: none; }
  body[data-headline="return"] .summary-item--leads-on-return { display: grid; }
  .summary-group--pair { display: none; }

  /* One column on a phone: half a phone is not a width for a
     forty-character industry name. */
  .share-list { columns: 1; }
}
.chart-keys__name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--color-text-secondary);
  overflow-wrap: break-word;
}
.chart-keys__value {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.activity-chart__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}
.activity-chart__header { justify-content: space-between; }
.activity-chart__title {
  font-size: var(--text-sm);
  font-weight: 700;
}
.activity-chart__marker {
  display: inline-grid;
  min-width: var(--space-4);
  place-items: center;
  font-family: var(--font-data);
  font-size: var(--text-base);
  line-height: 1;
}
.activity-chart__marker--buy { color: var(--color-gain); }
.activity-chart__marker--sell { color: var(--color-loss); }
.activity-chart__marker--dividend {
  min-width: var(--space-4);
  min-height: var(--space-4);
  border: var(--border-w) solid var(--color-warn);
  color: var(--color-warn);
  font-size: var(--text-xs);
}
.activity-chart__range {
  justify-content: flex-start;
  margin-inline: 0;
}
.activity-chart__main-chart {
  position: relative;
  width: 100%;
  height: var(--chart-height-activity);
}
.activity-chart__plot-markers {
  position: absolute;
  z-index: 3;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.activity-chart__plot-marker {
  position: absolute;
  min-width: var(--space-5);
  min-height: var(--space-5);
  padding: 0;
  border: 0;
  color: var(--color-text);
  background: transparent;
  font-family: var(--font-data);
  font-size: var(--text-base);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
}
.activity-chart__plot-marker--buy {
  color: var(--color-gain);
  transform: translate(-50%, var(--space-2));
}
.activity-chart__plot-marker--sell {
  color: var(--color-loss);
  transform: translate(-50%, calc(-100% - var(--space-2)));
}
.activity-chart__plot-marker--dividend {
  min-width: var(--space-4);
  min-height: var(--space-4);
  border: var(--border-w) solid var(--color-warn);
  color: var(--color-warn);
  font-size: var(--text-xs);
  transform: translate(-50%, var(--space-2));
}
/* The bridge that carries every chart colour from CSS into JavaScript, and the
   reason this rule cannot be deleted as unused. `chart-factory.js` creates a
   span with this class, reads its *computed style*, and maps each property to
   a chart colour -- so the class name appears in the stylesheet and in the
   script and nowhere else, and nothing renders it:

     color                 -> text        border-left-color     -> crosshair
     background-color      -> grid        outline-color         -> up
     border-top-color      -> line        text-decoration-color -> down
     border-right-color    -> areaTop     column-rule-color     -> benchmark
     border-bottom-color   -> areaBottom  caret-color           -> warn

   Deleted on 17 September 2026 while removing the activity strip, which put a
   white area fill on every chart in production: with no rule the span is
   unstyled and all ten resolve to browser defaults. It is invisible by design,
   so no golden master, snapshot or lint could see it go. `ui-chart-runtime.py`
   now asserts the probe resolves, which is the check that was missing.

   The --allocation-N variants below inherit the hiding from here. */
.chart-token-probe {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  color: var(--chart-text);
  background-color: var(--chart-grid);
  border-top-color: var(--chart-line);
  border-right-color: var(--chart-area-top);
  border-bottom-color: var(--chart-area-bottom);
  border-left-color: var(--chart-crosshair);
  outline-color: var(--chart-up);
  text-decoration-color: var(--chart-down);
  column-rule-color: var(--chart-benchmark);
  caret-color: var(--color-warn);
  font-family: var(--font-ui);
}
/* The signed fills, one probe each, for the same reason the allocation palette
   gets its own elements: the probe above has run out of colour properties to
   carry a token on. Read by chart-factory.js and rendered by nothing, so a
   selector deleted as unused takes the fill with it silently -- which is what
   `css-classes-javascript-reads.py` exists to catch. */
.chart-token-probe--area-gain {
  border-top-color: var(--chart-area-top-gain);
  border-bottom-color: var(--chart-area-bottom-gain);
}
.chart-token-probe--area-loss {
  border-top-color: var(--chart-area-top-loss);
  border-bottom-color: var(--chart-area-bottom-loss);
}
.chart-token-probe--allocation-1 { color: var(--chart-allocation-1); }
.chart-token-probe--allocation-2 { color: var(--chart-allocation-2); }
.chart-token-probe--allocation-3 { color: var(--chart-allocation-3); }
.chart-token-probe--allocation-4 { color: var(--chart-allocation-4); }
.chart-token-probe--allocation-5 { color: var(--chart-allocation-5); }
.chart-token-probe--allocation-6 { color: var(--chart-allocation-6); }
.chart-token-probe--tag-1 { color: var(--chart-tag-1); }
.chart-token-probe--tag-2 { color: var(--chart-tag-2); }
.chart-token-probe--tag-3 { color: var(--chart-tag-3); }
.chart-token-probe--tag-4 { color: var(--chart-tag-4); }
.chart-token-probe--tag-5 { color: var(--chart-tag-5); }

/* ---- utility-first financial workbenches ------------------ */
.workbench {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
  /* The board runs the whole surface at its own body size, leading 21; this
     stepped down one notch to 13 and 19.5, which is why the app read denser
     than the artboard everywhere at once rather than in any one component.
     --text-table still holds a table cell at 13, which is what the boards
     draw — only the surface around it moves. */
  font-size: var(--text-base);
}
/* A column stretches its children, which turned a pill group standing alone
   between two panes into a full-width bar with four pills at the left of it.
   The boards draw it at its own width, every time. */
.workbench > .segmented,
.workbench > .tabs { align-self: flex-start; }
.workbench-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.workbench-header h1 {
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
}
.workbench-header__meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
/* Separate tiles with a gutter between them, as the artboards draw them —
   not one bordered strip divided by rules. Sizes to the metrics a page
   actually has: the fixed six-column grid this replaces left dead columns on
   shorter strips, which invited padding them out with placeholders. The lead
   spans two of those columns, so it is wider than the rest by the artboards'
   own ratio rather than by a fraction invented here. */
.summary-strip {
  display: grid;
  grid-template-rows: auto auto auto;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-2) var(--gutter);
  overflow-x: auto;
}
/* A tile pays 18 down and 20 across, as the boards draw it: the figure
   wants room above and below more than beside. */
.summary-item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  align-content: start;
  min-width: 0;
  padding: var(--space-tile-block) var(--margin);
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-pane);
}
.summary-item--primary { grid-column: span 2; }
/* The summary on the page's own two columns: the three tiles above the value
   chart are exactly its width, the two above Portfolios exactly that pane's.
   Five tiles spread evenly across the full width put every boundary in a
   place nothing below shares, which is what reads as two rows that do not
   line up. The tracks and the gutter are the split's, so the seam is the
   same seam rather than a second one measured to match. */
.summary-strip--aligned {
  grid-template-columns: var(--workbench-split);
}
/* A group is a column of the strip, not a box: it passes the strip's three
   rows down so a label, a figure and a foot still line up across all five
   tiles, and it spaces the tiles inside it exactly as the strip spaces the
   groups. Any other inner gap would make the spacing uneven at one seam. */
.summary-group {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  grid-auto-flow: column;
  /* A group holding more tiles than its variant declares columns puts the
     extras in implicit tracks, and the default `auto` sizes those by their
     content — so a tile's width becomes a property of its own strings. The
     Brief's `Dated ahead` was a third the width of the close because
     `To Friday 11 Sep` is short (issue 962). `.summary-strip` has carried
     this rule since it was written; the group was never given it. */
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-2) var(--gutter);
  min-width: 0;
}
/* The lead takes what is left once every other tile is one width. On the
   split, two tiles over the rail and a lead and two over the main column are
   equal only when the lead is twice the split's excess, 2 x (1.62 - 1) = 1.24:
   the split's number rather than a new one, so the seams stay on the panes
   below and a page without a split draws the same tiles (issue 962). */
.summary-group--lead { grid-template-columns: minmax(0, 1.24fr) minmax(0, 1fr) minmax(0, 1fr); }
.summary-group--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* A tile that follows the chart's range, with its readings beside it. Not a
   box: the strip's tiles pass its three rows through themselves, so a wrapper
   with a layout of its own would take a tile's place and leave the label, the
   figure and the foot lining up with nothing. The templates inside it render
   nothing until a range is pressed (issue 788, issue 790). */
.range-swap { display: contents; }
/* Inside a group the lead is one column of three, sized by the group --
   including when a range swap wraps it, which is why this is not `>`. */
.summary-group .summary-item--primary { grid-column: auto; }
.summary-item__label {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
}
/* On the tiles that lead with a percentage the money sits in the foot, and
   the boards colour it there: `<span class="loss amount">−CHF 3'657</span>`
   under Today, `<span class="gain amount">+CHF 300'189</span> · excluding
   flows` under Year to date. The muted foot colour would otherwise win on
   source order and leave the only figure on the tile grey. */
/* The figure inside the foot, not the foot: the boards colour "+CHF 300'189"
   and leave "· excluding cash flows" in the muted tone it belongs to. */
.summary-item__meta > .gain { color: var(--color-gain); }
.summary-item__meta > .loss { color: var(--color-loss); }
.summary-item__value {
  align-self: baseline;
  white-space: normal;
  overflow-wrap: break-word;
  font-family: var(--font-data);
  font-size: var(--text-figure);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-figure);
}
/* Only the lead tile takes the headline size, as the artboards do: every
   destination leads with one metric and the rest of the strip reads a step
   down from it. Drawing all five at the headline size is what tipped a
   long figure onto a second line in a tile a fifth of the strip wide, and a
   second line of the figure is the single largest thing that has ever made
   this strip taller than the artboards draw it. */
/* The lead tile is bigger twice over on the boards: it spans two columns and
   its figure steps up. Tallied across Main, Ledger and SoldHoldings the strip
   is four figures at 27 and one at 31, never five alike. */
.summary-item--primary .summary-item__value { font-size: var(--text-figure-lead); }
.summary-item__meta { color: var(--color-text-muted); font-size: var(--text-xs); }
/* A total that cannot be complete. Muted and lighter than a figure, so the
   refusal does not read as a number: the mockups' kpi__value--void. It takes
   no red and no amber on purpose — red is reserved for something that broke
   and offers a retry, amber for an open item a person must decide, and a sum
   whose parts were never recorded is neither. */
.summary-item__value--void { color: var(--color-text-muted); font-weight: 500; }
/* The scale and the foot share the tile's third row, so a tile that carries
   one is still a tile in the strip's grid rather than one standing outside
   it. The inner gap is the strip's own row gap, so the four things down a
   band tile are spaced exactly as the three down every other one. */
.summary-item__band {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
/* ---- band meter -------------------------------------------
   A classification drawn as the whole scale with one place lit. Every pip is
   the same width, because the pips count bands and not the distance between
   their edges: a scale whose segments were sized by their thresholds would
   be a bar chart of the thresholds, and the reading is the position.

   The lit pips take the current band's colour and the rest take the tone a
   selected row takes, which is what the drawing puts behind them. The series
   tones carry Moderate and Elevated rather than the amber token: amber means
   an open item somebody has to decide, and a book being volatile is not an
   item. Only the top band spends the loss token. */
.band-meter { display: flex; gap: var(--space-1); }
.band-meter__pip {
  flex: 1;
  height: var(--progress-bar);
  border-radius: var(--radius-pill);
  background: var(--color-surface-selected);
}
/* Low and Steady share a green: the difference between them is not a
   difference in what anybody should do, so the count carries the granularity
   and the colour carries the severity. */
.band-meter--steady .band-meter__pip--lit { background: var(--chart-series-3); }
.band-meter--moderate .band-meter__pip--lit { background: var(--chart-series-4); }
.band-meter--elevated .band-meter__pip--lit { background: var(--chart-series-2); }
.band-meter--high .band-meter__pip--lit { background: var(--color-loss); }
/* One row of the published scale: the meter at that band's own position, the
   band's name, and the volatility it starts at. The meter is held to one
   width here rather than filling the row, because five rows only read as a
   scale if the five pictures are the same size. */
.band-row__name {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.band-row__name .band-meter { flex: 0 0 var(--band-scale); }
/* The row this workspace falls in — the one thing on the card that is about
   the reader, so it takes the page's own text colour where the rest is
   muted. The badge beside it says which reader. */
.band-row--current .band-row__name { color: var(--color-text); }
.portfolio-headline {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-2) var(--space-3);
}
.portfolio-headline__value-group,
.portfolio-headline__change-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.portfolio-headline__change-group { align-items: flex-end; text-align: right; }
.portfolio-headline__label {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
}
.portfolio-headline__value,
.portfolio-headline__change {
  overflow-wrap: anywhere;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}
.portfolio-headline__value { font-size: var(--text-2xl); line-height: var(--leading-tight); }
.portfolio-headline__change { font-size: var(--text-base); }
.portfolio-headline__meta { color: var(--color-text-muted); font-size: var(--text-xs); }
.dividend-overview {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
  padding: var(--space-3) var(--space-5) var(--space-5);
}
.alternative-intelligence {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
  padding: var(--space-3) var(--space-5) var(--space-5);
}
.instrument-identity--position[href] {
  color: inherit;
  text-decoration: none;
}
.instrument-identity--position[href]:hover .instrument-identity__name,
.instrument-identity--position[href]:focus-visible .instrument-identity__name {
  text-decoration: underline;
}
.summary-strip--dividend {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
/* Six tiles share this strip, so its lead steps down to the size the rest of
   the strip already reads at; the strip clips rather than scrolls, and at the
   headline size the last tile fell off the right edge on a tablet. */
.summary-strip--dividend .summary-item__value {
  font-size: var(--text-xl);
}
.dividend-overview__period {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: var(--border-w) solid var(--color-border);
}
.dividend-overview__period-label {
  font-weight: 750;
}
.dividend-overview__period-meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.dividend-history {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
  min-width: 0;
  max-width: 100%;
  margin: 0;
  padding: 0 0 var(--space-2);
  overflow-x: auto;
  list-style: none;
}
.dividend-history__year {
  display: grid;
  grid-template-rows: var(--chart-height-compact) auto auto auto;
  flex: 1 0 calc(var(--space-8) + var(--space-7));
  gap: var(--space-1);
  min-width: 0;
  text-align: center;
}
.dividend-history__plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  border-bottom: var(--border-w) solid var(--color-border-strong);
}
.dividend-history__plot::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset-inline: 0;
  top: 50%;
  border-top: var(--border-w) dashed var(--color-border);
}
.dividend-history__bar {
  position: relative;
  z-index: 1;
  width: 58%;
  min-height: var(--space-1);
  background: var(--color-accent);
}
.dividend-history__bar--stacked { background: transparent; }
.dividend-history__segments {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.dividend-history__segment {
  stroke: var(--color-surface);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
  cursor: help;
}
.dividend-history__segment:hover,
.dividend-history__segment:focus {
  stroke: var(--color-text);
  stroke-width: var(--outline-w);
  outline: none;
}
.dividend-history__segment--1 { fill: var(--chart-allocation-1); }
.dividend-history__segment--2 { fill: var(--chart-allocation-2); }
.dividend-history__segment--3 { fill: var(--chart-allocation-3); }
.dividend-history__segment--4 { fill: var(--chart-allocation-4); }
.dividend-history__segment--5 { fill: var(--chart-allocation-5); }
.dividend-history__segment--6 { fill: var(--chart-allocation-6); }
.dividend-history__year--current .dividend-history__bar {
  background: var(--color-gain);
}
.dividend-history__year--current .dividend-history__bar--stacked {
  background: transparent;
}
.dividend-history__year--forecast {
  padding-inline: var(--space-2);
  border-inline-start: var(--border-w) solid var(--color-border);
}
.dividend-history__year--forecast .dividend-history__bar {
  background: transparent;
  border: calc(var(--border-w) * 2) dashed var(--color-accent);
}
.dividend-history__bar--0 { height: 0; min-height: 0; }
.dividend-history__bar--1 { height: 10%; }
.dividend-history__bar--2 { height: 20%; }
.dividend-history__bar--3 { height: 30%; }
.dividend-history__bar--4 { height: 40%; }
.dividend-history__bar--5 { height: 50%; }
.dividend-history__bar--6 { height: 60%; }
.dividend-history__bar--7 { height: 70%; }
.dividend-history__bar--8 { height: 80%; }
.dividend-history__bar--9 { height: 90%; }
.dividend-history__bar--10 { height: 100%; }
.dividend-history__label,
.dividend-history__comparison {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.dividend-history__value {
  overflow-wrap: anywhere;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.dividend-history__comparison.gain { color: var(--color-gain); }
.dividend-history__comparison.loss { color: var(--color-loss); }
.dividend-overview .table--fixed th:first-child,
.dividend-overview .table--fixed td:first-child { width: 36%; }
.dividend-overview .table--fixed th:not(:first-child),
.dividend-overview .table--fixed td:not(:first-child) { width: 16%; }
.dividend-overview .table--dividend-forecast th:first-child,
.dividend-overview .table--dividend-forecast td:first-child { width: 28%; }
.dividend-overview .table--dividend-forecast th:not(:first-child),
.dividend-overview .table--dividend-forecast td:not(:first-child) { width: 14.4%; }
.dividend-overview .table--fixed { min-width: calc(var(--workbench-master) + var(--workbench-rail)); }
.dividend-overview .table--fixed .num { white-space: nowrap; }
.pane {
  min-width: 0;
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-pane);
  overflow: hidden;
}
.pane--focus { border-color: var(--color-border-strong); }
/* No rule under the head. No board draws one: the head and the body are one
   surface and the head is told apart by its weight, so a divider there is a
   second horizontal line competing with the one the table already has under
   its column row. It also pays no bottom padding — whatever follows it, a
   body or a tab row, brings its own. */
.pane__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-tile-block) var(--margin) 0;
  background: var(--color-surface);
}
.pane__title { flex: 1 1 auto; min-width: 0; font-size: var(--text-pane-title); font-weight: 600; }
.pane__meta { color: var(--color-text-muted); font-size: var(--text-xs); }
.pane__header-actions {
  display: flex;
  flex: 0 1 auto;
  max-width: 100%;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}
.pane__body { padding: var(--space-4) var(--margin) var(--margin); }
/* Flush means the table reaches the pane's edges, not that everything does.
   The table pays its own gutter cell by cell; anything else in the body --- a
   help line under it, an alert over it --- still sits in the pane's margin,
   or it reads as falling out of the card. */
.pane__body--flush { padding: var(--space-3) 0 var(--space-2); }
.pane__body--flush > :not(.table-wrap):not(.tabs) { margin-inline: var(--margin); }
.pane__body--flush > .alert { margin: var(--space-4) var(--margin); }
.pane__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-top: var(--border-w) solid var(--color-border);
}
.workbench-layout {
  display: grid;
  gap: var(--space-5);
  /* A gutter, not a rhythm step. The strip above a split stands on the same
     tracks and pays the same gutter, and the artboards draw both at this
     one measure -- a wider space between the columns than between the tiles
     puts the two seams a few pixels apart, which is exactly as visible as
     putting them in different places altogether. */
  column-gap: var(--gutter);
  align-items: start;
  min-width: 0;
}
/* The two columns of a split finish level, as the boards draw them: on
   body-main the Value card and the Portfolios card share a top and a bottom
   edge. The layout above starts its columns rather than stretching them,
   which is right where a short pane should stay short, and wrong here --
   it left a visible step between two cards the reader reads as a pair. */
/* A select in a toolbar takes its width from its longest option, so one
   long label -- "All held or historical instruments" -- pushes the row past
   the viewport and wraps the button onto a second line. Seen on a tablet at
   1024. The cap is on the control rather than the label because shortening
   the label only moves the problem to the next long instrument name.

   A field in a toolbar, not a field inside a form inside one. The `form`
   was in this selector for two years and matched only one of the two ways a
   filter row is built: Dividends opens the group and puts the form inside
   it, while Positions and the ledger filters open the form and put the
   toolbar inside that -- so on those the form is an *ancestor* of the group
   and the cap silently did not apply. The rows it missed are the worst case
   for it, because their options are the owner's own tag and group names and
   the row's width was set by the longest one anybody had typed (issue 792).
   The alignment rule below keys on `> .field` and matched both all along,
   so the row lined up correctly and only the width dropped. */
.toolbar__group .field__input,
.toolbar__group .field { max-width: var(--control-select-max); }
.toolbar__group .field__input { text-overflow: ellipsis; }
.pane__body--narrow > * { max-width: var(--content-narrow); }
.workbench-layout--split { grid-template-columns: var(--workbench-split); align-items: stretch; }
.workbench-layout--split-start { grid-template-columns: var(--workbench-split); align-items: start; }
.workbench-layout--split > .workbench-layout__column > .pane { flex: 1 1 auto; }
.workbench-layout--sheet { grid-template-columns: var(--workbench-sheet); align-items: stretch; }
.workbench-layout--sheet > .workbench-layout__column > .pane { flex: 1 1 auto; }
.workbench-layout--thirds { grid-template-columns: var(--workbench-thirds); align-items: stretch; }
.workbench-layout--thirds > .workbench-layout__column > .pane { flex: 1 1 auto; }
.workbench-layout--ledger { grid-template-columns: minmax(0, 4fr) minmax(var(--workbench-rail), 1fr); }
.workbench-layout--evidence { grid-template-columns: minmax(var(--workbench-master), 1fr) minmax(0, 3fr); }
.workbench-layout--single { grid-template-columns: minmax(0, 1fr); }
/* Three equal panes that answer one question between them — the Signal v2
   income sheet reads metrics, growth and projection side by side, and putting
   any of them in a rail would say one matters more than the others. */
/* Three panes the reader reads as one row finish level, for the reason the
   split and thirds variants already give: the layout above starts its columns,
   which is right where a short pane should stay short and wrong where three
   cuts of one portfolio sit side by side. On Insights the currency, sector and
   country rings carry two, three and four keys, and started left a visible
   step between neighbours that a reader takes for a fourth figure. */
.workbench-layout--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: stretch;
}
/* And the pane fills the column it was stretched into, as in the two variants
   above: a stretched column holding a short pane is the same step again, one
   level down, with the difference hidden as empty space inside the column.
   The instrument's dividend view asks for three equal panes in its own comment
   and had never had them. */
.workbench-layout--three > .workbench-layout__column > .pane { flex: 1 1 auto; }
.workbench-layout__column {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}
.workbench-layout__wide { grid-column: 1 / -1; }
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  min-height: var(--control-height);
}
.toolbar__group {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  min-width: 0; max-width: 100%;
}
/* Line the fields up on their labels, not on their feet. Bottom-alignment
   read fine while every field was a label over a control, and then a field
   with a help line under it grew downwards — so its input rode a line above
   the two beside it while the help text alone sat level with them. */
.toolbar__group:has(> .field) { align-items: flex-start; }
/* ...and a button in that row then sat level with the labels, a line above
   the inputs it acts on. It cannot be aligned to the bottom -- that is the
   rule above, and the help line is why -- so it is pushed down by exactly the
   block a field puts over its control: one --text-sm line at the body's
   leading, plus the field's own row gap. Both are tokens, so this stays
   correct if either moves; a pixel here would not. */
.toolbar__group:has(> .field) > .btn {
  margin-block-start: calc(var(--text-sm) * var(--leading-base) + var(--space-1));
}
/* A checkbox or a nested group is in the same position as the button and
   was left out of it, so it rode up level with the labels. It takes the same
   push and is centred on the control's height, so it shares the inputs'
   line. The row's note is left out: in a narrow drawer it wraps onto a line
   of its own, and the push would open a gap above it there. A note that
   belongs on the line goes inside a nested group with what it describes. */
@media (min-width: 48.0625rem) {
  .toolbar__group:has(> .field) > :is(.check-field, .toolbar__group) {
    display: flex; align-items: center;
    min-height: var(--control-height);
    margin-block-start: calc(var(--text-sm) * var(--leading-base) + var(--space-1));
  }
}
/* The first group takes the row so a group marked --end has somewhere to go. */
.toolbar > .toolbar__group { flex: 1 1 auto; }
.toolbar__group--end { flex: 0 0 auto; margin-inline-start: auto; }
/* A note keeps its place beside what it explains and never claims the
   end of the row; the controls do. */
.toolbar__group > .toolbar__meta { flex: 0 1 auto; min-width: 0; }
/* A filter group is usually a form, and a form is a box the row has to
   see through: without this the controls inside it stack instead of
   sitting on the line the rest of the toolbar is on. */
.toolbar__group > form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}
.toolbar__group > .field { flex: 0 1 auto; }
.toolbar__meta { color: var(--color-text-muted); font-size: var(--text-xs); }
/* ---- tabs -------------------------------------------------------------
   A row of links on the pane's divider, not a bordered box: the divider is
   already there, so the active tab is marked by a rule under its own label
   rather than by a fill. The count rides inside the link so it cannot be
   read as a separate control, and it is quieter than the label it counts. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  padding-inline: var(--margin);
  border-bottom: var(--border-w) solid var(--color-border);
}
/* A tab row is the pane's divider, so it spans the pane and carries its own
   inset -- which is why `.pane__body--flush` exempts it from the rule that
   insets everything else. In a padded body it has to undo that padding for
   itself, or it is indented twice and its rule stops short of the edges it is
   meant to divide. What follows stands off it: the row is a boundary, and
   content resting on a boundary reads as belonging to it.

   The undoing is only correct where there is something to undo. A flush body
   has no inline padding, so the negative margin pulled the row a margin past
   the pane on each side and the row's own inset brought its labels back to
   exactly the pane's edge -- which is the owner's report, and the smaller half
   of it: the divider was overhanging the pane's own border. Measured against
   this stylesheet, a padded body put the first label 21 from the pane and a
   flush body put it at 1, with the rule standing 19 proud on each side. */
.pane__body > .tabs { margin-block-end: var(--space-4); }
.pane__body:not(.pane__body--flush) > .tabs {
  margin-inline: calc(var(--margin) * -1);
}
/* The same boundary rule, for the control that never got it. A segmented row
   inside a pane body had no standoff at all, so on the one page that puts one
   directly above a table the pills rested on the table with nothing between
   them -- while a tab row in that position took the space above. The principle
   is the one written for tabs three comments up; only the selector was
   missing. */
.pane__body > .segmented { margin-block-end: var(--space-4); }
.tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tab-height);
  margin-right: var(--space-tile-block);
  /* The current tab's rule replaces the divider under it rather than standing
     on top of it. Without this the tab's own underline sits above the row's
     border and the selected tab is marked by three stacked pixels in two
     tones, which is also why the row measured 41 against the 38 every board
     draws. Every artboard pulls the tab down over the border. */
  margin-block-end: calc(var(--border-w) * -1);
  padding-inline: var(--space-1);
  border-bottom: var(--underline-w) solid transparent;
  color: var(--color-text-muted);
  font-size: var(--text-table);
  font-weight: 500;
  text-decoration: none;
}
.tab:last-child { margin-right: 0; }
.tab:hover { color: var(--color-text); }
.tab[aria-current="page"] {
  color: var(--color-text);
  border-bottom-color: var(--color-text);
}
.tab__n { color: var(--color-text-muted); font-size: var(--text-micro); font-weight: 500; }
.tab[aria-current="page"] .tab__n { color: var(--color-text-secondary); }

/* A track with pills riding in it, as every artboard draws it — not a
   bordered row with dividers. The chosen segment is a filled pill, so there
   is no rule under it and no accent: the fill is the whole signal, which is
   why this survives the accent colour being dropped product-wide. */
.segmented {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
}
.segmented__item {
  display: inline-flex;
  align-items: center;
  min-height: var(--segment-height);
  padding-inline: var(--control-pad-sm);
  border: 0;
  border-radius: var(--radius-pill);
  color: var(--color-text-muted);
  background: transparent;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}
.segmented__item:hover { color: var(--color-text); }
a.segmented__item:hover { color: var(--color-text); text-decoration: none; }
.segmented__item[aria-current="page"],
.segmented__item[aria-pressed="true"] {
  color: var(--color-text);
  background: var(--color-surface-selected);
  font-weight: 600;
}
/* A count riding inside a segment, where a tab row would have carried it under
   the pane's name. Its own small disc so the number reads as a quantity rather
   than as part of the word: round at one digit, a short pill at two or three,
   which is as far as a count in a pane head goes. */
.segmented__n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--count-disc);
  height: var(--count-disc);
  margin-inline-start: var(--space-2);
  padding-inline: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-count);
  font-weight: 600;
  line-height: 1;
}
/* One ground for every disc, so selection is carried by the number and the
   word. Giving the unselected discs the lighter fill made them read louder
   than the selected one. */
.segmented__item[aria-current="page"] .segmented__n { color: var(--color-text); }
/* A pill group in the title bar, as every artboard draws it — not the banded
   row it used to be below the toolbar. It carries no heading of its own, so
   the names are the label. */
.portfolio-switcher {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 0 1 auto;
  min-width: 0;
  padding: var(--space-1);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
  overflow-x: auto;
  scrollbar-width: none;
}
.portfolio-switcher::-webkit-scrollbar { display: none; }
.portfolio-switcher__option {
  flex: 0 0 auto;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.portfolio-switcher__option:hover {
  background: var(--color-surface);
  color: var(--color-text);
  text-decoration: none;
}
.portfolio-switcher__option[aria-current="page"] {
  background: var(--color-surface-selected);
  color: var(--color-text);
}
.command-band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-block: var(--border-w) solid var(--color-border-strong);
  background: var(--color-surface-subtle);
}
.command-band__label { font-size: var(--text-xs); font-weight: 600; }
/* A band whose whole content is what to do next. Its name stays for the
   screen reader and the buttons go to the end, where the reader's hand
   already is after the last field. */
.command-band--actions { justify-content: flex-end; }
.equation {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3);
  border-block: var(--border-w) solid var(--color-border-strong);
  background: var(--color-surface);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}
.equation__value { font-size: var(--text-lg); font-weight: 700; }
.equation__operator { color: var(--color-text-muted); }
.master-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.master-item {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-bottom: var(--border-w) solid var(--color-border);
  color: var(--color-text);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
}
.master-item:hover { background: var(--color-surface-subtle); }
.master-item[aria-pressed="true"] {
  background: var(--color-surface-selected);
  box-shadow: inset var(--accent-bar-w) 0 var(--color-accent);
}
.master-item__top { display: flex; justify-content: space-between; gap: var(--space-3); }
.master-item__title { font-weight: 700; }
.master-item__meta { display: block; color: var(--color-text-muted); font-size: var(--text-xs); }
/* ---- Mode A dashboard compositions ------------------------ */
.dashboard-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(15rem, 1fr);
  gap: var(--space-4);
  align-items: start;
}
.dashboard-layout--briefing { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dashboard-layout--performance { grid-template-columns: minmax(0, 1fr); }
.dashboard-layout--allocation { grid-template-columns: minmax(0, 3fr) minmax(15rem, 2fr); }
.dashboard-layout--ledger { grid-template-columns: minmax(0, 3fr) minmax(15rem, 1fr); }
.dashboard-layout__wide { grid-column: 1 / -1; }

/* ---- dense analysis and decision workspaces (Modes B-D) --- */
.workspace-layout {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}
.workspace-layout--analysis {
  grid-template-columns: minmax(0, 2fr) minmax(var(--workspace-aside-min), 1fr);
}
.workspace-layout--decision {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.workspace-layout__wide { grid-column: 1 / -1; }

/* ---- guided workflow progress ----------------------------- */
.workflow-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.workflow-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-top: var(--space-2);
  border-top: var(--accent-bar-w) solid var(--color-border-strong);
}
.workflow-step--complete { border-top-color: var(--color-gain); }
.workflow-step--current { border-top-color: var(--color-accent); }
.workflow-step__number,
.workflow-step__state {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.workflow-step__label { font-size: var(--text-sm); font-weight: 600; }
.workflow-step--complete .workflow-step__state { color: var(--color-gain); }
.workflow-step--current .workflow-step__state { color: var(--color-accent); }

.stat-row {
  display: grid;
  grid-template-columns: minmax(16rem, 1.5fr) repeat(4, minmax(8rem, 1fr));
  gap: var(--space-5);
}

/* ---- standing: the one-paragraph answer ------------------- */
.standing {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}
/* The other one. The artboards' own .eyebrow is capitals at 11/600, and this
   is its counterpart, so it keeps them where the rest of the sheet lost them. */
.standing__eyebrow {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.standing__headline {
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  text-wrap: pretty;
}
.standing__body {
  color: var(--color-text-secondary);
  text-wrap: pretty;
}
.standing__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}
.standing__actions > a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding-inline: var(--space-2);
}

/* Supporting evidence and occasional editing stay within the workbench. */
.disclosure {
  border-block: var(--border-w) solid var(--color-border);
  min-width: 0;
}
.disclosure > summary {
  cursor: pointer;
  min-height: var(--touch-target);
  padding: var(--space-3);
  font-weight: 600;
}
.disclosure__body {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-3);
  min-width: 0;
}
.conditional-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}
.conditional-fields[hidden] { display: none; }
.conditional-fields > legend {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  margin-block-end: var(--space-2);
}

/* A claim is stated beside what it rests on, never above it: the evidence
   column is a sibling, so the two scroll and print together. */
.pane__body--split {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}
.pane__body--split > * + * {
  padding-inline-start: var(--space-6);
  border-inline-start: var(--border-w) solid var(--color-border);
}
.pane__body--evidence { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }
.pane__body--stacked { grid-template-columns: minmax(0, 1fr); padding: 0; }
/* In a flush stack, prose keeps the gutter and a table section pays its own
   inset cell by cell. Retain a section wrapper when it owns a scroll window. */
.pane__body--flush > .pane__body.pane__body--stacked-flush { margin-inline: 0; }
.pane__body--stacked-flush > :not(.table-wrap):not(:has(> .table-wrap)) { margin-inline: var(--margin); }
.pane__body--stacked > * + * {
  padding-inline-start: 0;
  padding-block-start: var(--space-4);
  border-inline-start: 0;
  border-block-start: var(--border-w) solid var(--color-border);
}
/* Metric lists either side of a stacked boundary are one list of rows written
   as two sections -- the months and the sessions of the same period -- so the
   boundary between them is a row boundary and pays what a row pays. A bridge
   above a table is two different things and keeps the block gap.

   Without this the last row of the first list loses its own rule to
   :last-child, correctly, because that rule is written for a list that ends a
   pane. This one does not end the pane; it hands over to another list. So the
   row kept none of its own spacing and then stood in the block gap as well,
   and one row in twelve sat in more than twice the space of its neighbours --
   the owner's report of 17 September 2026, issue 425. (Written without the
   hash: design-lint reads three digits after one as a raw hex colour.) */
.pane__body--stacked > :has(> .metric-list) + :has(> .metric-list) {
  margin-block-start: calc(var(--space-3) - var(--space-6));
  padding-block-start: var(--space-3);
}
/* A stacked body inside the pane that fills: its last section takes the spare
   row and becomes the flex column the scroll window inside it is asking for.
   Without both halves the window sits at its floor -- its flex:1 resolves
   against a block parent, which is no parent at all, and the grid has no
   fractional row to give. */
.pane--fill > .pane__body > .pane__body--stacked,
.pane--fill > .pane__body--stacked {
  /* flex:1 as well as the fractional row: a pane wraps its caller in one
     .pane__body and pane_body() adds a second, so the stacked grid is itself
     a flex item. Give it only the row and it still sizes to its content,
     because nothing told it to grow first. */
  flex: 1;
  grid-template-rows: auto minmax(0, 1fr);
  min-block-size: 0;
}
.pane--fill > .pane__body > .pane__body--stacked > :last-child,
.pane--fill > .pane__body--stacked > :last-child {
  /* align-self as well: a split body aligns its children to start, so the
     section sits at its content height inside a row that is now taller than
     it. Every link in this chain has to be present -- the row, the growth and
     the stretch -- and it measured at its floor until all three were. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  min-block-size: 0;
}

.pane[id], .disclosure[id] { scroll-margin-block-start: calc(var(--space-8) + var(--space-7)); }

/* ---- policy band ------------------------------------------
   Every lane is centred on its own target, so drift reads as distance from
   the middle before any number is read. Geometry arrives as percentages
   computed in the page view; the lane stretches horizontally, so strokes are
   non-scaling and no element relies on a preserved aspect ratio. */
.policy-band {
  display: grid;
  grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr) minmax(10rem, 15rem);
  gap: var(--space-5);
  align-items: center;
  min-width: 0;
}
.policy-band__identity,
.policy-band__readout {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.policy-band__readout { text-align: right; align-items: flex-end; }
.policy-band__label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-base);
  font-weight: 650;
}
.policy-band__swatch {
  flex: 0 0 auto;
  width: var(--space-3);
  height: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
}
.policy-band__swatch--1 { background: var(--chart-allocation-1); }
.policy-band__swatch--2 { background: var(--chart-allocation-2); }
.policy-band__swatch--3 { background: var(--chart-allocation-3); }
.policy-band__swatch--4 { background: var(--chart-allocation-4); }
.policy-band__swatch--5 { background: var(--chart-allocation-5); }
.policy-band__swatch--6 { background: var(--chart-allocation-6); }
.policy-band__meta,
.policy-band__scale {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.policy-band__value {
  font-family: var(--font-data);
  font-size: var(--text-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.policy-band__lane {
  display: block;
  width: 100%;
  height: var(--space-5);
  overflow: visible;
}
.policy-band__track { fill: var(--color-border); }
.policy-band__tolerance {
  fill: var(--color-surface-selected);
  stroke: var(--color-border-strong);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
}
.policy-band__breach { fill: var(--color-warn); }
.policy-band__target {
  stroke: var(--color-text-muted);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
}
/* A breach paints the overshoot in the same warn colour as the marker, so
   the marker needs a surface ring or the current position disappears into
   the bar it is meant to sit at the end of. */
.policy-band__marker-halo {
  stroke: var(--color-surface);
  stroke-width: var(--space-3);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.policy-band__marker {
  stroke: var(--color-text);
  stroke-width: var(--space-2);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.policy-band--breach .policy-band__marker { stroke: var(--color-warn); }
.policy-band--breach .policy-band__value { color: var(--color-warn); }
/* In a rail there is no room for three columns: identity above, readout
   below, the lane between. Same geometry, same colours, smaller type. */
.policy-band--compact {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
  padding-block: var(--space-2);
  border-top: var(--border-w) solid var(--color-border);
}
.policy-band--compact .policy-band__identity,
.policy-band--compact .policy-band__readout {
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  text-align: left;
}
.policy-band--compact .policy-band__label { font-size: var(--text-sm); }
.policy-band--compact .policy-band__value { font-size: var(--text-base); }
.policy-band--compact .policy-band__lane { height: var(--space-4); }
.policy-band--compact .policy-band__scale-row { display: none; }
.policy-band--compact .policy-band__readout > :last-child { order: -1; }
.policy-band__scale-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
}

/* ---- attribution bridge -----------------------------------
   An exact identity drawn as floating bars on one vertical scale. Each leg
   is restated in the table beneath, so the drawing is never the only record. */
.bridge {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-2);
  align-items: start;
  min-width: 0;
}
.bridge__column {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.bridge__plot {
  display: block;
  width: 100%;
  height: var(--chart-height-compact);
  overflow: visible;
}
.bridge__zero { stroke: var(--color-border-strong); stroke-width: var(--border-w); }
.bridge__connector {
  stroke: var(--color-border-strong);
  stroke-width: var(--border-w);
  stroke-dasharray: 3 3;
}
.bridge__bar { fill: var(--color-border-strong); rx: var(--radius-chart); }
.bridge__bar--gain { fill: var(--color-gain); }
.bridge__bar--loss { fill: var(--color-loss); }
.bridge__bar--total { fill: var(--color-text); }
.bridge__label,
.bridge__value {
  display: block;
  text-align: center;
  overflow-wrap: anywhere;
}
.bridge__label { color: var(--color-text-secondary); font-size: var(--text-xs); }
.bridge__value {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* ---- cumulative gap ---------------------------------------
   Excess return against a benchmark, chained day by day. The zero line is
   the benchmark, so the reader compares against a line rather than a number. */
.gap-chart { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.gap-chart__plot {
  display: block;
  width: 100%;
  height: var(--chart-height-compact);
  overflow: visible;
}
.gap-chart__zero {
  stroke: var(--color-text-muted);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
}
.gap-chart__line {
  fill: none;
  stroke: var(--color-text);
  stroke-width: var(--outline-w);
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.gap-chart__scale {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* ---- monthly return series --------------------------------
   One bar per calendar month on a shared vertical scale. Zero is a drawn
   line, so a losing month is a bar below it and colour is never the only
   signal; the best, worst and benchmark-relative months are restated as
   rows beneath. The plot is unscaled in x, which is why the bars carry no
   corner radius: non-uniform scaling would stretch one into an ellipse.
   Those rows follow directly beneath and a pane body is a plain block, so
   the plot owns the space under its own scale. */
.month-chart {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
  min-width: 0;
}
.month-chart .chart-tooltip { border-radius: var(--radius-sm); pointer-events: auto; }
/* A bar is a control now, so it takes the focus ring every other one takes. */
.month-chart__bar:focus-visible { outline: var(--outline-w) solid var(--color-focus); }
.month-chart__plot {
  display: block;
  width: 100%;
  height: var(--chart-height-compact);
  overflow: visible;
}
.month-chart__zero {
  stroke: var(--color-text-muted);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
}
.month-chart__bar { fill: var(--color-border-strong); }
.month-chart__bar--gain { fill: var(--color-gain); }
.month-chart__bar--loss { fill: var(--color-loss); }
/* ---- chart axis ------------------------------------------
   A plot and the scale beside it. Both plots stretch a unit box
   (preserveAspectRatio="none") so that a pane of any shape gets the full
   width, which means no text can live inside them -- a glyph would stretch
   with the box. The axis is HTML in a gutter to the right, absolutely placed
   on the same percentages the plot is drawn from, so the two cannot drift.

   The gutter is a fixed width rather than max-content: its children are
   absolutely positioned and would collapse the column to nothing. 4rem holds
   the widest thing in it -- the closing marker, dot and all -- and is the
   share of the frame the board gives its own axis. */
.chart-frame {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 4rem;
  column-gap: var(--space-2);
  align-items: stretch;
  min-width: 0;
}
/* The gutter is its own SVG at 1:1 with its box, so its text is drawn at the
   size it says and never stretched the way the plot's own box is. Everything
   inside is placed with attributes rather than a style, which is the only
   thing the design gate accepts and what every other chart here already
   does. */
.chart-axis {
  display: block;
  block-size: var(--chart-height-compact);
  overflow: visible;
}
.chart-axis__label,
.chart-axis__marker {
  fill: var(--color-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
/* The closing value, against the end of the line: the dot is centred on the
   gutter's leading edge, which is exactly where the plot's right edge is, so
   it lands on the last point rather than beside it. */
.chart-axis__marker { fill: var(--color-text); font-weight: 650; }
.chart-axis__marker.gain, .chart-axis__dot.gain { fill: var(--color-gain); }
.chart-axis__marker.loss, .chart-axis__dot.loss { fill: var(--color-loss); }
/* The line takes the sign its own marker already took (issue 601). The dot and
   the closing figure have been signed since they were drawn; the polyline they
   sit at the end of was the text ink whatever the gap did, so a portfolio
   behind its benchmark drew a neutral line to a red number. Flat keeps
   --color-text above, which is the rule rather than an omission. */
.gap-chart__line.gain { stroke: var(--color-gain); }
.gap-chart__line.loss { stroke: var(--color-loss); }
.chart-axis__dot {
  fill: var(--color-surface);
  stroke: currentColor;
  stroke-width: var(--border-w);
}
.chart-grid {
  stroke: var(--color-border);
  stroke-width: var(--border-w);
  vector-effect: non-scaling-stroke;
}

/* The dates close the chart, on the rule every block under them is closed
   with. Without it they ran into the first metric row -- which carries a
   border of its own -- so the plot's own foot read as part of that row
   instead of as the end of the chart. Same padding and same rule as
   `.metric-row`, so the rhythm down the pane is one rhythm (issue 800). */
.month-chart__scale {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  padding-block-end: var(--space-3);
  border-block-end: var(--border-w) solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* ---- evidence bar ----------------------------------------
   One proportional segment per evidence class. Colour is never the only
   signal: every segment is restated as a labelled row beneath. */
.evidence-bar {
  display: block;
  width: 100%;
  height: var(--space-3);
  border-radius: var(--radius-chart);
  overflow: hidden;
  background: var(--color-border);
}
.evidence-bar__segment--announced { fill: var(--color-warn); }
.evidence-bar__segment--estimated { fill: var(--color-accent); }
.evidence-bar__segment--none { fill: var(--color-border-strong); }
.evidence-bar__segment--unknown { fill: var(--color-text-muted); }

/* ---- section navigation (narrow viewports) ---------------- */
.section-nav {
  display: none;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  border-top: var(--border-w) solid var(--color-border);
  background: var(--color-surface);
}
.section-nav a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--space-7);
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: none;
}
/* A direct child only: the bar marks where you are with a rule along the top
   edge of the slot, and a cell in the sheet has no such edge to draw it on. */
.section-nav > a[aria-current="page"] {
  color: var(--color-text);
  font-weight: 650;
  box-shadow: inset 0 var(--outline-w) var(--color-text);
}
.section-nav__icon { flex: 0 0 auto; }

/* ---- the phone bar's fifth slot, and what it opens ---------
   More is a slot of the bar, so it is shaped like the four beside it rather
   than like the bordered control it was in the title bar.

   What it opens is the rest of the rail in the bar's own shape: the rail's
   marks, cells of icon over label, three across. The owner chose that over a
   list on 19 September 2026. The first version reused the title bar's
   dropdown -- twelve words in one column, no marks, no scrim, no current
   state, over half the screen -- and no gate could see it, because the tool
   that photographs a page cannot capture a sheet anchored on a fixed bar.

   The bar is the containing block -- it is position: fixed -- so the sheet
   anchors on it and needs no height written down anywhere. */
.section-nav .shell-menu--all { position: static; display: block; }
.section-nav .shell-menu--all > summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--space-7);
  padding: 0;
  border: 0;
  border-radius: 0;
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: inherit;
}
.section-nav .shell-menu--all[open] > summary { color: var(--color-text); }
/* No caret. The generic .shell-menu rule draws one and is declared after the
   --all rule that removes it, so a slot in the bar inherits a marker under
   its label that points at nothing. */
.section-nav .shell-menu--all > summary::after { content: none; }
/* The page behind, dimmed, so the sheet reads as a layer over it. It sits
   inside the disclosure, so it arrives and leaves with the sheet.

   It dims the page and stops at the bar, rather than covering the screen.
   Covering the screen covered More itself -- the summary is static and the
   scrim is positioned, so the scrim painted over the one control that closes
   the sheet, and every tap on the page landed on a scrim that closes nothing
   (issue 801). Anchored on the bar like the sheet above it, the bar stays pressable
   with or without a script: More closes what More opened, and the four slots
   beside it still navigate. */
.section-nav .shell-menu__scrim {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 100%;
  block-size: 100vh;
  background: var(--color-backdrop);
}
.section-nav .shell-menu__items {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 100%;
  display: block;
  padding-block-end: var(--space-2);
  border-block-start: var(--border-w) solid var(--color-border);
  border-start-start-radius: var(--radius-pane);
  border-start-end-radius: var(--radius-pane);
  background: var(--color-surface);
}
.section-nav .shell-menu__head {
  display: block;
  padding: var(--space-3) var(--space-4) var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.section-nav .shell-menu__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding-inline: var(--space-2);
}
/* .section-nav a already stacks a label under a mark and centres it, which is
   what a cell here is; what it does not have is the resting surface that says
   which one you are on, because the bar says that with a rule above the slot
   and a sheet has no edge to draw it on. */
.section-nav .shell-menu__grid > a {
  min-height: var(--nav-rail-item);
  border-radius: var(--radius-sm);
  font-size: var(--text-nav-compact);
  text-align: center;
}
.section-nav .shell-menu__grid > a[aria-current="page"] {
  background: var(--color-surface-subtle);
  color: var(--color-text);
}

/* ---- pane figure ------------------------------------------ */
.pane__figure {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  padding-bottom: var(--space-3);
  border-bottom: var(--border-w) solid var(--color-border);
}
.pane__figure-label {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
}
.pane__figure-value {
  font-family: var(--font-data);
  font-size: var(--text-2xl);
  font-weight: 700;
  line-height: var(--leading-tight);
  overflow-wrap: anywhere;
}
.pane__figure-meta { color: var(--color-text-muted); font-size: var(--text-xs); }

/* ---- attention and account lists -------------------------- */
.attention-list,
.metric-list,
.allocation-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.attention-item,
.metric-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-w) solid var(--color-border);
}
.attention-item:last-child,
.metric-row:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}
/* A group reads as one thing: no rules inside it, one rule under it. The
   rule is the boundary between this group and whatever follows, which is the
   only boundary the reader needs. Row-by-row rules inside a group of two made
   the first portfolio look like a section of its own. */
.metric-list--group > .metric-row {
  padding-bottom: 0;
  border-bottom: 0;
}
.metric-list--group > .metric-row:last-child {
  padding-bottom: var(--space-3);
  border-bottom: var(--border-w) solid var(--color-border);
}
/* The same rows read across a pane instead of down a column, for a list that
   states what the reader holds above prose that talks about it. The research
   note's own: five figures beside a 218-word paragraph made a column three
   times shorter than the text next to it, and the space left over was the
   owner's report of 21 September 2026. Across, the list is one band and the
   paragraph under it keeps the pane's full measure.

   A row turns with the list: label over figure, since a label at one end of
   a cell a seventh of the pane wide and its figure at the other is a row
   nobody reads as a pair.
   The value loses its right edge for the same reason -- five figures aligned
   left under five labels line up with the labels rather than with a rule
   that is not there. One rule under the band, and none between the rows. */
/* Three rows the whole band shares, which is the strip's own mechanic and the
   reason it works: a label, a figure and a note line up across every cell
   whether or not a cell has all three. Built as five independent stacks the
   figures sat on two baselines, a line apart and measured, because the two
   cells carrying a note were taller than the three that did not, and five
   figures in a row on two lines is not a row. */
.metric-list--across {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  gap: var(--space-1) var(--gutter);
  padding-bottom: var(--space-4);
  border-bottom: var(--border-w) solid var(--color-border);
}
.metric-list--across > .metric-row {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  align-content: start;
  padding-bottom: 0;
  border-bottom: 0;
  min-width: 0;
}
.metric-list--across .metric-row__label { font-size: var(--text-xs); }
/* The figure keeps the sheet's own size and takes its separation from the
   data face and the weight instead. **22 was tried and measured**: the scale
   has no step between 16 and 22, and at 22 "CHF 46'780.00" wraps inside a
   cell a fifth of this pane wide, which puts the five figures back on the two
   baselines the subgrid above exists to remove. A figure that wraps is a
   worse defect than a figure that is one step quieter than it could be, and
   the strip gets 27 because its lead tile is more than twice as wide as one
   of these cells. */
.metric-list--across .metric-row__value {
  align-self: baseline;
  text-align: left;
  font-family: var(--font-data);
  font-size: var(--text-lg);
  line-height: var(--leading-figure);
}
/* The note a figure carries, under the figure. It is the third line of a
   cell, so it is the last thing read and qualifies what is above it; put
   between the label and the figure it joins them into a sentence neither was
   written to be part of. Muted and small, the weight `metric-row__meta` has
   in a row, because it is the same note in a different place. */
.metric-row__note {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
/* The eyebrow over a pane's second section. Small, spaced and upper-case, so
   it reads as a label for what follows rather than as a row of it. */
.group-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.group-heading__meta {
  font-size: var(--text-xs);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

/* A group is its own list, so the list's own row gap does not reach the group
   below it — the rule ended up with space above and none beneath, which is
   the one uneven step in the pane. This is that gap, paid once. */
.metric-list--group { margin-bottom: var(--space-3); }
/* The last group needs no boundary beneath it: the pane's own edge is the
   boundary. A rule there is a line drawn against nothing. */
.metric-list--group:last-child { margin-bottom: 0; }
.metric-list--group:last-child > .metric-row:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}
.attention-item__title,
.metric-row__value { font-weight: 600; }
.attention-item__meta,
.metric-row__label {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.metric-row__label {
  min-width: 0;
  overflow-wrap: anywhere;
}
.metric-row__meta { display: block; margin-top: var(--space-1); font-size: var(--text-xs); }
/* On the label's own line, for a note short enough to read as part of it. */
.metric-row__meta--inline {
  display: inline;
  margin-top: 0;
  margin-inline-start: var(--space-2);
}
.metric-row__value {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---- allocation and drift bars ---------------------------- */
.allocation-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.allocation-item__header,
.allocation-item__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.allocation-item__label { font-weight: 600; }
.allocation-item__meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
.allocation-item__track {
  appearance: none;
  width: 100%;
  height: var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-border);
}
.allocation-item__track::-webkit-progress-bar { background: var(--color-border); }
.allocation-item__track::-webkit-progress-value { background: var(--color-accent); }
.allocation-item__track::-moz-progress-bar { background: var(--color-accent); }
.allocation-item--warn .allocation-item__track::-webkit-progress-value { background: var(--color-warn); }
.allocation-item--warn .allocation-item__track::-moz-progress-bar { background: var(--color-warn); }

/* ---- the morning's note ------------------------------------
   A headline and exactly three paragraphs. The count is the response
   schema's, so the grid is fixed rather than auto-fitted: a note can never
   arrive with a fourth paragraph to break the row, and a two-paragraph note
   leaving a column empty would be the schema failing, which the read model
   refuses before the page sees it. */
.daily-note {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
/* The portal's page-title size rather than the board's 24: the type scale
   has no step between 22 and 31, and a heading a page invents for itself is
   the thing the token scale exists to stop. */
.daily-note__headline {
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: var(--leading-tight);
  text-wrap: pretty;
}
.daily-note__prose {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-6);
}
/* Three columns is the morning note's shape, and a note with one paragraph
   in it is not that shape: it takes a third of the pane and wraps at about
   twenty characters, which is a column of single words rather than prose.
   A research note is one paragraph about one company and met this first,
   but nothing here is about that page -- a brief that ever composed
   one paragraph would have drawn the same. Higher specificity than the two
   breakpoints below, so it wins at every width without repeating itself. */
.daily-note__prose:has(> :only-child) {
  grid-template-columns: minmax(0, 1fr);
}
/* One measure, paragraphs down it. A note whose paragraphs are grouped by
   subject is uneven by design -- the filed years carry six sentences and the
   week carries two -- and three equal columns set the shortest one beside the
   longest with the rest of its column empty. It also takes four paragraphs,
   which a three-column grid hangs under the first. */
.daily-note__prose--stacked { grid-template-columns: minmax(0, 1fr); }
.daily-note__paragraph {
  color: var(--color-text-secondary);
  text-wrap: pretty;
}

@media (max-width: 90rem) {
  .shell-menu > summary { padding-inline: var(--space-2); }
}
@media (max-width: 80rem) and (min-width: 48.0625rem) {
  /* Tablet widths: values step down one size, and a strip of five or six
     tiles flows into rows of three instead of clipping its last tiles. The
     lead is the only tile still above this size, so it is the only one that
     has to be brought down. */
  .summary-item--primary .summary-item__value,
  body[data-headline="return"] .summary-item--leads-on-return .summary-item__value { font-size: var(--text-xl); }
  body[data-headline="return"] .summary-item--primary.as-return .summary-item__value { font-size: var(--text-xl); }
  .summary-strip--aligned { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .summary-group { display: contents; }
  .summary-group > .summary-item--primary { grid-column: span 2; }
  .summary-strip {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-flow: row;
    row-gap: var(--space-1);
  }
  /* A stacked strip pays its padding and its gaps twice, so it takes the
     tighter density the artboards give a tile on a phone rather than the one
     they give it on the web. Held to one row the strip is the same height
     either way, and the artboards' own web spacing reads better; stacked, the
     web spacing is what turns a summary into half of the first screen. */
  .summary-item { padding: var(--space-3); }
}
@media (max-width: 56rem) {
  .stat-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
  }
  .stat-row .stat:first-child { grid-column: 1 / -1; }
  .summary-strip--aligned { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .summary-group { display: contents; }
  .summary-group > .summary-item--primary { grid-column: span 2; }
  .summary-strip {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-flow: row;
    row-gap: var(--space-1);
  }
  /* Stacked here too, so the same tighter density. */
  .summary-item { padding: var(--space-3); }
  .summary-item--primary { grid-column: span 2; }
  .workbench-layout--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two, for the same reason as the line above it: three columns of prose in
     this width read at about twenty-five characters a line, which is the
     measure problem the three columns exist to fix, arriving from the other
     end. */
  .daily-note__prose { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 48rem) {
  /* The glance: the primary nav collapses into a bottom section bar, which
     is reachable one-handed. The scrolling top nav is redundant then. */
  .section-nav {
    display: grid;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 20;
  }
  /* The rail folds away with the top nav: the bottom bar is the phone's
     navigation, and two navigations is one too many. Everything the rail
     carried stays reachable from the overflow menu in the title bar. */
  .shell { grid-template-columns: minmax(0, 1fr); }
  .rail { display: none; }
  .shell-menu--all { display: block; }
  /* The filling page gets its tail back here: what it clears is the fixed
     section bar, not spare height, and sums hidden under the navigation are
     worse than sums above a band. */
  .shell-main,
  .shell__column:has(.workbench--fill) .shell-main { padding-bottom: calc(var(--space-7) + var(--space-5)); }
  /* Important actions remain easy to tap alongside dense financial rows. */
  .btn, .shell-menu > summary, .shell-menu__items > a {
    min-height: var(--touch-target);
  }
  .field__input { min-height: var(--touch-target); font-size: var(--text-base); }
  .summary-item__value { white-space: normal; overflow-wrap: anywhere; }
  .summary-item--primary .summary-item__value,
  body[data-headline="return"] .summary-item--leads-on-return .summary-item__value { font-size: var(--text-xl); }
  body[data-headline="return"] .summary-item--primary.as-return .summary-item__value { font-size: var(--text-xl); }
  .pane__header { padding-inline: var(--space-3); }
  .pane__body { padding-inline: var(--space-3); }
  .pane__body--flush, .pane__body--stacked { padding-inline: 0; }
  /* Two across rather than five: on a phone a fifth of the pane is about
     sixty across, narrower than "CHF 46'780.00" and narrower than three of
     the five labels. Two keeps the band a band instead of letting it become
     the column it was drawn to replace. */
  .metric-list--across {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3) var(--space-4);
  }
  .pane__body--stacked-flush > :not(.table-wrap):not(:has(> .table-wrap)) { margin-inline: var(--space-3); }
  .table-wrap--scroll-window { max-height: none; }
  .table--responsive thead {
    position: absolute;
    width: var(--border-w);
    height: var(--border-w);
    overflow: hidden;
    clip-path: inset(50%);
  }
  .table--responsive tbody, .table--responsive tr { display: grid; }
  .table--responsive tr {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
    padding: var(--space-4) var(--space-3);
    border-bottom: var(--border-w) solid var(--color-border);
  }
  .table--responsive tbody tr > th,
  .table--responsive td[colspan],
  .table--responsive td:has(form) { grid-column: 1 / -1; }
  .table--responsive tbody tr > th { grid-row: 1; }
  .table--responsive thead:has(.table-sort) {
    position: static;
    width: auto;
    height: auto;
    clip-path: none;
  }
  .table--responsive thead:has(.table-sort) tr { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
  .table--responsive thead:has(.table-sort) tr::before { content: "Sort by"; font-size: var(--text-xs); }
  .table--responsive thead:has(.table-sort) th { padding: 0; border: 0; }
  .table--responsive thead:has(.table-sort) th:not(:has(.table-sort)) { display: none; }
  .table--responsive .table-sort { min-height: var(--touch-target); padding-inline: var(--space-2); }
  .table.table--responsive tbody tr > th,
  .table.table--responsive tbody tr > td {
    padding: 0;
    border: 0;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: start;
  }
  .table--responsive [data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--text-xs);
    font-weight: 400;
  }
  /* The one-percent width that keeps the costs column tight beside the
     amount would set the stacked label one letter a line; and a row with no
     cost has nothing to put under the label at all. */
  .table--entry-log.table--responsive td.entry-log__costs { inline-size: auto; }
  .table--entry-log.table--responsive td.entry-log__costs:not(:has(.entry-log__cost)) { display: none; }

  /* --- a data table becomes a list, not a card ----------------
     The fold above turns a row into a two-column card, which is right for a
     row whose cells have no natural leader and wrong for most financial
     tables: on a holding it printed a card 271 tall per line, so the Overview
     named no holding until 1384 down and fitted two on a screen. The phone
     boards draw a row of 57 -- a mark, a name over its qualifier, and a
     figure over its second figure. Every one of them draws the same object,
     from a holding to a ledger entry to a return by period.

     This is that object. The geometry is here; which cells fill it is stated
     per table below, because the geometry generalises and the judgement does
     not. A ledger row leads with an amount and a watched listing with a
     close, and nothing in a stylesheet can work that out.

     Two ways to say which cells fill it, and the difference is worth
     understanding before adding a third table:

       data-label  -- .table--positions places by the fold's own captions, so
                      no template changed to get the row. The cost is that
                      the caption text is load-bearing: rename the column and
                      the phone layout silently reverts to a card.
       data-phone  -- every other table names the slot on the cell. More to
                      write, but the placement survives a column being
                      renamed, and it can promote a cell that is not the row
                      header, which a caption cannot.

     New tables should use data-phone. It is read on both variants, so markup
     added to the positions table later -- a group header row, say -- can name
     its slots rather than inherit the caption scheme that table started on.
     Nothing there uses it yet; the rules below simply accept it. */
  .table--positions.table--responsive,
  .table--listrow.table--responsive {
    /* Eight characters of an instrument and its ticker beneath. Below this a
       name stops identifying the row it is on. */
    --listrow-name-min: 8rem;
    /* Load-bearing. While this is display: table the browser keeps sizing
       cells by its own column algorithm whatever the row declares, and the
       identity cell sits at 83 in a track of 245 with the instrument name
       wrapped over six lines. */
    display: block;
  }
  /* Both groups, not just the body. A table-header-group left inside a table
     that is no longer a table gets wrapped in an anonymous table box, which
     shrink-to-fits: the sort row came out 344 wide inside a table of 380 and
     wrapped its last chip onto a second line, costing more than the row
     rules had just saved. */
  .table--positions.table--responsive > thead,
  .table--positions.table--responsive > tfoot,
  .table--positions.table--responsive > tbody,
  .table--listrow.table--responsive > thead,
  .table--listrow.table--responsive > tfoot,
  .table--listrow.table--responsive > tbody { display: block; }
  .table--positions.table--responsive :is(tbody, tfoot) tr,
  .table--listrow.table--responsive :is(tbody, tfoot) tr {
    display: grid;
    /* The name has a floor and the figures do not. A money cell may carry a
       note under its figure, and on the watchlist's stale rows that note is a
       sentence -- "nothing since 02 Sep 2026" -- which sized the figure track
       to 270 of a work area of 338 and left the instrument 20, printing the
       name as "A...". Letting the figure track merely shrink is not enough:
       a max-content track still takes its content's width first and leaves
       the remainder to the 1fr beside it. So the name states the width below
       which it stops being a name, the figures take what is left, and the
       note is clipped like the name above it. A row whose figures are
       genuinely short is unaffected -- the floor only binds when something
       long is in the other track. */
    grid-template-columns: minmax(var(--listrow-name-min), 1fr) minmax(0, max-content);
    grid-template-rows: auto auto;
    align-items: center;
    /* Both, not column-gap alone: the fold sets `gap`, and a column-gap here
       leaves its row gap of 12 standing between the value and the change --
       12 of the row's height, spent holding two lines of one figure apart. */
    gap: 0 var(--space-3);
    padding: var(--space-1) var(--margin);
    min-height: var(--touch-target);
  }
  /* The row shows what it places and nothing else. Hiding the columns that do
     not fit by name looked equivalent and was not: between 760 and 768 the
     work area is wide enough for the container query to give a p3 column back
     while this fold is still in force, and an unplaced cell auto-placed
     itself into a third line. The phone standard is two lines each side with
     everything else dropped, so dropping is the default and placing is the
     exception -- a column added or restored upstream cannot lengthen the row.

     It fails closed, which is the trade: a column added to a template later
     is invisible here until someone places it. That is the right failure for
     the 760-768 band and the wrong one to be surprised by, so it is written
     down rather than discovered. */
  .table--positions.table--responsive :is(tbody, tfoot) tr > *,
  .table--listrow.table--responsive :is(tbody, tfoot) tr > * { display: none; }
  /* The row header leads unless a cell claims the slot. A received dividend
     is headed by its portfolio and led on a phone by its date; the table says
     so on the cell rather than restructuring its markup for one viewport. */
  .table--positions.table--responsive :is(tbody, tfoot) tr > th,
  .table--listrow.table--responsive :is(tbody, tfoot) tr > th,
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr > [data-phone="head"] {
    display: block;
    grid-column: 1;
    /* min-width, not width: a width of 100% leaves the cell at its
       shrink-to-fit size inside the track. */
    min-width: 100%;
    text-align: start;
  }
  /* The leading cell spans both rows by default, because most of these
     identities are already two lines of their own -- a name over its ticker,
     a pool over its currency. Only a row that states a separate meta cell
     gives the second line away, and then the header takes one.

     Getting this backwards costs a line rather than erroring: the header
     sits on row 1 at two lines tall, the second figure sits on row 2, and
     the row is three lines instead of two. */
  .table--positions.table--responsive :is(tbody, tfoot) tr > th,
  .table--listrow.table--responsive :is(tbody, tfoot) tr > :is(th, [data-phone="head"]) { grid-row: 1 / 3; }
  .table--listrow.table--responsive :is(tbody, tfoot) tr:has(> [data-phone="meta"], > [data-phone="meta-named"]) > :is(th, [data-phone="head"]) { grid-row: 1; }
  /* One meta slot, not a list of them: two lines each side is the boards'
     discipline, and a second meta cell would land on a third line or on top
     of the first. Where a row wants two qualifiers the template composes them
     into one cell, which the money cell already does for a charge under a
     price. */
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr > :is([data-phone="meta"], [data-phone="meta-named"]) {
    display: block;
    grid-column: 1;
    grid-row: 2;
    min-width: 100%;
    color: var(--color-text-muted);
    font-size: var(--text-xs);
    text-align: start;
  }
  /* A qualifier that is a bare figure needs its column to mean anything:
     "+1.12%" under a period is unreadable and "Benchmark +1.12%" is what the
     board draws. A qualifier that is already a phrase does not -- "Driver
     Price move" is worse than "Price move". So the caption comes back on the
     cells that ask for it, inline rather than on a line of its own. */
  .table--listrow.table--responsive [data-phone="meta-named"][data-label]::before {
    content: attr(data-label) " ";
    display: inline;
    margin: 0;
    color: inherit;
    font-size: inherit;
    font-weight: inherit;
  }
  .table--positions.table--responsive :is(tbody, tfoot) tr > [data-label="Value"],
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr > [data-phone="lead"] {
    display: block;
    grid-column: 2;
    grid-row: 1;
    text-align: end;
  }
  .table--positions.table--responsive :is(tbody, tfoot) tr > [data-label="P/L"],
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr > [data-phone="sub"] {
    display: block;
    grid-column: 2;
    grid-row: 2;
    text-align: end;
  }
  /* A row that can still be acted on keeps its control, in a track of its
     own beside the figures. Dropping it is right where the row opens the
     thing it is about and the action is on that page; where the row is inert
     without it, dropping it leaves a list nobody can use. */
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr:has(> [data-phone="act"]) {
    grid-template-columns: minmax(var(--listrow-name-min), 1fr) minmax(0, max-content) max-content;
  }
  :is(.table--positions, .table--listrow).table--responsive :is(tbody, tfoot) tr > [data-phone="act"] {
    display: block;
    grid-column: 3;
    grid-row: 1 / 3;
    text-align: end;
  }
  /* The control's own width comes out of the name's. At the button's standing
     inline padding the action took 100 of a work area of 338 and the identity
     got 130, which truncates an instrument at thirteen characters. The label
     is unchanged and the tap target is unchanged -- the row is already taller
     than the minimum -- so the padding is the only thing there is to give. */
  .table--listrow.table--responsive :is(tbody, tfoot) tr > [data-phone="act"] .btn {
    padding-inline: var(--space-2);
  }
  /* The name is one line and clipped. Wrapping is what the card was for. */
  .table--positions.table--responsive .instrument-identity,
  .table--listrow.table--responsive .instrument-identity { width: 100%; gap: var(--space-2); }
  .table--positions.table--responsive .instrument-identity__name,
  .table--positions.table--responsive .instrument-identity__meta,
  .table--listrow.table--responsive .instrument-identity__name,
  .table--listrow.table--responsive .instrument-identity__meta,
  .table--listrow.table--responsive :is(tbody, tfoot) tr > :is(th, [data-phone="head"], [data-phone="meta"], [data-phone="meta-named"]) {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    overflow-wrap: normal;
  }
  /* The note under a figure is clipped for the same reason the name beside it
     is: one line each, and wrapping is what the card was for. */
  .table--positions.table--responsive :is([data-label="Value"], [data-label="P/L"]) .money-cell__secondary,
  :is(.table--positions, .table--listrow).table--responsive :is([data-phone="lead"], [data-phone="sub"]) .money-cell__secondary {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  /* In a list the column is the label. Repeating it on every row is what the
     card needs and what costs this row 37 of its height. */
  .table--positions.table--responsive [data-label]::before,
  .table--listrow.table--responsive [data-label]::before { content: none; }
  /* Two lines of money beside a two-line name is three lines of row. The
     leading is the desktop's, set for a row with one line in it; the sizes
     are not touched, because the figures are the reason the page exists. */
  .table--positions.table--responsive .instrument-identity__name,
  .table--positions.table--responsive .instrument-identity__meta,
  .table--positions.table--responsive .money-cell__primary,
  .table--positions.table--responsive .money-cell__secondary,
  .table--listrow.table--responsive .instrument-identity__name,
  .table--listrow.table--responsive .instrument-identity__meta,
  .table--listrow.table--responsive .table__primary,
  .table--listrow.table--responsive .table__meta,
  .table--listrow.table--responsive .money-cell__primary,
  .table--listrow.table--responsive .money-cell__secondary { line-height: var(--leading-tight); }
  /* The fold gives every row 16 above and below, which the sort row does not
     need: its chips already clear the tap target on their own. */
  .table--positions.table--responsive thead tr,
  .table--listrow.table--responsive thead tr { padding-block: var(--space-2); }
  /* The footing stays on one line, three columns rather than two rows.
     A sticky footing draws its rule as a box on each cell rather than as a
     border -- a collapsed border scrolls away with the table -- so cells on
     two rows draw two short lines at two heights. On one row, with no column
     gap for them to break across, the same boxes meet and read as the single
     rule the desktop draws. The sums stay stuck to the bottom of the window,
     which is the whole reason that rule exists.

     It is not only a sticky problem, which is what measuring it here
     corrected: `.table tfoot > tr > *` gives every footing cell its own
     border-top, so a footing split over two grid rows draws two short rules
     at two heights whether it scrolls or not. Every list row's footing gets
     one line. */
  .table--positions.table--responsive tfoot tr {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-rows: auto;
    gap: 0;
  }
  .table--positions.table--responsive tfoot tr > th { grid-row: 1; }
  .table--positions.table--responsive tfoot tr > [data-label="Value"] { grid-column: 2; grid-row: 1; }
  .table--positions.table--responsive tfoot tr > [data-label="P/L"] { grid-column: 3; grid-row: 1; }
  .table--positions.table--responsive tfoot tr > :not(:first-child) { padding-inline-start: var(--space-3); }
  .table--listrow.table--responsive tfoot tr {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-rows: auto;
    gap: 0;
  }
  .table--listrow.table--responsive tfoot tr > :is(th, [data-phone="head"]) { grid-column: 1; grid-row: 1; }
  .table--listrow.table--responsive tfoot tr > [data-phone="lead"] { grid-column: 2; grid-row: 1; }
  .table--listrow.table--responsive tfoot tr > [data-phone="sub"] { grid-column: 3; grid-row: 1; }
  .table--listrow.table--responsive tfoot tr > :not(:first-child) { padding-inline-start: var(--space-3); }

  .bridge { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .bridge__column {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    padding-block: var(--space-2);
    border-bottom: var(--border-w) solid var(--color-border);
  }
  .bridge__plot { display: none; }
  .bridge__label { grid-column: 1; grid-row: 1; text-align: start; font-size: var(--text-sm); }
  .bridge__value { grid-column: 2; grid-row: 1; text-align: end; }
  .policy-band {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
  }
  .policy-band__readout { align-items: flex-start; text-align: left; }
  .pane__body--split,
  .pane__body--evidence { grid-template-columns: minmax(0, 1fr); }
  /* One column where every other split on this page becomes one. A pane that
     reflowed at a width nothing else on the page reflowed at would read as a
     defect rather than as a layout. */
  .daily-note__prose { grid-template-columns: minmax(0, 1fr); }
  .pane__body--split > * + * {
    padding-inline-start: 0;
    padding-block-start: var(--space-4);
    border-inline-start: 0;
    border-block-start: var(--border-w) solid var(--color-border);
  }
  .shell-main { padding-top: var(--space-5); }
  .page-header {
    align-items: flex-start;
    flex-direction: column;
  }
  .workbench-header {
    align-items: flex-start;
    flex-direction: column;
  }
  .workbench-layout,
  .workbench-layout--sheet,
  .workbench-layout--split,
  .workbench-layout--thirds,
  .workbench-layout--ledger,
  .workbench-layout--evidence,
  .workbench-layout--single,
  .workbench-layout--three {
    grid-template-columns: minmax(0, 1fr);
  }
  .workbench-layout__wide { grid-column: auto; }
  .summary-strip--dividend { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dividend-overview,
  .alternative-intelligence { padding-inline: var(--space-3); }
  .dashboard-layout,
  .dashboard-layout--briefing,
  .dashboard-layout--performance,
  .dashboard-layout--allocation,
  .dashboard-layout--ledger {
    grid-template-columns: minmax(0, 1fr);
  }
  .dashboard-layout__wide { grid-column: auto; }
  .workspace-layout,
  .workspace-layout--analysis,
  .workspace-layout--decision {
    grid-template-columns: minmax(0, 1fr);
  }
  .workspace-layout__wide { grid-column: auto; }
  .workflow-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .attention-item,
  .allocation-item__header,
  .allocation-item__meta {
    align-items: flex-start;
  }
  .metric-row,
  .allocation-item__meta {
    flex-direction: column;
    gap: var(--space-1);
  }
  .metric-row__value { text-align: left; }
}

@media (max-width: 32rem) {
  .form-fields { grid-template-columns: minmax(0, 1fr); }
  .toolbar__group > .field,
  .conditional-fields,
  .conditional-fields > .field { flex: 1 1 100%; }
  .field__input { width: 100%; }
  .section-nav a { min-width: 0; }
  .summary-strip--aligned { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .summary-group { display: contents; }
  .summary-group > .summary-item--primary { grid-column: 1 / -1; }
  .summary-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-flow: row;
  }
  .summary-item--primary { grid-column: 1 / -1; }
  .summary-strip--dividend .summary-item--primary { grid-column: auto; }
  .summary-strip--dividend .summary-item--primary .summary-item__value { font-size: var(--text-base); }
  .toolbar,
  .command-band { align-items: stretch; flex-direction: column; }
  .portfolio-headline { align-items: flex-start; flex-direction: column; }
  .portfolio-headline__change-group { align-items: flex-start; text-align: left; }
  .segmented { width: 100%; }
  /* ...except inside a flush pane body, which already gives it gutters.
     `.pane__body--flush > :not(.table-wrap):not(.tabs)` sets `--margin` on
     both sides, and `width: 100%` then claims the body's whole content box on
     top of the left one -- so the control is as wide as its holder while
     starting a margin in, and overhangs the right edge by exactly `--margin`.
     `.pane` clips, so the reader loses the rounded end of the last control
     rather than seeing it hang out. Measured on Income's "Received window" at
     phone width, which is the only control in the product this selector
     matches. `auto` lets the two margins decide the width, which is what they
     are for, and the control still fills the row between them. */
  .pane__body--flush > .segmented { width: auto; }
  .segmented__item { flex: 1 1 auto; padding-inline: var(--space-2); }
}

@media (max-width: 24rem) {
  .section-nav a { font-size: var(--text-nav-compact); }
  .summary-item__value,
  .summary-item--primary .summary-item__value,
  body[data-headline="return"] .summary-item--leads-on-return .summary-item__value { font-size: var(--text-lg); }
  body[data-headline="return"] .summary-item--primary.as-return .summary-item__value { font-size: var(--text-lg); }
}
/* Shared column assignment: dragging and standard form controls are equivalent. */
.column-mapping { display: grid; gap: var(--space-4); }
.column-mapping__sources { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.column-mapping__source { cursor: grab; text-align: left; white-space: normal; }
.column-mapping__target { padding: var(--space-2); border: var(--border-w) solid transparent; }
.column-mapping__target[data-drag-over] { outline: var(--border-w) solid currentColor; }

/* ---- dividend income views --------------------------------
   Series colours are fixed per instrument slot and never cycled; slot 0 is
   "Other". Rects and legend keys share the classes so identity is never
   colour alone: every bar also carries its label and amount. */
.series-1 { fill: var(--chart-series-1); background: var(--chart-series-1); }
.series-2 { fill: var(--chart-series-2); background: var(--chart-series-2); }
.series-3 { fill: var(--chart-series-3); background: var(--chart-series-3); }
.series-4 { fill: var(--chart-series-4); background: var(--chart-series-4); }
.series-5 { fill: var(--chart-series-5); background: var(--chart-series-5); }
.series-6 { fill: var(--chart-series-6); background: var(--chart-series-6); }
.series-0 { fill: var(--chart-series-other); background: var(--chart-series-other); }
.series-key {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 0.125rem;
  vertical-align: baseline;
}
.series-key--projected {
  background: transparent;
  border: var(--border-w) dashed var(--color-accent);
}
.series-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-3) 0 0;
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
}
.series-legend__item { display: inline-flex; align-items: center; gap: var(--space-2); }
.year-chart { position: relative; margin: var(--space-3) 0 0; }
.year-chart .chart-tooltip { border-radius: var(--radius-sm); pointer-events: auto; }
.year-chart__svg { display: block; width: 100%; height: auto; font-family: var(--font-data); }
.year-chart__grid { stroke: var(--chart-grid); stroke-width: 1; }
.year-chart__tick, .year-chart__label { fill: var(--color-text-muted); font-size: 0.75rem; }
.year-chart__total { fill: var(--color-text); font-size: 0.8125rem; font-weight: 600; }
.year-chart__change { font-size: 0.75rem; font-weight: 600; }
.year-chart__change.gain { fill: var(--color-gain); }
.year-chart__change.loss { fill: var(--color-loss); }
.year-chart__bar { cursor: pointer; }
.year-chart__segment { rx: var(--radius-chart); }
.year-chart__bar a:hover .year-chart__segment,
.year-chart__bar a:focus-visible .year-chart__segment { opacity: 0.85; }
.year-chart__bar[aria-current="true"] .year-chart__label { fill: var(--color-text); font-weight: 700; }
.year-chart__hatch-ground { fill: var(--color-surface); }
.year-chart__hatch-line { stroke: var(--color-accent); stroke-width: 2; }
.year-chart .year-chart__segment--projected { fill: url("#year-chart-hatch"); stroke-width: 1.5; }
.year-chart .year-chart__segment--projected.series-1 { stroke: var(--chart-series-1); }
.year-chart .year-chart__segment--projected.series-2 { stroke: var(--chart-series-2); }
.year-chart .year-chart__segment--projected.series-3 { stroke: var(--chart-series-3); }
.year-chart .year-chart__segment--projected.series-4 { stroke: var(--chart-series-4); }
.year-chart .year-chart__segment--projected.series-5 { stroke: var(--chart-series-5); }
.year-chart .year-chart__segment--projected.series-6 { stroke: var(--chart-series-6); }
.year-chart .year-chart__segment--projected.series-0 { stroke: var(--chart-series-other); }
.holding-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
}
.holding-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-4);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}
.holding-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.holding-card__figure { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-3); }
.holding-card__value {
  font-family: var(--font-data);
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  font-variant-numeric: tabular-nums;
}
.holding-card__change { font-size: var(--text-xs); font-weight: 600; color: var(--color-text-muted); }
.holding-card__change.gain { color: var(--color-gain); }
.holding-card__change.loss { color: var(--color-loss); }
.holding-card__meta, .holding-card__foot { color: var(--color-text-muted); font-size: var(--text-xs); }
.holding-card__svg { display: block; width: 100%; height: 6rem; font-family: var(--font-data); }
.holding-card__svg rect { rx: var(--radius-chart); }
.holding-card__bar--past { opacity: 0.55; }
.month-strip { display: flex; flex-direction: column; gap: var(--space-1); margin-top: var(--space-3); font-size: var(--text-xs); overflow-x: auto; }
.month-strip__row {
  display: grid;
  min-width: 48rem;
  grid-template-columns: 4.5rem repeat(12, minmax(0, 1fr)) 7.5rem;
  gap: var(--space-1);
  align-items: center;
}
.month-strip__head { text-align: center; color: var(--color-text-muted); }
.month-strip__head--total, .month-strip__total { text-align: right; font-variant-numeric: tabular-nums; }
.month-strip__label { color: var(--color-text-secondary); }
.month-strip__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2.5rem;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  background: var(--color-surface-subtle);
}
.month-strip__cell--heat-0 { color: var(--color-text-muted); }
.month-strip__cell--heat-1 { background: color-mix(in oklab, var(--color-accent) 25%, var(--color-surface)); }
.month-strip__cell--heat-2 { background: color-mix(in oklab, var(--color-accent) 40%, var(--color-surface)); }
.month-strip__cell--heat-3 { background: color-mix(in oklab, var(--color-accent) 55%, var(--color-surface)); }
.month-strip__cell--heat-4 { background: color-mix(in oklab, var(--color-accent) 75%, var(--color-surface)); }
.month-strip__cell--heat-5 { background: var(--color-accent); color: var(--color-accent-contrast); }
@media (max-width: 64rem) {
  .holding-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 40rem) {
  .holding-cards { grid-template-columns: minmax(0, 1fr); }
}


/* ---- instrument combobox ----------------------------------
   The list sits under the input, inside the field's own box, so nothing
   else on the form moves while it is open. */
.combobox { position: relative; }
/* A search field carries its magnifier inside the box, and is wide enough for
   a full company name without clipping. */
.field__control { position: relative; display: flex; align-items: center; max-width: 100%; }
.field__control > .glyph {
  position: absolute; inset-inline-start: var(--space-3);
  color: var(--color-text-muted); pointer-events: none;
}
.field__control > .field__input {
  width: var(--workbench-master); max-width: 100%;
  padding-inline-start: calc(var(--space-3) + var(--space-5));
}
/* A pane clips to its rounded corners, and a short pane cut the open list
   off at its own bottom edge. Only while the list is open. */
.pane:has(.combobox__list:not([hidden])) { overflow: visible; }
/* An empty state centres its field, so the list centres on it too; held to
   the field's start it ran off a phone's right edge. */
.empty .combobox__list { inset-inline: auto; left: 50%; translate: -50% 0; }
.combobox__list {
  position: absolute;
  z-index: 2;
  inset-inline: 0;
  top: calc(var(--space-5) + var(--control-height) + var(--space-1));
  max-width: min(100%, var(--content-narrow));
  /* Never narrower than a row can be read in. Beside a button the input is
     short, and a list held to it wrapped every name a word per line. */
  min-width: min(24rem, calc(100vw - 2 * var(--space-5)));
  max-height: 20rem;
  text-align: start;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
}
.combobox__option, .combobox__empty {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.combobox__empty { cursor: default; color: var(--color-text-muted); font-size: var(--text-sm); }
.combobox__option:hover,
.combobox__option[aria-selected="true"] { background: var(--color-surface-selected); }
.combobox__selected { color: var(--color-text-secondary); font-size: var(--text-xs); }
/* A row with a column of its own for the ticker, because the ticker is what
   was typed. Everything that used to sit on one muted line -- ticker, venue,
   currency, ISIN and two status words at one weight -- is ranked instead:
   name first, the venue line under it, and the ticker and what this workspace
   already knows about the identity on the right. */
.combobox__option--rich {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.combobox__option--rich .instrument-identity { min-width: 0; }
.combobox__option-rail { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.combobox__ticker {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-text);
}
/* The part of the row that is the reason it is in the list. */
.combobox__match { font-weight: 700; color: var(--color-text); }
/* Two groups, and they divide on what the *workspace* knows: an instrument
   its ledger names, or a listing it watches. Not on whether a listing id
   exists -- `instrument_listings` is shared reference data, so a row can
   carry one because another workspace holds it. */
.combobox__group {
  padding: var(--space-2) var(--space-3) var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* The pick, framed. Unframed it was an identity line one shade off the help
   text under it, which is a poor way to show the one field on this form whose
   value is not what the reader typed. */
.combobox__selected--picked {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-selected);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius-sm);
}

/* ============================================================
   Signal v2 components. design/portal-mockups/signal2/PORT.md
   ============================================================ */

/* --- privacy ------------------------------------------------
   A portfolio gets opened in public. Hiding the amounts has to
   leave the shape of the page intact — the percentages, the
   ranking, the gain and loss reading all still work — so a
   figure is redacted in place rather than removed, and the
   layout never moves.

   Redaction, not blur. The flat grammar has no filter, and a
   blurred number is only half hidden: squint, or sharpen the
   screenshot, and it comes back. That reasoning still stands;
   what changed is the mark. The first build drew a solid bar in
   the figure's own colour, and the day after, on 10 September
   2026, the owner compared six treatments and chose Blank: the
   digits go transparent and a dashed rule stays where they
   were, like a field nobody filled in. The direction colour
   moves to the rule, so a gain still reads as a gain without
   saying how large it is.

   A rule and not a wrapper, because .amount sits on the value
   element itself — chart-factory.js writes textContent onto the
   headline readouts, so a span around the digits would not
   survive the first chart interaction. The digits stay in the
   DOM behind their transparent colour, so user-select is what
   stops a redacted figure being copied back out. */
.amount { transition: color var(--ease), text-decoration-color var(--ease); }
[data-private="on"] .amount {
  color: transparent;
  /* SVG text paints with `fill`, not `color`, so a marked <text> stayed fully
     legible under the rule above -- the Income year chart's totals and ticks
     were the instance. Setting both here rather than adding an SVG-only
     selector: `fill` does nothing on an HTML element, and two rules that have
     to agree eventually do not. */
  fill: transparent;
  text-decoration-line: underline;
  text-decoration-style: dashed;
  text-decoration-thickness: var(--border-w);
  text-underline-offset: var(--space-1);
  text-decoration-color: var(--color-border-strong);
  user-select: none;
}
[data-private="on"] .amount * { color: transparent; fill: transparent; }
[data-private="on"] .amount.gain { text-decoration-color: var(--color-gain); }
[data-private="on"] .amount.loss { text-decoration-color: var(--color-loss); }
@media (prefers-reduced-motion: reduce) { .amount { transition: none; } }

/* --- the feedback bar ---------------------------------------
   Inline work reports back here. It fades; it never asks to be
   dismissed, because a report you must dismiss is a dialog. */
.toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-5);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  color: var(--color-text);
  /* It rises into place and fades out of it, so its resting state is
     below the mark and transparent; .is-on brings it up. The
     reduced-motion escape is in the motion section at the end. */
  opacity: 0;
  transform: translate(-50%, var(--space-3));
  transition: opacity var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out);
}
/* The display above beats the user agent's [hidden], the same way .strength
   does below. Until the shell carried one on every page a hidden toast was
   merely transparent rather than gone -- which is a fixed bar across the foot
   of the page, invisible and still taking the taps aimed under it. */
.toast[hidden] { display: none; }
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.toast__dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--radius-pill);
  background: var(--color-gain);
  flex: 0 0 auto;
}
.toast--error .toast__dot { background: var(--color-loss); }

/* --- overlays -----------------------------------------------
   A modal is a decision that interrupts; a drawer is a task
   beside a page that stays readable. Never two at once. */
.overlay { position: fixed; inset: 0; z-index: 30; display: flex; }
.overlay[hidden] { display: none; }
.overlay__scrim { animation: overlay-scrim-in var(--t-base) var(--ease-out) backwards; }
.overlay__modal { animation: overlay-modal-in var(--t-base) var(--ease-out) backwards; }
.overlay__drawer { animation: overlay-drawer-in var(--t-slow) var(--ease-out) backwards; }
.overlay[data-closing="true"] .overlay__scrim { animation: overlay-scrim-out var(--t-fast) var(--ease-out) forwards; }
.overlay[data-closing="true"] .overlay__modal { animation: overlay-modal-out var(--t-fast) var(--ease-out) forwards; }
.overlay[data-closing="true"] .overlay__drawer { animation: overlay-drawer-out var(--t-base) var(--ease-out) forwards; }
@keyframes overlay-scrim-in { from { opacity: 0; } }
@keyframes overlay-modal-in { from { opacity: 0; transform: translateY(var(--space-2)) scale(0.99); } }
@keyframes overlay-drawer-in { from { opacity: 0; transform: translateX(var(--margin)); } }
@keyframes overlay-scrim-out { to { opacity: 0; } }
@keyframes overlay-modal-out { to { opacity: 0; transform: translateY(var(--space-2)) scale(0.99); } }
@keyframes overlay-drawer-out { to { opacity: 0; transform: translateX(var(--margin)); } }
@media (prefers-reduced-motion: reduce) {
  .overlay .overlay__scrim, .overlay .overlay__modal, .overlay .overlay__drawer,
  .overlay[data-closing="true"] .overlay__scrim,
  .overlay[data-closing="true"] .overlay__modal,
  .overlay[data-closing="true"] .overlay__drawer { animation: none; }
}
.overlay__scrim { position: absolute; inset: 0; background: var(--color-backdrop); }
.overlay__modal {
  position: relative;
  margin: auto;
  width: min(var(--content-dialog), calc(100% - var(--space-6)));
  max-height: calc(100% - var(--space-7));
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.overlay__drawer {
  position: relative;
  margin-left: auto;
  width: min(28rem, 100%);
  height: 100%;
  background: var(--color-surface);
  border-left: var(--border-w) solid var(--color-border-strong);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.overlay__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: var(--border-w) solid var(--color-border);
}
.overlay__title { font-size: var(--text-base); font-weight: 600; margin-right: auto; }
.overlay__body {
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow: auto;
  flex: 1 1 auto;
}

/* --- empty states -------------------------------------------
   Four kinds, and treating them alike is the usual mistake.
   Only "done" is green: it is success, and a grey box there
   reads as broken. */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding: var(--space-7) var(--space-5);
  margin: auto;
}
.empty__icon {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-sm);
  background: var(--color-surface-subtle);
  border: var(--border-w) solid var(--color-border);
  display: grid;
  place-items: center;
  color: var(--color-text-muted);
}
.empty__title { font-size: var(--text-base); font-weight: 600; }
.empty__line {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  max-width: 46ch;
  line-height: var(--leading-base);
}
.empty--done .empty__icon { border-color: var(--color-gain); color: var(--color-gain); }

/* --- the session's shape ------------------------------------
   A line beside the percentage it explains: a position up two per
   cent all session and one that gave back a spike read the same
   as a number and differently as a shape. Narrow on purpose — it
   qualifies the figure beside it rather than competing with it. */
/* The cell is the line and nothing else: a table cell's own gutter is wider
   than the shape, so paying it twice left nine pixels of a fifty-six pixel
   drawing. The gutter either side comes from the columns it sits between. */
.table th.spark-col,
.table td.spark-col { width: var(--spark-width); padding-inline: 0; }
.spark {
  display: block;
  width: var(--spark-width);
  height: var(--spark-height);
  overflow: visible;
}
.spark path {
  fill: none;
  stroke: var(--color-text-muted);
  stroke-width: 1.25;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.spark--gain path { stroke: var(--color-gain); }
.spark--loss path { stroke: var(--color-loss); }
/* The column drops with p4, the canvas's own first-to-go threshold: a shape
   is the first thing a narrow table can spare, because the figure it
   qualifies is still in the column beside it. The priority classes carry
   that, so this rule states nothing about width. */

/* --- skeletons ----------------------------------------------
   Shape first, numbers last. Sized to what will arrive, so the
   page does not jump when it does. */
.sk {
  display: block;
  border-radius: var(--radius-chart);
  background: var(--color-surface-subtle);
  animation: sk-pulse 1.4s ease-in-out infinite;
}
.sk--label { width: 5rem; height: 0.75rem; }
.sk--value { width: 8rem; height: 1.5rem; }
.sk--foot  { width: 7rem; height: 0.75rem; }
.sk--line  { width: 100%; height: 0.875rem; }
.sk--pill  { width: 4rem; height: 1.25rem; border-radius: var(--radius-pill); }
.sk--chart { width: 100%; height: var(--chart-height); }
/* Pending table cells retain their content's layout while the existing
   skeleton occupies that measured cell. Headers and paging remain readable. */
.table.table--loading tbody > tr > td,
.table.table--loading tbody > tr > th { position: relative; color: transparent; }
.table--loading tbody > tr > td > *,
.table--loading tbody > tr > th > * { visibility: hidden; }
.table--loading tbody > tr > td > .sk,
.table--loading tbody > tr > th > .sk {
  position: absolute;
  inset-inline: var(--margin);
  top: 50%;
  transform: translateY(-50%);
  width: auto;
  visibility: visible;
}
/* Inside a chart the skeleton takes the whole box rather than a row of its
   own. The plain chart centres its children in a grid and the activity chart
   stacks them in a column, so a second child in normal flow would add its own
   height to either; taking it out of flow keeps the pane exactly the size it
   was. The status sentence is lifted back over it, because the artboard draws
   the shape alone and the application also says what it is waiting for. */
.chart > .sk--chart { position: absolute; inset: 0; width: auto; height: auto; }
.chart > .chart__status { position: relative; }
@keyframes sk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) { .sk { animation: none; } }

/* --- switch -------------------------------------------------
   Takes effect at once. A setting that needs saving is a
   checkbox, and this is not one. */
.switch-field__label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  font-size: var(--text-sm);
}
.switch-field__input {
  position: absolute;
  width: var(--border-w);
  height: var(--border-w);
  opacity: 0;
}
.switch-field__track {
  width: 2.25rem;
  height: 1.25rem;
  padding: 0.125rem;
  border-radius: var(--radius-pill);
  background: var(--color-surface-selected);
  border: var(--border-w) solid var(--color-border-strong);
  flex: 0 0 auto;
  transition: background-color var(--ease);
}
.switch-field__knob {
  display: block;
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-pill);
  background: var(--color-text-muted);
  transition: transform var(--ease), background-color var(--ease);
}
.switch-field__input:checked + .switch-field__track { background: var(--color-gain); }
.switch-field__input:checked + .switch-field__track .switch-field__knob {
  transform: translateX(1rem);
  background: var(--color-surface);
}
.switch-field__input:focus-visible + .switch-field__track {
  outline: var(--outline-w) solid var(--color-focus);
  outline-offset: 0.125rem;
}
.switch-field__help { font-size: var(--text-xs); color: var(--color-text-muted); }
@media (prefers-reduced-motion: reduce) {
  .switch-field__track, .switch-field__knob { transition: none; }
}

/* --- icon button --------------------------------------------
   Icon only, so the accessible name carries the whole meaning.
   Never a commit: those say what they do in words. */
.iconbtn {
  width: 2.125rem;
  height: 2.125rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  border: var(--border-w) solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  cursor: pointer;
}
.iconbtn:hover { background: var(--color-surface-subtle); color: var(--color-text); }
.iconbtn[aria-pressed="true"] { background: var(--color-surface-selected); color: var(--color-text); }
/* The theme button carries all three marks and shows the one its state
   names. Both marks and button are the same grid cell, so the three stack
   and the button is sized by the icon like every other one. */
.theme-mark { display: none; }
.theme-mark > .glyph { display: block; }
[data-theme-state="auto"] > .theme-mark[data-theme-mark="auto"],
[data-theme-state="light"] > .theme-mark[data-theme-mark="light"],
[data-theme-state="dark"] > .theme-mark[data-theme-mark="dark"] { display: block; }

/* --- curated sort order --------------------------------------
   aria-sort="other" means the table arrived ordered by something
   that is not a value: severity, reporting order, a configured
   list. The indicator is a level bar, not a chevron. */
[aria-sort="other"] .table-sort__indicator { opacity: 0.7; }

/* --- the access screens --------------------------------------
   Sign in, verify, invitation, recovery, reset. Nobody is signed
   in, so there is no shell to be inside: the artboards draw one
   centred panel on the background and put the whole screen in it.
   The workbench itself is that panel, which is why these pages
   carry no pane — a pane here would draw a second border around
   the same content. */
.workbench--access {
  width: 100%;
  max-width: var(--content-access);
  margin-inline: auto;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-5);
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
}
.access-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
}
.access-header__title { font-size: var(--text-lg); line-height: var(--leading-tight); }
.access-header__lead {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  line-height: var(--leading-base);
  text-wrap: balance;
}
/* Token-coloured, so the mark follows the theme rather than
   carrying a palette of its own. The dot is the gain green: the
   product has one green and this is it. */
.access-mark { display: block; flex: none; }
.access-mark__plate {
  fill: var(--color-surface-subtle);
  stroke: var(--color-border-strong);
  stroke-width: 1.5;
}
.access-mark__glyph {
  fill: none;
  stroke: var(--color-text);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.access-mark__signal {
  fill: var(--color-gain);
  stroke: var(--color-surface-subtle);
  stroke-width: 0.7;
}
/* The dot reports the last action on these screens, never the day: red is a
   refused sign-in and nothing else. Only the failure takes a colour -- green
   would be a reading this screen cannot make, since nobody has tried yet. The
   rail's mark is a different component on pages these screens cannot reach. */
.access-mark--fail .access-mark__signal { fill: var(--color-loss); }
/* Recovery is a rescue, not a way in. Three controls of the card's width would
   make three equal choices out of one way in, so this is a line under the two
   that are. The caption size is the one the card's other quiet text uses. */
.access-link {
  display: block;
  margin-top: var(--space-2);
  text-align: center;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* Promoted in place after a refusal: same words, same position, more weight.
   The reader who has just been refused is the one who needs this, and a new
   line arriving beneath them would be the card changing shape while they read
   it. */
.access-link--promoted {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text);
}
/* Two windows, one document. An installed application and a browser tab are
   served the same HTML -- display-mode is the only thing that tells them apart
   and it never reaches the server -- so anything that differs between them is
   rendered twice and the window chooses.

   The first use is the emailed sign-in link, which cannot work inside an
   installed application on iOS: it holds its own cookie store, so the link
   signs the BROWSER in and leaves the application's sign-in screen untouched,
   silently, because the second request is inside a cooldown and answers
   identically to the first.

   The browser is the default in both rules rather than the standalone case
   being the default, so a browser too old to understand display-mode gets the
   offer -- which is a browser where the link works. */
.installed-only { display: none; }
@media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui) {
  .browser-only { display: none; }
  .installed-only { display: block; }
}
/* One primary, one quiet beneath it, both the width of the card.
   Anywhere else a full-width button claims a row it has not
   earned; here the card is the page. */
.btn--full { width: 100%; }
.field__help--centred { text-align: center; text-wrap: balance; }
/* A one-time code: wide, centred, evenly tracked. The artboard
   draws six boxes; one field with the digits spaced reads the
   same and still accepts a paste. */
.field__input--code {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.35em;
  text-indent: 0.35em;
}

/* --- password strength ---------------------------------------
   Red until the password is long enough, green after. Width
   carries the rest: long enough is two thirds, long and mixed is
   full. Colour is never the only signal — the reading beside the
   bar says the same thing in words, and it is a live region so
   it is not only visual. */
.strength { display: flex; flex-direction: column; gap: var(--space-2); }
/* An author display beats the user agent's [hidden], and without this the
   inert bar would show to everyone whose script did not run. */
.strength[hidden] { display: none; }
.strength__track {
  height: var(--accent-bar-w);
  border-radius: var(--radius-pill);
  background: var(--color-surface-subtle);
  border: var(--border-w) solid var(--color-border);
  overflow: hidden;
}
.strength__fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  transition: width var(--ease), background-color var(--ease);
}
.strength[data-strength-level="short"] .strength__fill {
  width: 30%;
  background: var(--color-loss);
}
.strength[data-strength-level="met"] .strength__fill {
  width: 66%;
  background: var(--color-gain);
}
.strength[data-strength-level="strong"] .strength__fill {
  width: 100%;
  background: var(--color-gain);
}
.strength__reading { font-size: var(--text-xs); color: var(--color-text-secondary); }
.strength[data-strength-level="short"] .strength__reading { color: var(--color-loss); }
.strength[data-strength-level="met"] .strength__reading,
.strength[data-strength-level="strong"] .strength__reading { color: var(--color-gain); }
.strength__rules { display: flex; flex-direction: column; gap: var(--space-1); }
.strength__rule {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-xs);
}
.strength__rule-label { color: var(--color-text-secondary); }
.strength__rule-value { color: var(--color-text-muted); }
.strength__rule[data-strength-met="true"] .strength__rule-value { color: var(--color-gain); }
@media (prefers-reduced-motion: reduce) { .strength__fill { transition: none; } }
/* --- the headline swap ---------------------------------------
   One lead metric at a time. Value answers "how much is there",
   return answers "how is it doing" — the same tile, two
   questions, and which one you want depends on the day. The
   page ships showing value, so a reader with no script or no
   stored preference still sees a complete tile. */
/* Only ever hide. Naming a display for the half that is showing means
   guessing what each element is -- it said `block`, then `inline` for spans,
   and the lead tile is neither: it is a grid whose three rows line up with
   the tiles beside it. Told to be a block it laid its label, figure and
   footnote out on one line. The chosen half keeps whatever display it has,
   so a new kind of element can be swapped without another special case. */
body:not([data-headline="return"]) .as-return { display: none; }
body[data-headline="return"] .as-value { display: none; }
/* ...and which tile leads changes with it. On Value the lead is what the
   portfolio is worth; on Return the owner's decision of 20 September 2026 is
   Today, Year to date, then the return since the first trade -- so the return
   face goes to the end of the row and Today takes the column it left. Order
   rather than markup, because both faces are in the document at once and the
   press that swaps them cannot re-render either (issue 789). The row is a
   three-column grid whose first column is the wider one, so moving the tile
   moves the emphasis with it and nothing here restates a width.

   The lead's figure steps up, so the step moves too. Both halves are needed:
   without the second, a row led by Today would carry two stepped-up figures. */
body[data-headline="return"] .summary-group--lead > .as-return { order: 1; }
body[data-headline="return"] .summary-item--leads-on-return .summary-item__value {
  font-size: var(--text-figure-lead);
}
body[data-headline="return"] .summary-item--primary.as-return .summary-item__value {
  font-size: var(--text-figure);
}
/* A range the return cannot be read over leaves the row rather than staying
   pressable and answering with a sentence about why nothing is drawn. One day
   holds one stored session and a return is measured between two, so the pill
   is an offer the page cannot keep. It is hidden here rather than by the
   script so that a reader whose stored headline is Return never sees it paint;
   chart-factory.js moves the press to the next range along. */
body[data-headline="return"] [data-chart-range-value-only] { display: none; }

/* --- motion --------------------------------------------------
   The canvas's micro-interactions on the application's class
   names. Three durations and one curve from tokens.css; nothing
   here writes its own timing. A control acknowledges a press at
   --t-fast, a state changes at --t-base, and something arrives
   at --t-slow. One reduced-motion block at the end answers for
   all of it, so a new rule has one place to be let out. */

/* Controls: colour and a press. The rail item gives a little
   more because it is the largest target on the page. */
.btn, .segmented__item, .iconbtn, .rail__item {
  transition:
    background-color var(--t-fast) var(--ease-out),
    border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out);
}
.btn:active, .segmented__item:active, .iconbtn:active { transform: scale(0.97); }
.rail__item:active { transform: scale(0.94); }

/* Surfaces that change state under the pointer or a selection:
   a row lighting up, a pane taking focus. */
.table tbody tr { transition: background-color var(--t-fast) var(--ease-out); }
.pane { transition: border-color var(--t-base) var(--ease-out); }
.metric-row { transition: background-color var(--t-fast) var(--ease-out); }

/* The sort indicator fades at --t-fast and turns at --t-base. The
   arrows are text, so no rotation is written here: a turned "↑"
   is not a "↓". */
.table-sort__indicator {
  transition: opacity var(--t-fast) var(--ease-out), transform var(--t-base) var(--ease-out);
}

/* Icons move; labels do not. The phone's section bar takes the
   press but not the hover, because under a finger hover is only
   the last tap, stuck. The mark is the one icon that never
   reacts: a logo is not a control. */
.rail__icon, .section-nav__icon, .iconbtn .glyph {
  transition:
    transform var(--t-base) var(--ease-out),
    stroke-width var(--t-base) var(--ease-out),
    opacity var(--t-fast) var(--ease-out);
}
.rail__item:hover .rail__icon { transform: translateY(-0.0625rem) scale(1.06); }
.rail__item:active .rail__icon,
.section-nav a:active .section-nav__icon { transform: translateY(0) scale(0.94); }
.iconbtn:hover .glyph { transform: rotate(-6deg) scale(1.08); }
.iconbtn:active .glyph { transform: scale(0.92); }
.rail__mark, .rail__mark * { transition: none; }

/* Financial figures, charts and navigation counts paint immediately.
   Entrance fades would conceal complete data after the request finishes. */

/* The moment between the press and the result.

   A row action is one named verb in its own column and it posts a form, so
   the press and the page that answers it are separated by a request nothing
   on screen admitted to. The form already goes aria-busy and its submit
   already disables — .btn[disabled] is the board's opacity and its pointer
   lock, already in this sheet — so what was missing is the mark that says
   the request is running, and the width that stops the column moving while
   it does.

   The mark is sized in em rather than a token on purpose: no token names a
   small round mark inside a control, and the two that carry the board's 14
   are a chart legend key and a small button's inline padding. Tying it to
   the label's own type keeps it right on both button sizes.

   --pending-w is written by app-shell.js from the button's measured width
   before anything else changes, so the button keeps the box it had and the
   row does not move under the pointer. Nothing here removes the state: the
   form navigates, and the answer is the page that arrives. */
.spin {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  border: var(--outline-w) solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: spin 700ms linear infinite;
}
.btn--pending {
  position: relative;
  min-width: var(--pending-w, auto);
}

/* Anyone who asked for less movement gets none. */
@media (prefers-reduced-motion: reduce) {
  .btn, .segmented__item, .iconbtn, .rail__item,
  .table tbody tr, .pane, .metric-row, .table-sort__indicator, .toast,
  .rail__icon, .section-nav__icon, .iconbtn .glyph { transition: none; }
  /* The gap in the ring is only there to make the turn visible. With no turn
     it closes, and the same mark waits as a plain circle. */
  .spin { animation: none; border-top-color: currentColor; }
}

/* --- installed application ----------------------------------
   Installed, not visited: no URL bar and no browser back button.

   Nothing here reserves room for the status bar or the home
   indicator, and that is the decision rather than an omission. The
   viewport is deliberately contained -- see the note beside the
   viewport meta in base.html -- so the operating system insets the
   web view and paints those bands itself, from theme-color, which
   is the same ground the title bar under it is painted from. The
   page is never given that room and so has none to give back.

   This block held two padding declarations written for the other
   choice. Both added env(safe-area-inset-top), which is zero without
   viewport-fit=cover, so each one restated the padding its element
   already had; issue 584 removed them. They come back with the
   opt-in, the artboard and the theme-color note, or not at all.

   Written without the issue's number sign on purpose: design-lint
   reads a comment the way it reads a declaration, and a three-digit
   issue reference behind a hash is a raw hex colour to it. */
/* The pull that reloads the page, in the installed application. Owner decision
   of 18 September 2026, option A: it re-renders what the product already holds
   and does not go to the provider. Drawn on `PhonePull`, and this is that
   drawing in the product. Issue 680, and the block above says why the
   number is written without its hash.

   The band opens under the title bar and the page does not move. That is the
   whole of "it must read as a pull and not as the page being dragged" -- a
   frame that slides is a page being dragged, and a frame that stays put with a
   band opening beneath it is a pull.

   `pwa.js` sets three things on the body and this reads all three:

     data-pull            pulling | ready | refreshing | refused
     data-pull-refused    typing | sending | unread, only beside `refused`
     --pull-progress      unitless zero to one, following the finger

   One is the threshold rather than the maximum. A longer pull clamps, because
   past the threshold nothing further is being decided and a band that kept
   growing would say otherwise.

   Standalone only. On the web the browser has a reload button and its own
   gesture, and this would be a second one competing with it. */
.pull { display: none; }

@media (display-mode: standalone) {
  /* The browser's own pull is refused here so that ours can answer in its
     place. The reason this declaration used to give -- that there is no
     address bar to type the address back into -- does not survive: a reload
     in standalone reloads the current address, and there is nothing to type.

     The real reason it is here is that the browser's pull has no guards. It
     reloads whatever the page was in the middle of: an entry half typed, an
     entry still on its way, a report nobody has read. Those are the three
     things pwa.js refuses a pull for, and dropping this would hand the
     gesture back to the browser with none of them -- no guards, no threshold
     and nothing drawn. Issue 680 built the replacement; this is what makes
     room for it. */
  html, body { overscroll-behavior-y: contain; }

  [data-pull] .pull {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    overflow: hidden;
    height: calc(var(--pull-progress, 0) * var(--pull-open));
    background: var(--color-surface);
    border-block-end: var(--border-w) solid var(--color-border);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
  }

  /* Past the threshold the band stops growing, and the arrow turns over --
     which is the only thing that tells a reader the gesture has taken. */
  [data-pull="ready"] .pull,
  [data-pull="refreshing"] .pull,
  [data-pull="refused"] .pull { height: var(--pull-open); }
  [data-pull="ready"] .pull__arrow { transform: rotate(180deg); }

  /* One glyph and one sentence at a time. Drawn as three glyphs and six
     sentences in the markup rather than assembled here, so the copy stays
     where a reader, a translator and a test can all reach it. */
  .pull__spin, .pull__stop, .pull__say { display: none; }
  [data-pull="refreshing"] .pull__arrow,
  [data-pull="refused"] .pull__arrow { display: none; }
  [data-pull="refreshing"] .pull__spin { display: inline-flex; }
  [data-pull="refused"] .pull__stop { display: inline-flex; }

  [data-pull="pulling"] .pull__say[data-say="pulling"],
  [data-pull="ready"] .pull__say[data-say="ready"],
  [data-pull="refreshing"] .pull__say[data-say="refreshing"],
  [data-pull-refused="typing"] .pull__say[data-say="typing"],
  [data-pull-refused="sending"] .pull__say[data-say="sending"],
  [data-pull-refused="unread"] .pull__say[data-say="unread"] { display: inline; }
}

/* The application asking to be installed, once. It shares the toast's
   corner and its resting styling because both are the frame speaking
   rather than the page -- but this one waits for an answer, so it
   does not fade and it carries its own two controls. */
.install-bar {
  position: fixed;
  z-index: 40;
  inset-inline: var(--margin);
  bottom: var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-pane);
}
/* The display above beats the user agent's [hidden]. Without this the bar
   asks every reader on every page, which is the opposite of asking once. */
.install-bar[hidden] { display: none; }
.install-bar__text {
  font-size: var(--text-sm);
  margin-inline-end: auto;
}
.install-bar__actions {
  display: flex;
  gap: var(--space-2);
  flex: 0 0 auto;
}

/* Wide enough for the bar to sit in one corner rather than span the
   page: a full-width band on a desktop reads as an alert. */
@media (min-width: 48.0625rem) {
  .install-bar { inset-inline: auto var(--margin); }
}

/* Clear of the bottom section bar, which owns the foot of a phone. */
@media (max-width: 48rem) {
  .install-bar { bottom: calc(var(--space-7) + var(--space-3)); }
}

/* --- a row's secondary actions ---------------------------------
   Export, duplicate and archive are real and none of them is what
   a reader came to do. As four equal buttons they made two of them
   ("Customize", "Edit saved recipe") indistinguishable from each
   other; here the two verbs lead the body and the rest cost a
   glyph. <details> rather than script: no overlay in this
   application is opened by JavaScript, so a menu that waited for
   one would never open. */
.row-menu { position: relative; display: inline-flex; }
.row-menu > summary { list-style: none; cursor: pointer; }
.row-menu > summary::-webkit-details-marker { display: none; }
/* In the top layer now, not positioned against the row: `.table-wrap` is a
   scroll container on both axes, and an absolutely positioned sheet was
   clipped by it. `position-area` anchors the panel to the button that opened
   it -- `popovertarget` makes that the implicit anchor -- and the UA sheet's
   centring inset is released so the area can place it, the same three lines
   `.hint__panel` needs for the same reason.

   A browser without anchor positioning does NOT centre it. This said it did,
   and it is wrong for the reason already recorded on `.hint__panel`: the
   `inset: auto` two lines below is what releases the UA sheet's centring, and
   it is not gated on anchor positioning. Such a browser applies the release,
   ignores `position-area`, and is left with a sheet the UA no longer centres
   and nothing else places. Still not clipped -- that was what moving it to
   the top layer bought -- but not centred either, so "worse placement" was
   the wrong description of a panel with no placement at all.

   Not fixed here, deliberately, for the reason given on `.hint__panel`:
   nobody here can run a browser without anchor positioning to see where the
   sheet actually lands, and shipping an untested `@supports` fallback on top
   of a measured fix trades one unverified path for another. Issue 904 is the
   record, and it carries the screenshot that would settle it.

   Spelled "904" rather than with a hash because run-design-lint reads a
   three-digit hash as a raw hex colour, which it is. */
.row-menu__sheet {
  inset: auto;
  position-area: block-end span-inline-start;
  position-try-fallbacks: flip-block, flip-inline;
  margin: var(--space-2) 0 0 0;
  display: flex;
  flex-direction: column;
  min-inline-size: 13rem;
  padding: var(--space-1);
  background: var(--color-surface);
  /* No drop shadow: this sheet lifts off the pane with a strong border and an
     opaque surface, the way every other raised thing here does. Nothing in
     this stylesheet casts a shadow, and one exception would be the whole
     system's first. */
  border: var(--border-w) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
}
/* The closed sheet, restated. The UA sheet hides a closed popover with
   `[popover]:not(:popover-open) { display: none }`, and the `display` above is
   an author declaration, so it wins and every menu in the product is drawn
   open -- a table of rows each carrying one is what the owner photographed.
   The line survived the move off `<details>`, where it was only ever read
   inside an open one.

   `[popover]` and not the bare class: the phone's portfolio scope picker is
   still a `<details>` wearing this sheet, so the column above has to stay on
   the class or its links run across the row instead of down it. */
.row-menu__sheet[popover]:not(:popover-open) { display: none; }
.row-menu__item {
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.row-menu__item:hover,
.row-menu__item:focus-visible { background: var(--color-surface-selected); }
.row-menu__note {
  margin: 0;
  padding: 0 var(--space-3) var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* One condition is one row: the metric, how it compares, the number,
   and a way to drop it. Stacked into a block with its own legend it
   cost 175 vertical pixels each, and three conditions filled more of
   the form than the rest of the form did. The basis under the row is
   the definitions pane that used to stand beside it. */
.condition-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(6rem, 1fr) auto;
  gap: var(--space-2) var(--space-3);
  align-items: end;
}
.condition-row > [data-recipe-metric-help] {
  grid-column: 1 / -1;
  margin: 0;
}
@media (max-width: 40rem) {
  .condition-row { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
}

/* The run's identity beside the controls that act on it, rather than
   as a fourth button. */
.pane--focus .stack > .toolbar__group > .toolbar__meta {
  margin-inline-start: auto;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* Controls and badges keep their width; the label wraps beside them. */
.metric-row__value:has(.btn, .badge) { flex: 0 0 auto; }

/* The definition and the cookbook stand in one row of the split, so the
   shorter of the two is stretched to the taller. Its controls belong at the
   foot of the card it is in rather than floating in the middle of the slack,
   which is where the artboard puts them. */
.workbench-layout--split .pane--focus > .pane__body { display: flex; flex-direction: column; }
.workbench-layout--split .pane--focus > .pane__body > .stack { flex: 1 1 auto; }
.workbench-layout--split .pane--focus .stack > .toolbar__group { margin-block-start: auto; }
/* ...and the pane itself has to be a column for that body to grow into the
   height the row gave it. */
.workbench-layout--split .pane--focus { display: flex; flex-direction: column; }
.workbench-layout--split .pane--focus > .pane__body { flex: 1 1 auto; }

/* ---- insight figure --------------------------------------
   The picture on an Insights card. Three shapes, one component: lines for
   paths that are being compared with each other, bars for one holding's two
   shares of the same whole, and a split for parts of a total.

   Deliberately small and axis-less. A card states its numbers in the rows
   beneath, and the figure's job is the shape of the thing — four lines that
   rise and fall together, a value bar shorter than its variance bar. Where a
   figure would have to be read against a scale to mean anything it is the
   wrong component, and the pane's chart is the right one. */
.insight-figure { display: block; width: 100%; min-width: 0; }
.insight-figure__plot {
  display: block;
  width: 100%;
  height: var(--chart-height-tile);
  overflow: visible;
}
.insight-figure__line {
  fill: none;
  stroke: var(--color-text-muted);
  stroke-width: var(--outline-w);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.insight-figure__line.gain { stroke: var(--color-gain); }
.insight-figure__line.loss { stroke: var(--color-loss); }
.insight-figure__line--dashed { stroke-dasharray: 5 4; }
.insight-figure__plot--bars, .insight-figure__plot--split { height: 3rem; }
.insight-figure__track { fill: var(--color-surface-subtle); }
.insight-figure__fill { fill: var(--color-text-muted); }
.insight-figure__fill--lead { fill: var(--color-text); }
/* A split is parts of one total, so the parts are one ink at falling
   weights rather than five hues a reader has to decode from a legend. */
.insight-figure__segment { fill: var(--color-text-muted); }
.insight-figure__segment--1 { opacity: 1; }
.insight-figure__segment--2 { opacity: 0.78; }
.insight-figure__segment--3 { opacity: 0.6; }
.insight-figure__segment--4 { opacity: 0.46; }
.insight-figure__segment--5 { opacity: 0.34; }
.insight-figure__segment--6 { opacity: 0.24; }

/* Conversation and ticket rows — approved Support artboards, issue 1033. */
.conversation-entry { padding-block-end: var(--space-4); border-block-end: var(--border-w) solid var(--color-border); min-inline-size: 0; }
.conversation-entry__header { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); align-items: baseline; font-size: var(--text-xs); }
.conversation-entry__header > span { margin-inline-start: auto; color: var(--color-text-muted); }
.conversation-entry__body { white-space: pre-wrap; overflow-wrap: anywhere; margin-block: var(--space-3); }
.attachment-list { list-style: none; padding: 0; margin-block: var(--space-3); }
.attachment-list > li { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-block: var(--space-2); font-size: var(--text-xs); border-block-end: var(--border-w) solid var(--color-border); }
.attachment-list a { overflow-wrap: anywhere; min-inline-size: 0; }
.attachment-list span { color: var(--color-text-muted); }
.ticket-item { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-4) var(--space-5); border-block-end: var(--border-w) solid var(--color-border); }
.ticket-item:last-child { border-block-end: 0; }
.ticket-item__copy { display: flex; flex: 1 1 var(--workbench-rail); flex-direction: column; gap: var(--space-2); min-inline-size: 0; }
.ticket-item__title { font-weight: 600; overflow-wrap: anywhere; }
.ticket-item__meta { color: var(--color-text-muted); font-size: var(--text-xs); overflow-wrap: anywhere; }
.ticket-item__status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.ticket-item__unread { font-size: var(--text-xs); }

/* Small descriptive facts beside a conversation, using the existing list. */
.metric-list--compact { gap: var(--space-3); margin-block-end: var(--space-4); }
.metric-list--compact .metric-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); padding: 0; border: 0; }
.metric-list--compact .metric-row__label, .metric-list--compact .metric-row__value { font-size: var(--text-xs); font-weight: 400; }
.metric-list--compact .metric-row__value { text-align: end; overflow-wrap: anywhere; }

/* ---- Ideas: kinds, chips and cards ------------------------------------
   Every idea belongs to one kind, and the kind is carried three ways at once
   -- its name, its glyph and its colour -- so no reader depends on the hue.
   The modifier sets --idea; the chip, the tile and the card's top rule read
   it. */
.idea--quality {
  --idea: var(--idea-quality);
}
.idea--value {
  --idea: var(--idea-value);
}
.idea--growth {
  --idea: var(--idea-growth);
}
.idea--income {
  --idea: var(--idea-income);
}
.idea--safety {
  --idea: var(--idea-safety);
}
.idea--analysts {
  --idea: var(--idea-analysts);
}
.idea--defensive {
  --idea: var(--idea-defensive);
}
.idea--funds {
  --idea: var(--idea-funds);
}
.idea--retained {
  --idea: var(--color-text-muted);
}
.idea-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.idea-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--badge-height);
  padding: 0 var(--badge-pad);
  border: var(--border-w) solid color-mix(in srgb, var(--idea, var(--color-text-secondary)) 40%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--idea, var(--color-text-secondary)) 13%, transparent);
  color: var(--idea, var(--color-text-secondary));
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.idea-chip .glyph { width: var(--badge-glyph); height: var(--badge-glyph); }
.idea-chip[aria-current] { outline: var(--border-w) solid var(--idea); }
.idea-chip__n { opacity: 0.75; }
.idea-chip--plain {
  --idea: var(--color-text-secondary);
  background: transparent;
  border-color: var(--color-border-strong);
}
.idea-tile {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--idea) 15%, transparent);
  color: var(--idea);
}
.idea-section { display: flex; flex-direction: column; gap: var(--space-3); }
.idea-section__head { display: flex; align-items: center; gap: var(--space-3); margin: 0; font-size: var(--text-lg); }
.idea-section__meta { color: var(--color-text-muted); font-size: var(--text-xs); font-weight: 400; }
.idea-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr)); gap: var(--space-4); }
.idea-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: var(--border-w) solid var(--color-border);
  border-top: var(--accent-bar-w) solid var(--idea);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}
.idea-card__head { display: flex; align-items: flex-start; gap: var(--space-3); }
.idea-card__title { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.idea-card__name { margin: 0; font-size: var(--text-lg); }
.idea-card__plain { margin: 0 0 var(--space-1); }
.idea-card__details > summary {
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.idea-card__details[open] > summary { margin-block-end: var(--space-1); }
.idea-card__checks { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding-left: var(--space-4); color: var(--color-text-muted); font-size: var(--text-xs); }
.idea-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: var(--border-w) solid var(--color-border);
}
.idea-card__count { font-size: var(--text-lg); font-weight: 700; }
.idea-card__foot > :last-child { flex-shrink: 0; white-space: nowrap; }
.idea-card .idea-chip { white-space: normal; }
