/* ============================================================================
   Design tokens — the one file that makes desktop and phone the same product.

   Desktop and phone are two designed experiences, not one layout at two widths
   (docs/06_APP_SPEC.md section 6). What they share is THIS: colour, type scale,
   spacing, and the semantic meanings below. A band that is amber on desktop is
   never blue on mobile, and gain is never a different green in two places.

   Layouts differ freely. Identity does not.
   ============================================================================ */

:root {
    /* neutrals */
    --bg:            #0f1115;
    --surface:       #171a21;
    --surface-2:     #1e222b;
    --border:        #2a2f3a;
    --text:          #e6e9ef;
    --text-dim:      #9aa3b2;
    --text-faint:    #6b7484;

    /* semantic — meaning, not decoration */
    --gain:          #3fb950;
    --loss:          #f85149;
    --warn:          #d29922;
    --accent:        #58a6ff;
    --accent-dim:    #1f6feb;

    /* the four bands, cool to warm; the order IS the risk ordering (09 §2).
       Renamed 2026-08-25 with the vocabulary — same colours, so nothing about the
       pages' identity moved when the beta bands were retired. --band-none is the
       "Not banded" slice: a refusal is an absence, not a fifth risk level. */
    --band-core:      #5b8def;
    --band-balanced:  #22b8a0;
    --band-growth:    #b48cff;
    --band-satellite: #ff8a5b;
    --band-none:      #6b7484;

    /* comparison series — up to three portfolios on one chart. Distinct from the
       semantic colours above: a series colour identifies WHICH portfolio, never
       whether something is good, and the benchmark stays the dashed dim line. */
    --series-1: #58a6ff;
    --series-2: #d2a8ff;
    --series-3: #39c5cf;
    /* The two index funds the portfolio pages compare against, by name, so a chart
       macro and a legend swatch cannot pick different series colours for the same
       fund. VOO is the third series and QQQ the second; the account is always the
       first. Never a semantic colour: a benchmark is neither good nor bad. */
    --series-voo: var(--series-3);
    --series-qqq: var(--series-2);

    /* type — ONE scale, defined once.
       Only 400/500/600/700 are ever requested: those are the weights the system
       stacks below actually ship. Asking for 650 makes Segoe UI synthesise a face,
       which is most of why the type used to read as "off" — and a second :root
       further down the file quietly redefining this scale is how that 650 survived
       a scrub that was supposed to remove it. Add weights here or nowhere.

       Line heights tighten as size grows; big text needs less leading, and a flat
       1.5 everywhere made headings gappy. */
    --font: "Segoe UI Variable Text", "Segoe UI", -apple-system, ui-sans-serif,
            Roboto, Helvetica, Arial, sans-serif;
    --font-display: "Segoe UI Variable Display", "Segoe UI", -apple-system,
            ui-sans-serif, Roboto, Helvetica, Arial, sans-serif;
    --mono: "Cascadia Mono", Consolas, "SF Mono", ui-monospace, Menlo, monospace;

    --fs-xs:  0.75rem;   --lh-xs:  1.45;
    --fs-sm:  0.8125rem; --lh-sm:  1.5;
    --fs-md:  0.9375rem; --lh-md:  1.55;
    --fs-lg:  1.125rem;  --lh-lg:  1.35;
    --fs-xl:  1.5rem;    --lh-xl:  1.25;
    --fs-xxl: 2rem;      --lh-xxl: 1.15;
    /* The hero value only. Bigger than --fs-xxl on purpose and capped so it never
       wraps a currency figure onto two lines. */
    --fs-hero: clamp(2.25rem, 6vw, 3.5rem);

    --w-normal: 400; --w-medium: 500; --w-semi: 600; --w-bold: 700;

    /* spacing, radii, elevation */
    --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem;
    --sp-5: 1.5rem;  --sp-6: 2rem;   --sp-8: 3rem;
    --r-sm: 6px; --r-md: 10px; --r-lg: 14px;
    --shadow: 0 1px 3px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.24);

    /* touch targets — 44px is the minimum a thumb hits reliably */
    --tap: 44px;
}

/* Light is the OS's choice unless the person has chosen: the toggle in the nav stamps
   data-theme on <html> (kept in localStorage), and a stamp wins both ways. */
@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --bg: #f6f7f9; --surface: #ffffff; --surface-2: #f0f2f5;
        --border: #d8dce3; --text: #1c2027; --text-dim: #5b6472; --text-faint: #8a93a2;
        --gain: #1a7f37; --loss: #cf222e; --warn: #9a6700;
        --accent: #0969da; --accent-dim: #0550ae;
        --series-1: #0969da; --series-2: #8250df; --series-3: #1b7c83;
        --band-core: #3b6fd6; --band-balanced: #178f7c; --band-growth: #7e57d6; --band-satellite: #d9683a;
        --shadow: 0 1px 2px rgba(0,0,0,.06), 0 8px 24px rgba(0,0,0,.08);
    }
}
:root[data-theme="light"] {
        --bg: #f6f7f9; --surface: #ffffff; --surface-2: #f0f2f5;
        --border: #d8dce3; --text: #1c2027; --text-dim: #5b6472; --text-faint: #8a93a2;
        --gain: #1a7f37; --loss: #cf222e; --warn: #9a6700;
        --accent: #0969da; --accent-dim: #0550ae;
        --series-1: #0969da; --series-2: #8250df; --series-3: #1b7c83;
        --band-core: #3b6fd6; --band-balanced: #178f7c; --band-growth: #7e57d6; --band-satellite: #d9683a;
        --shadow: 0 1px 2px rgba(0,0,0,.06), 0 8px 24px rgba(0,0,0,.08);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: var(--fs-md);
    line-height: var(--lh-md);
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    /* clears the notch and home indicator on iPhone */
    padding: env(safe-area-inset-top) env(safe-area-inset-right)
             env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* Every figure, everywhere: same stack, same numerals, aligned columns. Tabular,
   lining numerals mean a 1 is as wide as an 8 — not cosmetic in a portfolio
   table, where ragged digits make two numbers of the same magnitude look
   different sizes. */
.num, .fvalue, .dvalue, .kvalue, .headline-number, .metrics dd, td.num, th.num {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
}

h1 { font-family: var(--font-display); font-size: var(--fs-xl);
     line-height: var(--lh-xl); font-weight: var(--w-semi);
     letter-spacing: -0.015em; margin: 0 0 var(--sp-3); }
h2 { font-weight: var(--w-semi); letter-spacing: -0.005em; }
strong, b { font-weight: var(--w-semi); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Motion is meaning here, never decoration: state changes get a transition so a
   hover or a selection reads as a change rather than a repaint. Anyone who has
   asked the OS for less motion gets none of it. */
a, button, .btn, .segbar a, .navlink {
    transition: background-color .15s ease, color .15s ease,
                border-color .15s ease, opacity .15s ease;
}
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important; animation-iteration-count: 1 !important;
        transition-duration: .01ms !important; scroll-behavior: auto !important;
    }
}

/* One visible focus ring for every interactive thing, including links. Removing
   focus rings is the single most common way a keyboard user loses their place. */
a:focus-visible, [tabindex]:focus-visible, summary:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
    border-radius: var(--r-sm);
}

.wrap { max-width: 1400px; margin: 0 auto; padding: var(--sp-4); }

/* ---- navigation ----------------------------------------------------------
   The bar was a wordmark and one bare link, which left no way to tell where you
   were or to get back to the list except by guessing that the wordmark was a
   link. Nav items now carry an active state, because "current location must be
   visually highlighted" is the one navigation rule a data tool cannot skip:
   every page here looks like every other page at a glance. */

header.top {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    position: sticky; top: 0; z-index: 30;
}
header.top .brand {
    font-family: var(--font-display); font-weight: var(--w-semi);
    letter-spacing: -0.01em; font-size: var(--fs-md);
    margin-right: var(--sp-3);
}
header.top .brand a { color: var(--text); }
header.top .brand a:hover { text-decoration: none; color: var(--accent); }
header.top .spacer { flex: 1; }

.navlink {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    min-height: var(--tap); padding: 0 var(--sp-3);
    border-radius: var(--r-sm); color: var(--text-dim);
    font-size: var(--fs-sm); font-weight: var(--w-medium);
    border: 1px solid transparent;
}
.navlink:hover { text-decoration: none; background: var(--surface-2); color: var(--text); }
.navlink[aria-current="page"] {
    color: var(--text); background: var(--surface-2); border-color: var(--border);
}
.navlink svg { width: 16px; height: 16px; flex: none; }

/* DEMO MODE, in the nav. Amber rather than red — it is a state to be aware of, not a
   fault — and loud enough that it cannot be left on by accident, which is the entire
   job of it. Never printed: `report.css` hides `header.top` on paper, and the sheet
   carries its own banner in the body. */
.navdemo {
    display: inline-flex; align-items: center; min-height: var(--tap);
    padding: 0 var(--sp-3); margin-right: var(--sp-2);
    border-radius: var(--r-sm); border: 1px solid var(--warn);
    background: color-mix(in srgb, var(--warn) 14%, transparent);
    color: var(--warn); font-size: var(--fs-sm); font-weight: var(--w-semi);
    font-family: var(--mono); white-space: nowrap;
}
.navdemo:hover { text-decoration: none;
                 background: color-mix(in srgb, var(--warn) 26%, transparent); }
header.top nav { display: flex; align-items: center; gap: var(--sp-1); }
/* On a phone the label is clipped rather than removed: the icon stays visible and
   the link keeps its accessible name, so a screen reader still announces
   "Portfolios" where a sighted user sees only the glyph. `display:none` here would
   have silently made the nav icon-only for everyone. */
.vis-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
@media (max-width: 599px) {
    header.top nav .navlink { padding: 0 var(--sp-3); }
    header.top nav .navlink svg { width: 20px; height: 20px; }
    header.top nav .navlabel {
        position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
        overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
}

/* Breadcrumb: a portfolio page is two levels deep and used to say so nowhere. */
.crumbs {
    display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
    font-size: var(--fs-sm); color: var(--text-faint);
    margin-bottom: var(--sp-3);
}
.crumbs a { color: var(--text-dim); }
.crumbs .sep { color: var(--text-faint); }

.card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: var(--sp-4); box-shadow: var(--shadow);
}

.grid { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 900px)  { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1300px) { .grid { grid-template-columns: repeat(3, 1fr); } }

.muted { color: var(--text-dim); }
.tiny  { font-size: var(--fs-sm); color: var(--text-dim); }
.num   { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.gain  { color: var(--gain); }
.loss  { color: var(--loss); }

.tag {
    display: inline-block; padding: 2px var(--sp-2); border-radius: 999px;
    font-size: var(--fs-xs); border: 1px solid var(--border); color: var(--text-dim);
}
.tag.model    { border-color: var(--warn); color: var(--warn); }
.tag.weights  { border-color: var(--accent); color: var(--accent); }

/* Controls are sized for a thumb everywhere. A 44px target is not a mobile
   concession — it is simply not annoying with a mouse either. */
button, .btn, input, select {
    font: inherit; min-height: var(--tap);
    border-radius: var(--r-sm); border: 1px solid var(--border);
    background: var(--surface-2); color: var(--text); padding: 0 var(--sp-3);
}
button, .btn {
    cursor: pointer; display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: 0 var(--sp-4);
}
button.primary, .btn.primary {
    background: var(--accent-dim); border-color: var(--accent-dim); color: #fff;
}
input:focus, button:focus-visible, select:focus {
    outline: 2px solid var(--accent); outline-offset: 1px;
}

/* Wide content scrolls inside its own container. The page body never scrolls
   sideways — on a phone that is the difference between usable and not. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: var(--sp-2) var(--sp-3);
         border-bottom: 1px solid var(--border); white-space: nowrap; }
th { font-size: var(--fs-sm); color: var(--text-dim);
     font-weight: var(--w-semi); letter-spacing: 0.01em; }
td.num, th.num { text-align: right; }

.flash { padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm);
         margin-bottom: var(--sp-4); border: 1px solid var(--border); }
.flash.error { border-color: var(--loss); color: var(--loss); }
.flash.ok    { border-color: var(--gain); color: var(--gain); }
.flash.warn  { border-color: var(--warn); color: var(--warn); }

.stack  { display: flex; flex-direction: column; gap: var(--sp-3); }
.row    { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

form.narrow { max-width: 420px; margin: var(--sp-8) auto; }
form.narrow input { width: 100%; margin-bottom: var(--sp-3); }

/* ============================================================================
   Stage D — the portfolio view.

   Two designed layouts, one identity (docs/06_APP_SPEC.md section 6). The
   desktop rules below are NOT the phone rules with more room: the hero goes
   three-across, the holdings table shows every column, and the charts sit side
   by side. The phone rules are written first and the desktop ones added at a
   breakpoint, but nothing desktop is capped by what fits on a phone.

   Breakpoint is 720px. Below it, one column and tap-to-read. Above, comparison
   work: aligned rows, dense tables, legends and hover readouts.
   ============================================================================ */

/* ---- the hurdle hero: one number owns the page ---------------------------- */

.hero {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-lg); padding: var(--sp-5);
    box-shadow: var(--shadow); margin-bottom: var(--sp-4);
}
.hero .eyebrow {
    font-size: var(--fs-xs); letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-dim); margin-bottom: var(--sp-2);
    font-weight: var(--w-medium);
}
.hero .headline-number {
    font-family: var(--mono);
    font-size: clamp(2rem, 7vw, 3.25rem); line-height: var(--lh-xxl);
    font-weight: var(--w-semi); letter-spacing: -0.02em;
}
.hero .headline-number.short { color: var(--loss); }
.hero .headline-number.ahead { color: var(--gain); }
.hero .headline-sub { color: var(--text-dim); margin-top: var(--sp-2); }
.hero .headline-sub strong { color: var(--text); font-family: var(--mono); }

/* ---- the portfolio header ------------------------------------------------
   What the account IS comes before how it compares. The benchmark hurdle used
   to be the only large figure on the page, which answered a question ("did you
   beat VOO?") before answering the one everybody actually opens the page with
   ("what have I got, and what did it make?"). The hurdle is still here — it is
   the point of the product — but as one tile among several rather than as the
   whole first screen.

   Rule that survives the redesign: no return appears without its benchmark and
   its as-of date (04 §7). Both live in the strip and the vs-benchmark tile. */

.phead { margin-bottom: var(--sp-3); }
.phead .ptitle { display: flex; align-items: center; gap: var(--sp-3);
                 flex-wrap: wrap; }
.phead .ptitle h1 { margin: 0; }
.phead .pmeta {
    display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3);
    font-size: var(--fs-xs); color: var(--text-faint);
    font-family: var(--mono); margin-top: var(--sp-2);
}
.phead .pmeta .sep { opacity: .5; }

.hero-primary { display: flex; flex-wrap: wrap; align-items: baseline;
                gap: var(--sp-2) var(--sp-4); }
.hero-value {
    font-family: var(--mono); font-size: var(--fs-hero);
    font-weight: var(--w-semi); letter-spacing: -0.025em;
    line-height: var(--lh-xxl); color: var(--text);
}
.hero-delta { font-family: var(--mono); font-size: var(--fs-lg);
              font-weight: var(--w-semi); }
.hero-delta .sub { color: var(--text-faint); font-weight: var(--w-normal);
                   font-size: var(--fs-sm); }
.hero-since { font-size: var(--fs-sm); color: var(--text-dim); margin-top: var(--sp-1); }

/* KPI tiles. Four across on desktop, two on a phone — never one, because a
   single column turns a comparison into a scroll. */
.kpis {
    display: grid; gap: var(--sp-3);
    grid-template-columns: repeat(2, 1fr);
    margin-top: var(--sp-5);
}
@media (min-width: 720px)  { .kpis { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1200px) { .kpis { grid-template-columns: repeat(5, 1fr); } }

.kpi {
    border-top: 1px solid var(--border); padding-top: var(--sp-3);
    min-width: 0;
}
.kpi.primary { border-top-color: var(--accent); border-top-width: 2px; }
.kpi .klabel {
    font-size: var(--fs-xs); color: var(--text-dim); font-weight: var(--w-medium);
    text-transform: uppercase; letter-spacing: 0.06em;
    display: block; margin-bottom: var(--sp-1);
}
.kpi .kvalue {
    font-size: var(--fs-lg); font-weight: var(--w-semi); line-height: var(--lh-lg);
    display: block; overflow-wrap: anywhere;
}
.kpi .kvalue .unit { font-size: var(--fs-sm); color: var(--text-faint);
                     font-weight: var(--w-normal); }
.kpi .ksub { font-size: var(--fs-xs); color: var(--text-faint);
             display: block; margin-top: 2px; }

/* The metric board is read in bands — return, then risk, then how the account is
   run — and an unlabelled wall of fifteen tiles is exactly the "over-complicated"
   failure this redesign was asked to avoid. The band label is what turns it back
   into three short answers instead of one long one. */
.kpirow {
    display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
    margin-top: var(--sp-5);
}
.kpirow .rowlabel {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--text-dim); font-weight: var(--w-semi);
}
.kpirow .rownote { font-size: var(--fs-xs); color: var(--text-faint); }
/* The row label sits directly above its tiles, so the tiles lose the extra gap
   their own margin adds when they follow the hero instead. */
.kpirow + .kpis { margin-top: var(--sp-2); }

/* The deployment sparkbar inside a tile. Same vocabulary as the big cash gauge —
   accent for invested, empty for cash — at tile scale. */
.kpi .gbar {
    height: 5px; border-radius: 999px; background: var(--surface-2);
    overflow: hidden; display: flex; border: 1px solid var(--border);
    margin-top: var(--sp-2);
}
.kpi .gbar .ginvested { background: var(--accent); }

/* ---- allocation donut + cash gauge --------------------------------------- */

.hero-viz {
    display: grid; gap: var(--sp-4); margin-top: var(--sp-5);
    padding-top: var(--sp-4); border-top: 1px solid var(--border);
    grid-template-columns: 1fr;
}
@media (min-width: 900px) { .hero-viz { grid-template-columns: 1fr 1fr; } }

.viz-head {
    display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
    margin-bottom: var(--sp-3);
}
.viz-head .vtitle {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-dim); font-weight: var(--w-medium);
}

.alloc { display: flex; align-items: center; gap: var(--sp-4); }
.alloc .donut-shell { position: relative; width: 132px; height: 132px; flex: none; }
.alloc .alegend { flex: 1; min-width: 0; display: grid; gap: 2px; }
.alloc .alegend .arow {
    display: grid; grid-template-columns: 10px 1fr auto;
    align-items: center; gap: var(--sp-2); font-size: var(--fs-sm);
}
.alloc .alegend .adot { width: 10px; height: 10px; border-radius: 2px; }
.alloc .alegend .aname { color: var(--text-dim); overflow: hidden;
                         text-overflow: ellipsis; white-space: nowrap; }
.alloc .alegend .apct { font-family: var(--mono); font-variant-numeric: tabular-nums;
                        color: var(--text); }

/* The cash gauge. A single bar, labelled at both ends, and NO adjective — idle
   cash is drag when accidental and a risk decision when deliberate, and no
   export can tell them apart (04 §7.4). */
.gauge .gbar {
    height: 12px; border-radius: 999px; background: var(--surface-2);
    overflow: hidden; display: flex; border: 1px solid var(--border);
}
.gauge .gbar .ginvested { background: var(--accent); }
.gauge .gbar .gidle     { background: transparent; }
.gauge .gends {
    display: flex; justify-content: space-between; gap: var(--sp-3);
    margin-top: var(--sp-2); font-size: var(--fs-sm);
}
.gauge .gends .gnum { font-family: var(--mono); font-variant-numeric: tabular-nums;
                      font-weight: var(--w-semi); display: block; }
.gauge .gends .glabel { color: var(--text-dim); font-size: var(--fs-xs); }
.gauge .gnote { font-size: var(--fs-xs); color: var(--text-faint);
                margin-top: var(--sp-3); }

/* The reconciliation line for the two bases — see `_cash_gap` in dashboard.py.
   It exists because two figures the reader has to reconcile themselves read as
   a contradiction; the page does the subtraction and shows its working. */
.gapline {
    margin-top: var(--sp-4); padding: var(--sp-3) var(--sp-4);
    border-left: 3px solid var(--accent); background: var(--surface-2);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    font-size: var(--fs-sm); color: var(--text-dim);
}
.gapline strong { color: var(--text); font-family: var(--mono); }

/* The three figures, as supporting detail rather than a competing table. */
.figures { display: grid; gap: var(--sp-3); margin-top: var(--sp-5); }
.figure {
    border-top: 1px solid var(--border); padding-top: var(--sp-3);
}
.figure .flabel { font-size: var(--fs-sm); color: var(--text-dim);
                  font-weight: var(--w-medium); }
.figure .fvalue {
    font-family: var(--mono);
    font-size: var(--fs-lg); font-weight: var(--w-semi); line-height: var(--lh-lg);
}
.figure .fmeta { font-size: var(--fs-xs); color: var(--text-faint);
                 font-family: var(--mono); }
.figure.benchmark .fvalue { color: var(--text-dim); }
.figure.benchmark { border-top-style: dashed; }

@media (min-width: 720px) {
    .figures { grid-template-columns: repeat(4, 1fr); gap: var(--sp-5); }
}

/* ---- section headings and cards ------------------------------------------ */

.section { margin-bottom: var(--sp-4); }
.section > h2 {
    font-size: var(--fs-md); font-weight: var(--w-semi); margin: 0 0 var(--sp-3);
    display: flex; align-items: baseline; gap: var(--sp-3);
}
.section > h2 .asof {
    font-size: var(--fs-xs); color: var(--text-faint); font-weight: var(--w-normal);
    font-family: var(--mono);
}

/* ---- charts -------------------------------------------------------------- */

/* A fixed height per breakpoint, because Chart.js needs a bounded parent and a
   percentage height against an auto-height card collapses to nothing. */
.chart-shell { position: relative; height: 260px; }
.chart-strip { position: relative; height: 14px; margin-top: 2px; }
.chart-legend-note {
    font-size: var(--fs-xs); color: var(--text-faint); margin-top: var(--sp-2);
}
@media (min-width: 720px)  { .chart-shell { height: 340px; } }
@media (min-width: 1200px) { .chart-shell { height: 400px; } }

/* Desktop puts the two charts beside each other; the phone stacks them. */
.charts { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 1200px) {
    .charts { grid-template-columns: 3fr 2fr; align-items: start; }
}

.swatch {
    display: inline-block; width: 10px; height: 10px; border-radius: 2px;
    vertical-align: -1px; margin-right: 4px;
}
.swatch.solid { background: var(--gain); }
.swatch.mixed { background: var(--warn); }
.swatch.weak  { background: var(--loss); }

/* ---- holdings ------------------------------------------------------------ */

/* The weight bar is drawn behind the ticker cell, so allocation is readable
   without a separate chart competing for the same information. */
td.wcell { position: relative; }
td.wcell .wbar {
    position: absolute; left: 0; top: 3px; bottom: 3px;
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    border-radius: 2px; z-index: 0;
}
td.wcell span { position: relative; z-index: 1; }

/* Contribution bar: ranking readable without reading every number. Gains and losses run
   in one direction each so the eye finds the losers instantly. */
.cbar {
    display: inline-block; height: 10px; border-radius: 2px;
    background: color-mix(in srgb, var(--gain) 55%, transparent);
    min-width: 2px; vertical-align: middle;
}
.cbar.neg { background: color-mix(in srgb, var(--loss) 55%, transparent); }

tbody tr.cashrow td { color: var(--text-dim); font-style: italic; }
tbody tr.shortrow td:first-child::after {
    content: "short"; margin-left: var(--sp-2); font-size: var(--fs-xs);
    color: var(--loss); font-style: normal;
}

/* On a phone the ticker column freezes so a horizontal scroll stays legible;
   the page body itself never scrolls sideways. */
@media (max-width: 719px) {
    .scroll-x table th:first-child,
    .scroll-x table td:first-child {
        position: sticky; left: 0; background: var(--surface);
        box-shadow: 1px 0 0 var(--border);
    }
    /* Columns that only earn their space on a wide screen. */
    .desktop-only { display: none; }
}
@media (min-width: 720px) {
    .phone-only { display: none; }
}

/* ---- cash: two denominators, no adjective -------------------------------- */

.denoms { display: grid; gap: var(--sp-3); }
@media (min-width: 720px) { .denoms { grid-template-columns: repeat(3, 1fr); } }
.denom .dlabel { font-size: var(--fs-sm); color: var(--text-dim); }
.denom .dvalue { font-family: var(--mono); font-size: var(--fs-lg);
                 font-weight: var(--w-semi); }
.deploybar {
    height: 8px; border-radius: 999px; background: var(--surface-2);
    overflow: hidden; margin-top: var(--sp-2); display: flex;
}
.deploybar .invested { background: var(--accent); }
.deploybar .idle     { background: var(--surface-2);
                       box-shadow: inset 0 0 0 1px var(--border); }

/* ---- caveats: stated beside the numbers, not in a footnote --------------- */

.caveats { list-style: none; padding: 0; margin: 0; }
.caveats li {
    font-size: var(--fs-sm); color: var(--text-dim);
    padding-left: var(--sp-4); position: relative; margin-bottom: var(--sp-2);
}
.caveats li::before {
    content: "!"; position: absolute; left: 0; top: 0;
    color: var(--warn); font-weight: var(--w-bold); font-family: var(--mono);
}

/* The hypothetical block must never be mistakable for the real figures. */
.hypothetical {
    border: 1px dashed var(--warn); border-radius: var(--r-md);
    padding: var(--sp-4); background: color-mix(in srgb, var(--warn) 6%, transparent);
}
.hypothetical > .htag {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--warn); font-weight: var(--w-bold);
}

/* ============================================================================
   Stage E — comparison.

   Desktop: true multi-pane, aligned rows, no scrolling to compare. Phone: the
   differences are summarised ABOVE the fold, then one portfolio at a time via a
   segmented control — "what changed" beats "here are both" on a small screen.
   The segmented control is radio-input CSS, no JS to break.
   ============================================================================ */

.cmp-grid { display: grid; gap: var(--sp-4); }
@media (min-width: 900px) {
    .cmp-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
    .cmp-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
    /* Three portfolios plus the benchmark. The index column is narrower: it carries
       fewer live figures and should not claim equal visual weight to a real account. */
    .cmp-grid.cols-4 { grid-template-columns: repeat(3, 1fr) 0.8fr; }
}

.cmp-col .colhead {
    display: flex; align-items: baseline; gap: var(--sp-2);
    border-bottom: 2px solid var(--series-1); padding-bottom: var(--sp-2);
    margin-bottom: var(--sp-3);
}
.cmp-col:nth-child(2) .colhead { border-color: var(--series-2); }
.cmp-col:nth-child(3) .colhead { border-color: var(--series-3); }
.cmp-col .colhead .cswatch {
    width: 14px; height: 10px; border-radius: 2px; background: var(--series-1);
    flex: none; align-self: center;
}
.cmp-col:nth-child(2) .cswatch { background: var(--series-2); }
.cmp-col:nth-child(3) .cswatch { background: var(--series-3); }

/* The swatch repeats the chart's DASH PATTERN as well as its colour, because the lines
   do (see charts.js). Three hues alone are three identical lines to a colour-blind
   reader, and this chart's whole job is telling them apart. */
.cmp-col:nth-child(2) .cswatch {
    background: repeating-linear-gradient(90deg, var(--series-2) 0 6px,
                                          transparent 6px 9px);
}
.cmp-col:nth-child(3) .cswatch {
    background: repeating-linear-gradient(90deg, var(--series-3) 0 2px,
                                          transparent 2px 5px);
}

/* The benchmark column: present, comparable, and visibly not one of the portfolios. */
.cmp-col.benchcol { background: transparent; border-style: dashed; }
.cmp-col.benchcol .colhead { border-bottom-color: var(--text-faint); }
.cmp-col.benchcol .cswatch {
    background: repeating-linear-gradient(90deg, var(--text-dim) 0 5px,
                                          transparent 5px 9px);
}
.metrics dt.muted, .metrics dt.muted + dd { color: var(--text-faint); }
/* The inactive denominator: present and legible, visibly not the one on the chart. */
.metrics dt.alt, .metrics dd.alt { color: var(--text-dim); }
.metrics dt .tiny { color: var(--text-faint); }

/* The spread column — how far apart the two weights are. It is the point of the table
   and the number it did not have. */
.cmp-holdings td.spread, .cmp-holdings th.spread {
    border-left: 1px solid var(--border); color: var(--text-dim);
}
.cmp-holdings tr.wide td.spread { color: var(--warn); font-weight: var(--w-semi); }

/* Aligned metric rows: same label order in every column, so the eye moves
   horizontally. A dt/dd pair per metric keeps them screen-reader-sane too. */
.metrics { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-3);
           margin: 0; }
.metrics dt { color: var(--text-dim); font-size: var(--fs-sm); }
.metrics dd { margin: 0; text-align: right; font-family: var(--mono);
              font-variant-numeric: tabular-nums; }

/* The phone diff summary — the answer before any swiping. */
.diff-lead { font-size: var(--fs-lg); }
.diff-lead strong { font-family: var(--mono); }

/* Segmented control: radio inputs, labels as segments, panels toggled by CSS. */
.seg { display: none; }
@media (max-width: 899px) {
    .seg { display: flex; gap: 0; border: 1px solid var(--border);
           border-radius: var(--r-sm); overflow: hidden; margin-bottom: var(--sp-4); }
    .seg label {
        flex: 1; text-align: center; padding: var(--sp-3) var(--sp-2);
        min-height: var(--tap); display: flex; align-items: center;
        justify-content: center; font-size: var(--fs-sm); color: var(--text-dim);
        background: var(--surface); cursor: pointer;
        border-left: 1px solid var(--border);
    }
    .seg label:first-child { border-left: 0; }
    /* Panels: all hidden on phone unless selected. Desktop shows all, grid does the rest. */
    .cmp-grid .cmp-col { display: none; }
    #seg1:checked ~ .seg label[for="seg1"],
    #seg2:checked ~ .seg label[for="seg2"],
    #seg3:checked ~ .seg label[for="seg3"],
    #seg4:checked ~ .seg label[for="seg4"] {
        background: var(--accent-dim); color: #fff;
    }
    #seg1:checked ~ .cmp-grid .cmp-col:nth-of-type(1),
    #seg2:checked ~ .cmp-grid .cmp-col:nth-of-type(2),
    #seg3:checked ~ .cmp-grid .cmp-col:nth-of-type(3),
    #seg4:checked ~ .cmp-grid .cmp-col:nth-of-type(4) { display: block; }
}
input.segr { position: absolute; opacity: 0; pointer-events: none; }

/* Weights-aligned holdings table on the compare page. */
.cmp-holdings td.num, .cmp-holdings th.num { text-align: right; }

/* ---- the row-aligned comparison blocks ------------------------------------

   Every area of the portfolio page renders as one of these: the figure down the
   left, the accounts across, the index in the last column. Twenty tables, one
   set of rules, so after the first one there is nothing to learn.

   The row heading is a `<th scope="row">` and carries the explanation underneath
   the label rather than only in a tooltip — hover does not exist on a phone, and
   a value only reachable by hovering is unreachable (06 section 6).
   -------------------------------------------------------------------------- */

.vs-block h2 { display: flex; align-items: baseline; gap: var(--sp-3); }
.vs-lead {
    margin: 0 0 var(--sp-4); max-width: 68ch;
    font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.55;
}
/* Not an error and not a caveat: it says why a cell is empty. Amber would read as
   "something went wrong" for what is usually an access rule working correctly. */
.vs-withheld {
    margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-3);
    border-left: 2px solid var(--border); color: var(--text-dim);
}
.vs-foot { margin: var(--sp-3) 0 0; color: var(--text-faint); max-width: 72ch; }

/* Horizontal only, and never vertical. `.scroll-x` sets `overflow-x: auto` and leaves
   `overflow-y` at `visible`, which CSS then computes to `auto` — so a table one pixel
   too wide grew a vertical scrollbar it had no rows for. These blocks are short; there
   is nothing to scroll downward past. */
.vs-scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }

table.vs { width: 100%; border-collapse: collapse; table-layout: auto; }

/* **Prose wraps here.** The app's base rule is `th, td { white-space: nowrap }`, which
   is right for the tables it was written for — tickers, weights, dates, none of which
   should ever break mid-value. These tables carry sentences: a row's explanation, a
   classification's reasoning, an account's wrapper. Held to `nowrap` each of those
   became one unbreakable line, which forced a horizontal scrollbar onto every block
   whether its numbers needed one or not — and a horizontal bar drags a vertical one in
   with it, because `overflow-x: auto` computes `overflow-y` to `auto` as well.
   So the numbers keep `nowrap` and everything else is allowed to wrap.

   The same defect from the same base rule is recorded further down this file for the
   Learn page ("Two defects, one cause"). The fix is scoped there and scoped here for
   the same reason: relaxing the rule globally would let a date column break in half on
   the portfolio page. */
table.vs th, table.vs td {
    padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border);
    vertical-align: top; white-space: normal;
}
/* A figure still never breaks across two lines. */
table.vs td.num, table.vs th.num { white-space: nowrap; }
/* Except a sentence in a value cell, which is a figure only by position: a wrapper
   name, a verdict, a classification's reasoning. It stops pretending to be a number —
   body font, left-aligned, free to wrap — and is capped at the measure prose is
   actually read at, so three paragraphs side by side are three readable columns rather
   than three sentences stretched the width of a monitor. */
table.vs td.txt {
    white-space: normal; text-align: left; font-family: var(--font);
    font-size: var(--fs-sm); line-height: 1.5; color: var(--text-dim);
}
table.vs td.txt .txtwrap { display: inline-block; max-width: 42ch; }
/* Not sticky. `.scroll-x` scrolls horizontally, and a `position: sticky` heading
   inside it sticks to the page viewport rather than to its own box — it would ride
   up over the nav on a long page. These tables are eight rows; there is nothing to
   stick for. */
table.vs thead th {
    background: var(--surface);
    font-size: var(--fs-sm); font-weight: var(--w-semi); color: var(--text-dim);
    white-space: nowrap;
}
table.vs thead th .tiny { display: block; color: var(--text-faint); font-weight: 400; }
table.vs tbody th {
    text-align: left; font-weight: 400; color: var(--text); white-space: normal;
    /* A share of the table rather than a fixed floor. `min-width: 15rem` made every
       block as wide as its widest possible label even when every label in it was one
       word — "Core", "Cash" — and that alone pushed narrow tables into scrolling. */
    width: 34%;
}
@media (min-width: 900px) { table.vs tbody th { width: 30%; } }
table.vs td.num, table.vs th.num { text-align: right; font-family: var(--mono); }
table.vs tbody tr:hover { background: var(--surface-2); }
table.vs tr.strong th, table.vs tr.strong td {
    font-weight: var(--w-semi); color: var(--text);
}
table.vs tr.strong td.num { font-size: var(--fs-lg); }

/* The explanation, under the label. One sentence, and it says what the reader
   should do differently rather than only what the figure is. */
.vs-note {
    display: block; margin-top: 0.15rem; font-size: var(--fs-xs);
    color: var(--text-faint); line-height: 1.45; font-family: var(--font);
}

/* A blank cell means "not loaded", "not yours to see" or "does not have one" —
   never zero. It is drawn faintly so it reads as absence rather than as a value. */
table.vs .hole { color: var(--text-faint); }

/* The leader, and only on a scale-free row — see versus.COMPARABLE_KINDS. Dollars
   are never marked: the largest account has the largest profit whatever anybody
   did with it. Weight as well as colour, so it survives colour-blindness and a
   monochrome print. */
table.vs td.win { color: var(--gain); font-weight: var(--w-semi); }
table.vs td.win::after { content: " \2022"; }

/* The index column: present, and visibly not one of the portfolios. */
table.vs .bench {
    color: var(--text-dim); border-left: 1px dashed var(--border);
    /* Flat colour first: `color-mix` is recent, and a browser that does not know it
       must still tell the index column apart from the accounts. */
    background: var(--surface-2);
    background: color-mix(in srgb, var(--surface-2) 55%, transparent);
}

/* The colour a portfolio wears on the chart, repeated on every table heading so a
   column and a line are the same account without the reader matching labels. */
table.vs .cswatch {
    display: inline-block; width: 8px; height: 8px; border-radius: 2px;
    margin-right: 0.4rem; background: var(--series-1);
}
table.vs .cswatch.s2 { background: var(--series-2); }
table.vs .cswatch.s3 { background: var(--series-3); }

/* ---- the section chooser, in app chrome -----------------------------------

   Same markup and the same `report.js` as the printable report's toolbar, because
   a second mechanism for "show me less of this page" is a second thing to keep in
   step. Only the colours are overridden: report.css dresses `.rt-*` for paper —
   white pills, ink-black text — and this page is the dark app.
   -------------------------------------------------------------------------- */

.cmp-toolbar {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: var(--sp-3);
    margin-bottom: var(--sp-4);
}
.cmp-toolbar .rt-presets button {
    background: var(--surface-2); color: var(--text); border-color: var(--border);
    min-height: var(--tap);
}
.cmp-toolbar .rt-presets button:hover {
    background: var(--surface); border-color: var(--accent); color: var(--text);
}
.cmp-toolbar .rt-more { margin-left: auto; }
.cmp-toolbar .rt-more > summary {
    color: var(--text-dim); border-color: var(--border); background: var(--surface-2);
    min-height: var(--tap); display: inline-flex; align-items: center;
}
.cmp-toolbar .rt-more[open] > summary {
    background: var(--surface); border-color: var(--accent); color: var(--text);
}
.cmp-toolbar .rt-panel { border-top-color: var(--border); }
.cmp-toolbar .rt-chip {
    background: var(--surface-2); color: var(--text-dim); border-color: var(--border);
    min-height: var(--tap);
}
.cmp-toolbar .rt-chip:has(input:checked) {
    background: var(--surface); border-color: var(--accent); color: var(--text);
}
.cmp-toolbar .rt-note { color: var(--text-faint); max-width: 68ch; }
.cmp-toolbar .rt-count { color: var(--text-faint); }

/* The picker (no ids yet). */
.picker .card { display: flex; align-items: center; gap: var(--sp-3); }

/* ---- the basis / period control strip ------------------------------------ */

.controls {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}
.segbar {
    display: inline-flex; border: 1px solid var(--border);
    border-radius: var(--r-sm); overflow: hidden; background: var(--surface);
}
.segbar a {
    padding: 0 var(--sp-4); min-height: var(--tap);
    display: inline-flex; align-items: center; gap: var(--sp-2);
    font-size: var(--fs-sm); color: var(--text-dim); text-decoration: none;
    border-left: 1px solid var(--border); white-space: nowrap;
}
.segbar a:first-child { border-left: 0; }
.segbar a:hover { text-decoration: none; background: var(--surface-2); }
.segbar a[aria-current="true"] {
    background: var(--accent-dim); color: #fff; font-weight: var(--w-medium);
}
.controls .ctl-label {
    font-size: var(--fs-xs); color: var(--text-faint);
    text-transform: uppercase; letter-spacing: 0.07em; margin-right: var(--sp-1);
}

/* ---- tooltips: hover on a pointer, TAP on a phone ------------------------ */
/* Hover does not exist on a phone, so any value only reachable by hovering is
   unreachable (docs/06_APP_SPEC.md section 6). These are focusable, so a tap
   opens them, and Escape / blur closes. */

.tip {
    border-bottom: 1px dotted var(--text-faint); cursor: help;
    position: relative; outline: none;
}
.tip::after {
    content: attr(data-tip);
    position: absolute; left: 0; top: calc(100% + 6px); z-index: 40;
    width: max-content; max-width: min(34ch, 78vw);
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-sm);
    box-shadow: var(--shadow); padding: var(--sp-3);
    font-family: var(--font); font-size: var(--fs-sm); line-height: var(--lh-sm);
    font-weight: var(--w-normal); text-transform: none; letter-spacing: normal;
    white-space: normal; text-align: left;
    opacity: 0; visibility: hidden; transition: opacity .12s ease;
    pointer-events: none;
}
.tip:hover::after, .tip:focus-visible::after, .tip:focus::after {
    opacity: 1; visibility: visible;
}
/* Near the right edge, open leftwards so the bubble stays on screen. */
.tip.tip-right::after { left: auto; right: 0; }
/* The partial-basis marker: an asterisk that is reachable, not decoration. */
.tip.partial {
    color: var(--warn); border-bottom: 0; margin-left: 2px; cursor: help;
}
.tip.partial::after { left: auto; right: 0; }
td .tip::after, th .tip::after { max-width: min(30ch, 74vw); }

/* ---- income block -------------------------------------------------------- */

.income { display: grid; gap: var(--sp-3) var(--sp-5); }
@media (min-width: 720px) { .income { grid-template-columns: repeat(4, 1fr); } }
.income .iitem .ilabel { font-size: var(--fs-sm); color: var(--text-dim); }
.income .iitem .ivalue {
    font-family: var(--mono); font-variant-numeric: tabular-nums lining-nums;
    font-size: var(--fs-lg); font-weight: var(--w-semi);
}
.income .iitem .isub { font-size: var(--fs-xs); color: var(--text-faint); }

/* ---- broker marks ---------------------------------------------------------
   Where the money sits, recognisable before the name is read. See
   equifolio/brokers.py for why these are our own badges in the firms' colours
   rather than the firms' logos.

   The ring is a translucent tint of the badge's own colour rather than the
   border token: on a card the badge must read as one object at 22px, and a
   grey outline around a saturated square makes it look pasted on. */

.bmark {
    display: inline-block; flex: none; line-height: 0;
    border-radius: 22%; overflow: hidden;
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--bmark) 35%, transparent),
                0 1px 2px rgba(0,0,0,.25);
}
.bmark svg { display: block; width: 100%; height: 100%; }

/* Several accounts at several firms overlap slightly, the way a stack of cards
   does — it reads as "more than one" without spending the width of each. */
.bmarks { display: inline-flex; align-items: center; flex: none; }
.bmarks .bmark + .bmark { margin-left: -8px; }
.bmarks .bmore {
    font-size: var(--fs-xs); color: var(--text-dim); margin-left: var(--sp-2);
    font-family: var(--mono);
}

/* ---- sparkline ------------------------------------------------------------
   The portfolio's own picture: growth of $100 over its whole life. Inline SVG
   inherits these tokens; the lazy <img> on the list carries its own copy,
   because an <img> cannot see the page's custom properties.

   `overflow: visible` matters: the endpoint dot sits ON the last point, so at
   the right edge half of it is outside the viewBox and would be clipped into a
   half-moon. */

.spark { display: block; overflow: visible; }
.spark .l { fill: none; stroke-width: 1.75; stroke-linecap: round;
            stroke-linejoin: round; }
.spark.up   .l { stroke: var(--gain); }
.spark.down .l { stroke: var(--loss); }
.spark.up   .a { fill: var(--gain); opacity: .13; }
.spark.down .a { fill: var(--loss); opacity: .13; }
.spark.up   .d { fill: var(--gain); stroke: none; }
.spark.down .d { fill: var(--loss); stroke: none; }
.spark-none { color: var(--text-faint); font-family: var(--mono);
              font-size: var(--fs-sm); }

.spark-img { display: block; width: 100%; height: 34px; }
.spark-head { display: inline-block; margin-left: var(--sp-2); }
.spark-col { margin: 0 0 var(--sp-3); }
.spark-col .spark { width: 100%; height: 40px; }

/* On a phone the heading sparkline drops: the title, badge and tags already
   fill the line, and a squeezed 40px curve says nothing. */
@media (max-width: 599px) { .spark-head { display: none; } }

/* The portfolio card: badge left, name and firm stacked beside it. */
.pcard { color: inherit; display: flex; flex-direction: column; gap: var(--sp-3); }
.pcard:hover { text-decoration: none; border-color: var(--accent); }
.pcard-head { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.pcard-name { min-width: 0; display: flex; flex-direction: column; }
.pcard-name strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcard .row { margin-top: auto; }

/* The detail page title sits beside its badge rather than under it. */
.phead .ptitle { align-items: center; }

/* ---- the model trade form -------------------------------------------------
   A labelled grid rather than a row of bare placeholders. Seven unlabelled
   boxes in a line is a puzzle: which one is price and which is amount is
   guessable at best, and the answer changes what gets recorded. Placeholders
   vanish the moment you type, so they cannot be the only label. */

.tradeform {
    display: grid; gap: var(--sp-3);
    grid-template-columns: 1fr 1fr;
    align-items: end;
}
@media (min-width: 720px) {
    .tradeform { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 1100px) {
    .tradeform { grid-template-columns: repeat(7, minmax(0, 1fr)) auto; }
}
.tradeform label {
    display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0;
    font-size: var(--fs-xs); color: var(--text-dim);
    text-transform: uppercase; letter-spacing: 0.05em; font-weight: var(--w-medium);
}
.tradeform label input, .tradeform label select { width: 100%; }
.tradeform label.checkline {
    flex-direction: row; align-items: center; gap: var(--sp-2);
    text-transform: none; letter-spacing: normal; font-size: var(--fs-sm);
}
.tradeform label.checkline input {
    width: auto; min-height: auto; width: 18px; height: 18px; flex: none;
}
.tradeform button { align-self: end; }

/* ---- the wealth bridge: proof, one click from the figure ------------------
   Folded because it is evidence rather than a headline, and reachable because
   "the picks made X, the cash made Y" is the sentence that stops the two
   measurement bases reading as a contradiction. */

details.bridge { margin-top: var(--sp-4); border-top: 1px solid var(--border);
                 padding-top: var(--sp-3); }
details.bridge > summary {
    cursor: pointer; font-size: var(--fs-sm); color: var(--text-dim);
    font-weight: var(--w-medium); list-style: none;
    min-height: var(--tap); display: flex; align-items: center; gap: var(--sp-2);
}
details.bridge > summary::-webkit-details-marker { display: none; }
details.bridge > summary::before {
    content: "+"; font-family: var(--mono); color: var(--accent);
    font-weight: var(--w-bold); width: 1em;
}
details.bridge[open] > summary::before { content: "\2212"; }
details.bridge td { white-space: normal; }
details.bridge td.indent { padding-left: var(--sp-6); color: var(--text-dim); }
details.bridge tr.bsum td {
    border-top: 1px solid var(--border); font-weight: var(--w-semi);
    background: var(--surface-2);
}

/* Generated SQL, offered for someone else to run. Monospace and scrollable in its
   own box — it is meant to be selected and copied whole, so it must not wrap. */
.sqlblock {
    margin: 0; padding: var(--sp-3) var(--sp-4);
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--r-sm);
    font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.5;
    color: var(--text-dim); white-space: pre; overflow-x: auto;
}
.sqlblock code { font: inherit; color: inherit; background: none; border: 0; padding: 0; }

.notice {
    border: 1px solid var(--warn); border-left-width: 3px;
    border-radius: var(--r-sm); padding: var(--sp-3) var(--sp-4);
    background: color-mix(in srgb, var(--warn) 7%, transparent);
    font-size: var(--fs-sm); margin-bottom: var(--sp-4);
}

/* ==========================================================================
   The research grid — docs/07_THE_LOOP.md §5.

   A dense small-multiples surface: tickers down, time scales across, four
   measurements per ticker stacked so a column reads as one span seen four
   ways. Density is the feature, so the type scale drops to --fs-xs here and
   the panels have no chrome of their own — the card edge and the gaps do the
   separating.
   ========================================================================== */

.rctl { padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }
.rctl .row + .chips { margin-top: var(--sp-3); }

.chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.chips + .chips { margin-top: var(--sp-2); }
.chips .clabel {
    font-size: var(--fs-xs); color: var(--text-faint);
    text-transform: uppercase; letter-spacing: .06em;
    margin-right: var(--sp-1);
}
.chips .clabel + .clabel, .chip + .clabel { margin-left: var(--sp-4); }

/* A checkbox wearing a chip. The input stays in the DOM rather than being
   replaced by JS: this whole page is a GET form, so the browser's own state
   is the page's state and nothing has to be synchronised. */
.chip {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    padding: 4px var(--sp-3); min-height: 30px;
    border: 1px solid var(--border); border-radius: 999px;
    font-size: var(--fs-sm); color: var(--text-dim);
    cursor: pointer; user-select: none; background: var(--surface);
}
.chip:hover { border-color: var(--text-faint); color: var(--text); }
.chip.on { border-color: var(--accent); color: var(--accent);
           background: color-mix(in srgb, var(--accent) 10%, transparent); }
.chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

.rcard { padding: var(--sp-3) var(--sp-4) var(--sp-4); margin-bottom: var(--sp-4); }
.rhead {
    display: flex; align-items: baseline; gap: var(--sp-3);
    margin-bottom: var(--sp-3); padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
}
.rhead b { font-size: var(--fs-lg); font-family: var(--mono); letter-spacing: -.01em; }
.rhead .muted { font-size: var(--fs-sm); }

.rgrid {
    display: grid;
    grid-template-columns: 132px repeat(var(--cols), minmax(104px, 1fr));
    gap: 1px var(--sp-2); align-items: stretch; min-width: max-content;
}

/* Column headings: the time scale, once per card. Repeated per card rather
   than made sticky at the top of the page — you read one ticker at a time and
   a heading three cards away is a heading you have to go and find. */
.rcol {
    font-size: var(--fs-xs); font-family: var(--mono); color: var(--text-faint);
    text-align: center; padding-bottom: var(--sp-1);
    border-bottom: 1px solid var(--border);
}

.rlane {
    display: flex; flex-direction: column; justify-content: center;
    font-size: var(--fs-sm); color: var(--text-dim); font-weight: var(--w-medium);
    padding-right: var(--sp-2); border-right: 1px solid var(--border);
}
.rlane i {
    font-style: normal; font-size: var(--fs-xs); color: var(--text-faint);
    line-height: 1.3; margin-top: 2px;
    /* The explanation is present but never allowed to set the row height. */
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ------------------------------------------------------------------ a panel */

.rp { padding: var(--sp-2) 0 var(--sp-1); min-width: 0; }
.rp svg {
    display: block; width: 100%; height: 44px;
    /* preserveAspectRatio="none" stretches the 100x40 viewBox to whatever the
       column is; non-scaling-stroke is what stops the line getting fatter in
       one axis than the other as it does. */
    overflow: visible;
}
.rp svg .l, .rp svg .g {
    fill: none; stroke-linecap: round; stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
.rp svg .l { stroke: var(--accent); stroke-width: 1.6px; }
.rp svg .g { stroke: var(--text-faint); stroke-width: 1px; stroke-dasharray: 3 3;
             opacity: .8; }
.rp svg .z { stroke: var(--border); stroke-width: 1px; vector-effect: non-scaling-stroke; }
.rp svg .v { fill: var(--text-faint); opacity: .35; }
.rp svg .e { fill: var(--accent); }

.rp.up   svg .l, .rp.up   svg .e { stroke: var(--gain); }
.rp.up   svg .e { fill: var(--gain); }
.rp.down svg .l, .rp.down svg .e { stroke: var(--loss); }
.rp.down svg .e { fill: var(--loss); }
.rp.flat svg .l { stroke: var(--accent); }

/* Too few observations to be a shape. Said in the drawing as well as in the
   tooltip, because a five-point line looks exactly as confident as a
   thousand-point one and that is the whole problem with a 1-week panel. */
.rp.thin svg .l { stroke-dasharray: 2 2; }

.rp .rf {
    display: flex; flex-direction: column; line-height: 1.25;
    margin-top: 2px; text-align: right;
}
.rp .rf b {
    font-family: var(--mono); font-size: var(--fs-xs); font-weight: var(--w-semi);
    font-variant-numeric: tabular-nums; color: var(--text);
}
.rp .rf i {
    font-style: normal; font-family: var(--mono); font-size: 0.6875rem;
    color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.rp.up .rf b { color: var(--gain); }
.rp.down .rf b { color: var(--loss); }

.rp.empty { display: flex; align-items: center; justify-content: center; }
.rp .rn {
    font-size: 0.6875rem; color: var(--text-faint); text-align: center;
    line-height: 1.3; padding: 0 var(--sp-1);
}

/* ------------------------------------------------------- the signal matrix */

.rtab { font-size: var(--fs-sm); }
.rtab th, .rtab td { padding: var(--sp-2) var(--sp-3); white-space: nowrap; }
.rtab tbody th { font-family: var(--mono); color: var(--text); }
.rtab td { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.rtab tbody tr:hover td, .rtab tbody tr:hover th { background: var(--surface-2); }

@media (max-width: 719px) {
    .rgrid { grid-template-columns: 92px repeat(var(--cols), minmax(84px, 1fr)); }
    .rlane i { display: none; }
    .rp svg { height: 38px; }
}

/* ============================================================================
   Stage G — users, the gallery, and the import forms.

   docs/08_TENANCY.md. Nothing here introduces a new colour, a new radius or a
   new type step: the two new tags reuse the existing semantic hues, and the
   forms reuse the label/field pattern .tradeform already established. A stage
   that adds a screen and also adds a palette is a stage that has quietly forked
   the design system.
   ============================================================================ */

/* A scaled sample is a MODEL that is additionally not made of real money, so it
   takes the model tag's warn hue filled rather than a colour of its own — the
   badge reads as "model, and more so" rather than as a fourth category.
   Public borrows the accent that already means "shared", for the same reason. */
.tag.sample  { border-color: var(--warn); color: var(--bg); background: var(--warn); }
.tag.public  { border-color: var(--accent); color: var(--accent); }
.tag.waiting { border-color: var(--warn); color: var(--bg); background: var(--warn);
               font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---- forms that ask a question rather than take a number ------------------ */
/* .tradeform is a dense row of numeric fields. These are the opposite: a few
   fields that each need a sentence under them, because the two that matter —
   tax treatment and lot method — silently change every downstream figure and a
   placeholder cannot carry that. */

textarea {
    font: inherit; width: 100%; border-radius: var(--r-sm);
    border: 1px solid var(--border); background: var(--surface-2);
    color: var(--text); padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-3); resize: vertical;
}
textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.setup { max-width: 40rem; }
.setup label, .stack > label {
    display: flex; flex-direction: column; gap: var(--sp-1);
    font-size: var(--fs-sm); color: var(--text-dim);
}
.setup label > input, .setup label > select,
.stack > label > input, .stack > label > select { width: 100%; }
.setup label .tiny { font-size: var(--fs-xs); }

.setup fieldset, fieldset.stack {
    border: 1px solid var(--border); border-radius: var(--r-md);
    padding: var(--sp-4); margin: 0;
}
.setup legend, fieldset.stack legend {
    font-size: var(--fs-sm); font-weight: var(--w-semi); color: var(--text);
    padding: 0 var(--sp-2);
}
/* 44pt targets: a radio list is the one control on this page a phone has to hit
   accurately, and getting it wrong picks the wrong tax wrapper. */
label.checkline {
    display: flex; align-items: flex-start; gap: var(--sp-3);
    min-height: var(--tap); padding: var(--sp-2) 0; cursor: pointer;
    color: var(--text-dim);
}
label.checkline input { margin-top: 0.2rem; flex: none; }
label.checkline strong { color: var(--text); font-weight: var(--w-medium); }

/* ---- the reconciliation block --------------------------------------------- */
/* Printed on every import, pass or fail (04 section 5.3). Monospace and
   scrollable rather than reflowed: it is a column-aligned comparison, and
   wrapping it destroys the alignment that makes it readable at a glance. */
.recon {
    font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.5;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--r-sm); padding: var(--sp-3);
    overflow-x: auto; margin: var(--sp-2) 0 0; white-space: pre;
}

/* ---- the gallery ---------------------------------------------------------- */
/* No new layout at all. A published sample IS a portfolio, so it uses .grid and
   .pcard exactly as the portfolio list does — giving it its own visual language
   would suggest it is a different kind of object, and the whole point of the
   construction is that it is not. */
.pcard-name .tiny { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* ------------------------------------------------------------------ bands page
   Pillar chips show all five sub-scores beside every quality total — the
   divergences carry the information (docs/09_SCORING.md §3), so the total is
   never shown alone. Chips stay monochrome except at the extremes: a wall of
   five colored chips per row reads as noise, a single hot or cold one reads. */
/* ---------------------------------------------------------------- the course
   The wizard's step strip: built steps are links, planned ones are visible but
   inert — a path that pretended to end at step 6 would be hiding its own plan.
   Numbers follow 10 §2 even where phase 4 skips some, so the page and the plan
   speak one ordering. */
.course-strip {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}
.cstep {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--fs-sm); color: var(--text-dim); text-decoration: none;
}
a.cstep:hover { background: var(--surface-2); text-decoration: none; }
.cstep.active { border-color: var(--accent); color: var(--text); }
.cstep.planned { color: var(--text-faint); border-style: dashed; cursor: help; }
.cnum {
  font-family: var(--mono); font-size: var(--fs-xs);
  border: 1px solid var(--border); border-radius: 50%;
  width: 1.4em; height: 1.4em; display: inline-flex;
  align-items: center; justify-content: center;
}
.cstep.active .cnum { border-color: var(--accent); color: var(--accent); }
/* A proposal card is visibly a hypothesis, the same discipline as MODEL vs REAL:
   dashed frame, and its "after" figures render in italics from the markup. */
.card.proposal { border-style: dashed; border-color: var(--warn); }

.pchips { display: inline-flex; gap: var(--sp-1); }
.pchip {
  font-size: 0.72rem; font-variant-numeric: tabular-nums;
  padding: 0 var(--sp-1); border: 1px solid var(--border); border-radius: 4px;
  color: var(--text-dim); white-space: nowrap;
}
.pchip.plow  { border-color: var(--loss); color: var(--loss); }
.pchip.phigh { border-color: var(--gain); color: var(--gain); }

/* The quality verdict, worded as the action it implies. Same skeleton as .tag. */
.qtag {
  font-size: 0.72rem; padding: 0 var(--sp-2); border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-dim); white-space: nowrap;
}
.qtag.q-priority { border-color: var(--gain); color: var(--bg); background: var(--gain); }
.qtag.q-eligible { border-color: var(--gain); color: var(--gain); }
.qtag.q-hold     { }
.qtag.q-review   { border-color: var(--warn); color: var(--warn); }
.qtag.q-trim     { border-color: var(--loss); color: var(--loss); }
.qtag.q-exit     { border-color: var(--loss); color: var(--bg); background: var(--loss); }


/* ============================================================================
   The export guide — a layer over the import form, and a page of its own.

   docs/reference/exporting.md rendered for somebody who is mid-import. No new
   colour, radius or type step: the layer is .card's surface and border lifted
   onto a backdrop, and the prose inside reuses the existing table styling.
   ============================================================================ */

/* ---- the layer ------------------------------------------------------------ */
/* <dialog> gives us Escape, the backdrop, focus containment and modal semantics
   for free. Everything below is only size and chrome. The header and footer are
   fixed and the body scrolls, because the guide is long and losing the close
   button off the top of a phone screen is how a modal becomes a trap. */

dialog.layer {
    width: min(56rem, calc(100vw - 2 * var(--sp-4)));
    max-height: min(46rem, calc(100dvh - 2 * var(--sp-5)));
    padding: 0;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow);
    /* Grid rather than flex: the middle row is the only one allowed to grow, and
       `minmax(0, 1fr)` is what actually lets it scroll instead of overflowing. */
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    overflow: hidden;
}
dialog.layer::backdrop { background: rgba(0, 0, 0, .6); backdrop-filter: blur(2px); }

.layer-head, .layer-foot {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-2);
}
.layer-head { border-bottom: 1px solid var(--border); }
.layer-foot { border-top: 1px solid var(--border); }
.layer-head h2 {
    margin: 0; flex: 1; min-width: 0;
    font-family: var(--font-display); font-size: var(--fs-lg);
}
.layer-foot .primary { min-width: 6rem; }
.layer-foot a { margin-left: auto; }

.layer-x {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--tap); height: var(--tap); padding: 0;
    background: transparent; border-color: transparent; color: var(--text-dim);
}
.layer-x:hover { color: var(--text); background: var(--surface); }
.layer-x svg { width: 18px; height: 18px; }

.layer-body { overflow-y: auto; -webkit-overflow-scrolling: touch; padding: var(--sp-4); }

/* Phones get the whole screen. A 56rem dialog inset by a gutter on a 390px screen
   is a modal with a decorative border and no room for the tables inside it. */
@media (max-width: 719px) {
    dialog.layer {
        width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
        border-radius: 0; border-width: 0;
    }
}

/* ---- the guide itself, in either container -------------------------------- */
/* Numbered steps, because these are performed in order in another application and
   the number is how you find your place again after switching windows. */

.prose h2 {
    font-family: var(--font-display); font-size: var(--fs-lg);
    margin: var(--sp-6) 0 var(--sp-2); padding-top: var(--sp-4);
    border-top: 1px solid var(--border);
}
.prose h2:first-child, .prose > *:first-child + h2 { margin-top: var(--sp-3); }
.prose code {
    font-family: var(--mono); font-size: 0.9em;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: 4px; padding: 0 var(--sp-1);
}
.prose table { font-size: var(--fs-sm); }
.prose table td:first-child { white-space: nowrap; }

/* The click-path line. Monospace-adjacent treatment so a sequence of menu names
   reads as one instruction rather than as a sentence to be interpreted. */
.prose .path {
    background: var(--surface-2); border-left: 2px solid var(--accent);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    padding: var(--sp-2) var(--sp-3); margin: var(--sp-2) 0;
    font-size: var(--fs-sm); color: var(--text);
}

/* Amber, not red: every one of these is a trap you can still walk into, not a
   failure that has already happened. */
.prose .warnline {
    border-left: 2px solid var(--warn);
    padding: var(--sp-2) var(--sp-3); margin: var(--sp-3) 0;
    font-size: var(--fs-sm); color: var(--text-dim);
}
.prose .warnline strong { color: var(--text); }

ol.steps { list-style: none; counter-reset: step; margin: var(--sp-4) 0 0; padding: 0; }
ol.steps > li {
    counter-increment: step;
    position: relative; padding: 0 0 var(--sp-5) var(--sp-8);
    border-left: 1px solid var(--border); margin-left: 0.9rem;
}
ol.steps > li:last-child { border-left-color: transparent; padding-bottom: 0; }
ol.steps > li::before {
    content: counter(step);
    position: absolute; left: -0.9rem; top: 0;
    width: 1.8rem; height: 1.8rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--mono); font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm); font-weight: var(--w-semi);
    background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim);
}
ol.steps > li > h3 {
    margin: 0.15rem 0 var(--sp-2);
    font-size: var(--fs-md); font-weight: var(--w-semi); color: var(--text);
}
ol.steps > li > p { margin: var(--sp-2) 0; }

@media (max-width: 719px) {
    /* The rail costs 3rem of a 390px screen and the tables inside need every
       pixel; the numbered disc alone carries the ordering. */
    ol.steps > li { padding-left: var(--sp-5); margin-left: 0.75rem; }
    ol.steps > li::before { width: 1.5rem; height: 1.5rem; left: -0.75rem; }
}


/* ============================================================================
   The plain-language summary (equifolio/narrative.py).

   Prose on a page made of tiles, so it gets a measure and a rhythm rather than
   the grid everything else uses. No new colour: tone reuses the semantic hues
   that already mean gain, loss and caution everywhere else, on the left edge
   only — a filled panel would out-shout the figures it is introducing.
   ============================================================================ */

.plainly { max-width: 60rem; }
.plainly-block { padding-left: var(--sp-4); border-left: 2px solid var(--border); }
.plainly-block + .plainly-block { margin-top: var(--sp-5); }
/* `h3` since 2026-09-01: the macro renders inside a section that already owns the h2. */
.plainly-block h3 {
    margin: 0 0 var(--sp-2);
    font-family: var(--font-display); font-size: var(--fs-md);
    text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim);
}
/* The lead is the sentence someone reads if they read one sentence. */
.plainly-block .lead { margin: 0; font-size: var(--fs-lg); line-height: var(--lh-lg); }
.plainly-block ul { margin: var(--sp-3) 0 0; padding-left: var(--sp-4); }
.plainly-block li { margin-bottom: var(--sp-2); color: var(--text-dim); }
.plainly-block li:last-child { margin-bottom: 0; }
/* The conclusion, set apart because it is the one line that is an opinion. */
.plainly-block .verdict {
    margin: var(--sp-3) 0 0; padding: var(--sp-3);
    background: var(--surface-2); border-radius: var(--r-sm);
    font-weight: var(--w-medium);
}

.plainly-block.tone-good { border-left-color: var(--gain); }
.plainly-block.tone-warn { border-left-color: var(--warn); }
.plainly-block.tone-bad  { border-left-color: var(--loss); }
.plainly-block.tone-good .verdict { color: var(--gain); }
.plainly-block.tone-warn .verdict { color: var(--warn); }
.plainly-block.tone-bad  .verdict { color: var(--loss); }


/* ===========================================================================
   Blocks brought over from the printable report — 2026-08-22.

   The report grew the sections that actually explain an account (where the money
   went, year by year, what the cash cost, each holding's story) and the page a
   person opens every day did not have them. One payload already carried all of
   it; only the markup was missing. Chart macros are shared outright rather than
   reimplemented, so the two views can never disagree about what a figure means.
   =========================================================================== */

.rk { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.rk-port { background: var(--series-1); } .rk-bench { background: #8a93a2; }
.rk-kept { background: #1a7f37; } .rk-tax { background: #cf222e; }
.rk-advisor { background: #bc4c00; } .rk-commissions { background: #8250df; }
.rk-margin { background: #9a6700; } .rk-other { background: #8a93a2; }

/* Where the money went: one bar, what was kept first. */
.rstack { margin: var(--sp-3) 0 0; }
.rs-bar { display: flex; height: 30px; border-radius: 3px; overflow: hidden;
          background: var(--surface-2); }
.rs-bar i { display: block; height: 100%; }
.rs-kept { background: #1a7f37; } .rs-tax { background: #cf222e; }
.rs-advisor { background: #bc4c00; } .rs-commissions { background: #8250df; }
.rs-margin { background: #9a6700; } .rs-other { background: #8a93a2; }
.rs-key { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex;
          flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); font-size: var(--fs-sm); }
.rs-key li { display: flex; align-items: center; gap: 0.4rem; }
.rs-key b { font-family: var(--mono); }
.rs-pct { color: var(--text-dim); font-family: var(--mono); }

/* Yearly bars, portfolio against the index on a shared zero line. */
.rbars { display: block; width: 100%; height: 190px; }
.rbars .rb-zero { stroke: var(--border); stroke-width: 1; }
.rbars .rb-port { fill: var(--series-1); }
.rbars .rb-bench { fill: #a9b2bf; }
.rbars .rb-value { fill: var(--text-dim); font-size: 10px; text-anchor: middle;
                   font-family: var(--mono); }
.rbars .rb-label { fill: var(--text-faint); font-size: 11px; text-anchor: middle; }

/* Share of the account at work, over time. */
.rdeploy { display: block; width: 100%; height: 150px; }
.rdeploy .rdp-grid { stroke: var(--border); stroke-width: 1; opacity: 0.5; }
.rdeploy .rdp-grid-label { fill: var(--text-faint); font-size: 9px;
                           font-family: var(--mono); }
.rdeploy .rdp-fill { fill: var(--series-1); fill-opacity: 0.16; stroke: none; }
.rdeploy .rdp-line { fill: none; stroke: var(--series-1); stroke-width: 1.75;
                     stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.rdeploy .rdp-axis { stroke: var(--border); stroke-width: 1; }
.rc-tick-label { fill: var(--text-faint); font-size: 10px; text-anchor: middle;
                 font-family: var(--mono); }

/* This pile against that pile. */
.rversus { margin: var(--sp-3) 0 0; }
.rv-row { display: flex; align-items: center; gap: var(--sp-3);
          margin-bottom: var(--sp-2); }
.rv-label { flex: 0 0 11rem; font-size: var(--fs-sm); color: var(--text-dim); }
.rv-bar { flex: 1; height: 24px; background: var(--surface-2); border-radius: 3px;
          overflow: hidden; }
.rv-bar i { display: block; height: 100%; }
.rv-bar .rv-a { background: var(--series-1); }
.rv-bar .rv-b { background: #a9b2bf; }
.rv-num { flex: 0 0 7.5rem; text-align: right; font-family: var(--mono);
          font-variant-numeric: tabular-nums; font-weight: var(--w-semi); }

/* A holding's purchases, as chips: a sequence of decisions rather than a blob. */
.rtranches { line-height: 1.9; }
.rtr { display: inline-block; margin: 0 0.3rem 0.15rem 0; padding: 0.05rem 0.4rem;
       border-radius: 3px; font-size: 0.72rem; font-family: var(--mono);
       background: var(--surface-2); color: var(--text); white-space: nowrap; }
.rtr b { font-weight: var(--w-semi); }
.rtr em { font-style: normal; color: #9a6700; }
.rtr-reinvested { opacity: 0.65; }
.rtr-transferred em { color: #9a6700; }

/* A conclusion the numbers support, not another number. */
.verdict { margin: var(--sp-3) 0 0; padding: var(--sp-3); background: var(--surface-2);
           border-radius: var(--r-sm); font-size: var(--fs-sm); }
.verdict.bad { color: #cf222e; } .verdict.warn { color: #9a6700; }
.verdict.good { color: #1a7f37; }
.bullets { margin: 0 0 var(--sp-3); padding-left: var(--sp-4); font-size: var(--fs-sm); }
.bullets li { margin-bottom: var(--sp-2); color: var(--text-dim); }
tr.quiet td { opacity: 0.55; }


/* ===========================================================================
   The Learn page — prose in tables, and a sheet you can write on. 2026-08-24.

   Two defects, one cause. `th, td { white-space: nowrap }` up at the top of this file
   is correct for every table in the app except these: a figures table wants a share
   price on one line, and a table whose cells are whole sentences ("How violently does
   this thing move?") simply refuses to wrap and pushes the card past the viewport. The
   fix is scoped rather than global — relaxing the rule for everyone would let a date
   column break in half on the portfolio page.

   And the page is written to be studied away from a screen, which had never been
   provided for: no print rules existed for anything outside `.report`, so printing
   /learn produced the nav bar, the anchor list, and every `<details>` still shut.
   Opening those is JavaScript's job (see the template) — CSS cannot reach inside a
   closed disclosure.
   ------------------------------------------------------------------------- */

/* `.learn-top` lived here until the 2026-08-29 merge, when the heading and the print
   button moved into the walkthrough's `.masthead` alongside the reading-level switch. */

/* The whole reason the tables were overflowing. `normal` on both, then the one
   exception: a leading label cell ("Trend", "A+ ≥ 90") reads as a heading for its row
   and should not fold onto two lines when there is room for it on one. */
.learn th, .learn td { white-space: normal; }
.learn table { table-layout: auto; }
.learn thead th { white-space: nowrap; }

/* Formulas are the one thing on the page with no spaces to break at, and they are
   long: `clip(1 − 1.5 · (share − 0.25)⁺, 0.40, 1.00)`. Without this they set the
   minimum width of their table cell and re-create the overflow the rule above fixed. */
.learn code { overflow-wrap: anywhere; }
/* A section heading now states its subject and, beside it, the question the section
   answers: "The risk score / how rough is the ride?". The separator is the flex gap and
   the italic, not a dash — the same arrangement `.asof` already uses on the compare and
   course headings, so the two do not read as different kinds of thing. What this adds
   beyond a colour is permission to wrap: `.section > h2` is a baseline flex row, and
   without wrapping a long question on a phone rides off the edge of the card instead of
   dropping under the heading it belongs to. */
.learn .section > h2 { flex-wrap: wrap; }
.learn .section > h2 .q, .learn h3 .q {
    font-size: var(--fs-sm); font-weight: var(--w-normal); font-style: italic;
    color: var(--text-dim);
}

/* The orientation list at the top is the one thing on this page a reader must not skim
   past, so it does not inherit `.bullets`' dimmed small type — that styling is for a
   supporting aside, and this is the opposite of one. */
.learn .brief li { color: var(--text); font-size: inherit; }

/* Sub-headings inside a section. Sized *below* the section's own h2 rather than above it,
   because a browser's default h3 is larger than `--fs-md` and would out-shout the heading
   it sits under — the one thing a sub-heading must not do. */
.learn h3 {
    font-size: var(--fs-sm); font-weight: var(--w-semi); color: var(--text);
    margin: var(--sp-4) 0 var(--sp-2);
}


/* ---------------------------------------------------------------------------
   The Learn walkthrough — merged from docs/prototypes/learn-wizard.html on
   2026-08-29.

   The prototype restated the whole token block because an artifact cannot link
   to a stylesheet. Nothing below redefines a colour, a size or a spacing step:
   every rule here uses the tokens already declared at the top of this file, so
   the walkthrough's bands are the same blue/green/amber/red as the map's and a
   theme change moves both together.

   Everything is scoped under `.learn`. Four of these class names already mean
   something else in this file — `.tip`, `.gauge`, `.alloc`, `.picker`,
   `.stack`, `.seg`, `.verdict` — so the walkthrough's versions carry an `l`
   prefix rather than relying on selector specificity to win. A rule that only
   works because it is longer than another rule is a rule that breaks the first
   time someone reorders the file.
   --------------------------------------------------------------------------- */

/* ---- type ----
   `.learn h3` above is sized BELOW its section's h2, which was right for the old
   single-scroll page where an h2 was --fs-md. The walkthrough's headings sit on a step
   rather than in a `.section`, so they set their own scale: h2 opens a step, h3 opens a
   card inside it, and both stay smaller than the h1 above the rail. Later in the file
   than `.learn h3` and at the same specificity, so this wins by order — the file is
   read top to bottom on purpose. */
.learn-wizard h1 {
    font-family: var(--font-display); font-size: var(--fs-xxl);
    font-weight: var(--w-bold); line-height: var(--lh-xxl); letter-spacing: -.02em;
    margin: 0; text-wrap: balance;
}
.learn-wizard h2 {
    font-family: var(--font-display); font-size: var(--fs-xl);
    font-weight: var(--w-semi); line-height: var(--lh-xl); letter-spacing: -.01em;
    margin: 0; display: block; text-wrap: balance;
}
.learn-wizard h3 {
    font-family: var(--font-display); font-size: var(--fs-lg);
    font-weight: var(--w-semi); line-height: var(--lh-lg); color: var(--text);
    margin: 0; text-wrap: balance;
}
/* Spacing between blocks is the flex gap on `.lstack-sm`, so a paragraph carries none of
   its own — except inside an aside, which is plain flow. */
.learn-wizard p { margin: 0; }
.learn-wizard .aside p + p { margin-top: var(--sp-2); }
.learn-wizard .aside p:first-child { margin-top: 0; }

/* ---- shell ---- */
.learn-wizard .masthead {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-5);
}
.learn-wizard .mast-controls {
    display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap;
}
.learn-wizard h1 { margin: 0; }
.learn .eyebrow {
    font-size: var(--fs-xs); letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-faint); font-weight: var(--w-semi); margin-bottom: var(--sp-2);
}
.learn .lede { color: var(--text-dim); max-width: 60ch; margin-top: var(--sp-3); }

.learn .lswitch {
    display: flex; align-items: center; gap: var(--sp-3); background: var(--surface);
    border: 1px solid var(--border); border-radius: 999px;
    padding: var(--sp-2) var(--sp-3);
}
.learn .lswitch.inline { align-self: flex-start; margin-bottom: var(--sp-2); }
.learn .lswitch .lab { font-size: var(--fs-xs); color: var(--text-dim); white-space: nowrap; }
.learn .lseg {
    display: flex; background: var(--surface-2); border-radius: 999px; padding: 2px;
    flex-wrap: wrap;
}
.learn .lseg button {
    font: inherit; font-size: var(--fs-xs); font-weight: var(--w-semi); border: 0;
    background: transparent; color: var(--text-dim); padding: var(--sp-2) var(--sp-3);
    border-radius: 999px; cursor: pointer; min-height: 32px;
    transition: background .15s, color .15s;
}
.learn .lseg button[aria-pressed="true"] { background: var(--accent); color: #fff; }

/* ---- the step rail ---- */
.rail {
    display: flex; gap: var(--sp-1); list-style: none; padding: 0;
    margin: 0 0 var(--sp-5); overflow-x: auto; scrollbar-width: thin;
}
/* `1 1 auto`, not `1 1 0`: eight equal columns of the sheet's measure give each step
   98px, and "Against the market" needs 115 — so the longest name wrapped to a second
   line and the rail cost twice the height for the sake of one label, while "Tax" sat in
   98px it did not want. Sized to content, with the slack shared out. */
.rail li { flex: 1 1 auto; min-width: 0; }
.rail button {
    width: 100%; text-align: left; font: inherit; cursor: pointer;
    background: transparent; border: 0; padding: var(--sp-2) 0 0;
    border-top: 3px solid var(--border); color: var(--text-faint);
    font-size: var(--fs-xs); line-height: 1.3; min-height: 0; border-radius: 0;
    display: block; transition: color .15s, border-color .15s;
}
.rail button .n {
    display: block; font-family: var(--mono); font-size: 10px; letter-spacing: .08em;
    margin-bottom: 2px;
}
.rail button .t { display: block; font-weight: var(--w-semi); }
.rail li.done button { border-top-color: var(--accent-dim); color: var(--text-dim); }
.rail li.now button { border-top-color: var(--accent); color: var(--text); }

/* One step at a time on screen; all nine on paper. `.active` is set by script, so
   with no script the first step keeps the class the template gave it and the rest
   are reachable through the jump list, which is anchors. */
.learn .step { display: none; }
.learn .step.active { display: block; animation: learn-rise .28s ease; }
@keyframes learn-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
    .learn .step.active { animation: none; }
    .learn * { transition: none !important; }
}

/* Two separately authored voices, one shown at a time. Both print. */
.learn .plain, .learn .deep { display: none; }
.learn[data-level="plain"] .plain { display: block; }
.learn[data-level="deep"] .deep { display: block; }

.learn .lstack { display: flex; flex-direction: column; gap: var(--sp-5); }
.learn .lstack-sm { display: flex; flex-direction: column; gap: var(--sp-3); }
.learn .card.sub { background: var(--surface-2); box-shadow: none; }
.learn .sub-p { color: var(--text-dim); font-size: var(--fs-sm); margin-top: var(--sp-2); }
.learn .two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
@media (max-width: 700px) { .learn .two { grid-template-columns: 1fr; } }
.learn .scroll { overflow-x: auto; }
.learn .lnote-nav a { white-space: nowrap; }

/* An aside with a coloured spine, the shape the prototype used for "here is the
   caveat". The colour carries meaning: warn for a trap, accent for a definition,
   gain for the argument that pays. */
.learn .aside { border-left: 3px solid var(--border); padding-left: var(--sp-4); }
.learn .aside.warn { border-left-color: var(--warn); }
.learn .aside.accent { border-left-color: var(--accent); }
.learn .aside.gain { border-left-color: var(--gain); }
.learn .verdictbox {
    border-left: 3px solid var(--gain); padding-left: var(--sp-4);
    font-size: var(--fs-sm); color: var(--text-dim);
}
.learn .lnote {
    font-size: var(--fs-xs); color: var(--text-faint);
    border-top: 1px solid var(--border); padding-top: var(--sp-3); margin: 0;
}
.learn .lnote.bare { border: 0; padding: 0; }
.learn .lnote.pagefoot { margin-top: var(--sp-6); }
.learn .why { font-size: var(--fs-sm); color: var(--text-dim); margin: var(--sp-2) 0 0; }
.learn .trythis {
    display: inline-block; font-size: 10px; letter-spacing: .09em;
    text-transform: uppercase; font-weight: var(--w-bold); color: var(--warn);
    border: 1px solid var(--warn); border-radius: 999px; padding: 2px 8px;
    vertical-align: middle;
}
.learn .lnav {
    display: flex; justify-content: space-between; gap: var(--sp-3);
    margin-top: var(--sp-5);
}
.learn .btn.small { padding: 0 var(--sp-4); min-height: 36px; font-size: var(--fs-xs); }
.learn .btn[disabled] { opacity: .35; cursor: not-allowed; }
.learn .bullets.numbered { list-style: decimal; }
.learn .bullets li b { color: var(--text); }

/* ---- ticker chip and its tooltip ---- */
/* `.ltip`, not `.tip`: `.tip` is the app's data-attribute tooltip and lives on the
   map. Two tooltips with one class name is a bug waiting for the first page that
   shows both. */
.learn .tk {
    position: relative; font: inherit; font-family: var(--mono); font-size: .92em;
    font-weight: var(--w-semi); cursor: help; color: var(--accent);
    background: transparent; border: 0; border-bottom: 1px dotted currentColor;
    padding: 0 1px; min-height: 0; border-radius: 0; display: inline;
}
.learn .ltip {
    position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%);
    width: min(260px, 74vw); background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-3);
    font-family: var(--font); font-size: var(--fs-xs); font-weight: var(--w-normal);
    line-height: 1.5; text-align: left; box-shadow: var(--shadow); z-index: 40;
    white-space: normal;
}
.learn .ltip[hidden] { display: none; }
.learn .ltip .tkn {
    display: block; font-family: var(--mono); font-weight: var(--w-bold);
    color: var(--accent); margin-bottom: 2px;
}
.learn .ltip .rs {
    display: block; margin-top: var(--sp-2); color: var(--text-faint);
    font-family: var(--mono);
}

/* ---- expandable rows: the risk components and the quality pillars ---- */
.learn .rows { display: grid; gap: var(--sp-2); }
.learn .rows > div > button {
    display: grid; grid-template-columns: auto 1fr auto; align-items: center;
    gap: var(--sp-3); width: 100%; text-align: left; font: inherit; cursor: pointer;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4); color: var(--text);
    min-height: var(--tap); transition: border-color .15s;
}
.learn .rows > div > button.withicon { grid-template-columns: auto auto 1fr auto; }
.learn .rows > div > button:hover { border-color: var(--accent-dim); }
.learn .rows > div > button[aria-expanded="true"] { border-color: var(--accent); }
.learn .wbadge {
    font-family: var(--mono); font-size: var(--fs-xs); font-weight: var(--w-semi);
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-sm);
    padding: 3px 7px; color: var(--text-dim); white-space: nowrap;
}
.learn .rowname { display: block; font-weight: var(--w-semi); }
.learn .rowtech {
    display: block; font-family: var(--mono); font-size: var(--fs-xs);
    color: var(--text-faint); font-weight: var(--w-normal); margin-top: 1px;
}
.learn .picon { font-size: 1.15rem; line-height: 1; width: 26px; text-align: center; }
.learn .chev { color: var(--text-faint); font-size: var(--fs-sm); transition: transform .18s; }
.learn .rows > div > button[aria-expanded="true"] .chev { transform: rotate(90deg); }
.learn .rowbody {
    padding: var(--sp-3) var(--sp-4) var(--sp-4) calc(var(--sp-4) + 46px);
    font-size: var(--fs-sm); color: var(--text-dim); display: grid; gap: var(--sp-3);
}
.learn .rowbody[hidden] { display: none; }
.learn .rowbody b { color: var(--text); }
.learn .rowbody p { margin: 0 0 var(--sp-2); }
.learn .anchors { font-size: var(--fs-xs); color: var(--text-faint); }
.learn .exl {
    border-left: 3px solid var(--border); padding-left: var(--sp-3);
    color: var(--text-faint);
}

/* ---- the step-by-step walkthrough inside a component row ----
   A second layer of disclosure inside an already-open row, deliberately: the reader who
   wants the definition has it above, and only the reader who wants to follow the
   arithmetic all the way through opens this. Shown at both reading levels, because a
   worked example is not a "simple" or an "advanced" voice — it is the same story told
   once. */
.learn details.story {
    border: 1px solid var(--border); border-radius: var(--r-md);
    background: var(--surface-2); padding: var(--sp-2) var(--sp-3);
}
.learn details.story > summary {
    cursor: pointer; font-weight: var(--w-med); color: var(--text);
    font-size: var(--fs-sm); list-style: none;
}
.learn details.story > summary::-webkit-details-marker { display: none; }
.learn details.story > summary::before { content: "\25B8  "; color: var(--text-faint); }
.learn details.story[open] > summary::before { content: "\25BE  "; }
.learn details.story[open] > summary { margin-bottom: var(--sp-3); }
.learn details.story ol {
    margin: 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-3);
}
.learn details.story li { padding-left: var(--sp-1); }
.learn details.story li b { display: block; color: var(--text); }
/* text left, picture right — the picture is an aside, not the main event */
.learn .rowbody.withchart {
    grid-template-columns: minmax(0, 1fr) 340px; align-items: start;
    column-gap: var(--sp-4);
}
@media (max-width: 820px) { .learn .rowbody.withchart { grid-template-columns: minmax(0, 1fr); } }

/* ---- the anchor ruler ---- */
.learn .ruler-wrap {
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: var(--sp-5);
}
.learn .ruler {
    position: relative; height: 14px; border-radius: 999px;
    margin: var(--sp-6) 0 var(--sp-2);
    background: linear-gradient(90deg, var(--band-core), var(--band-balanced),
                var(--band-growth), var(--band-satellite));
}
.learn .ruler .cap { position: absolute; top: 0; bottom: 0; background: var(--surface-2); opacity: .82; }
.learn .ruler .cap.lo { left: 0; border-radius: 999px 0 0 999px; }
.learn .ruler .cap.hi { right: 0; border-radius: 0 999px 999px 0; }
.learn .ruler .pin {
    position: absolute; top: -11px; width: 4px; height: 36px; border-radius: 2px;
    background: var(--text); box-shadow: 0 0 0 3px var(--surface-2); transition: left .1s;
}
.learn .ruler-ends {
    display: flex; justify-content: space-between; font-size: var(--fs-xs);
    color: var(--text-faint);
}
.learn .readout {
    display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
    margin-top: var(--sp-4);
}
.learn .readout .big {
    font-family: var(--mono); font-size: var(--fs-xxl); font-weight: var(--w-bold);
    line-height: 1;
}
.learn .readout .unit { color: var(--text-faint); font-size: var(--fs-sm); }
.learn .lverdict { font-size: var(--fs-sm); color: var(--text-dim); margin: var(--sp-2) 0 0; }
.learn .lverdict.clip { color: var(--warn); font-weight: var(--w-semi); }

/* ---- sliders ---- */
.learn input[type=range] {
    -webkit-appearance: none; appearance: none; width: 100%; height: 28px;
    background: transparent; cursor: pointer; margin: 0; padding: 0; border: 0;
    min-height: 28px;
}
.learn input[type=range]::-webkit-slider-runnable-track {
    height: 6px; border-radius: 999px; background: var(--border);
}
.learn input[type=range]::-moz-range-track {
    height: 6px; border-radius: 999px; background: var(--border);
}
.learn input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%;
    background: var(--accent); border: 3px solid var(--surface); margin-top: -8px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.learn input[type=range]::-moz-range-thumb {
    width: 22px; height: 22px; border-radius: 50%; background: var(--accent);
    border: 3px solid var(--surface); box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.learn .slider-row { display: grid; gap: var(--sp-1); }
.learn .slider-row label { font-size: var(--fs-sm); color: var(--text-dim); }
.learn .slider-row .ends {
    display: flex; justify-content: space-between; font-size: var(--fs-xs);
    color: var(--text-faint);
}
.learn .checkline {
    display: flex; gap: var(--sp-3); align-items: center; font-size: var(--fs-sm);
    color: var(--text-dim);
}
.learn .checkline input { width: 18px; height: 18px; min-height: 0; accent-color: var(--accent); }

/* ---- the band cards ---- */
.learn .bands { display: grid; gap: var(--sp-3); }
.learn .band {
    display: grid; grid-template-columns: 12px 1fr; gap: var(--sp-4);
    border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden;
    background: var(--surface-2);
}
.learn .band .stripe { background: var(--bc); }
.learn .band .in { padding: var(--sp-4) var(--sp-4) var(--sp-4) 0; display: grid; gap: var(--sp-2); }
.learn .band .hd { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.learn .band .nm {
    font-family: var(--font-display); font-weight: var(--w-semi);
    font-size: var(--fs-lg); color: var(--bc);
}
.learn .band .rng { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-faint); }
.learn .band p { font-size: var(--fs-sm); color: var(--text-dim); margin: 0; }
.learn .band .facts { display: flex; gap: var(--sp-5); flex-wrap: wrap; font-size: var(--fs-xs); }
.learn .band .facts div { color: var(--text-faint); }
.learn .band .facts b {
    display: block; font-family: var(--mono); font-size: var(--fs-md); color: var(--text);
}
.learn .band .eg { font-size: var(--fs-xs); color: var(--text-faint); }

/* ---- donut and legend ---- */
.learn .mix { display: grid; grid-template-columns: 200px 1fr; gap: var(--sp-5); align-items: center; }
@media (max-width: 640px) { .learn .mix { grid-template-columns: 1fr; } }
.learn .legend { display: grid; gap: var(--sp-2); }
.learn .legend .row {
    display: grid; grid-template-columns: 10px 1fr auto auto; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm);
}
.learn .legend .sw { width: 10px; height: 10px; border-radius: 3px; }
.learn .legend .pc, .learn .legend .dol {
    font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--text-dim);
}
.learn .dlab { font-family: var(--mono); }

/* ---- the refuel simulator ---- */
.learn .simgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
@media (max-width: 760px) { .learn .simgrid { grid-template-columns: 1fr; } }
.learn .setrow {
    display: grid; grid-template-columns: 80px 1fr 78px; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm);
}
.learn .setrow.topline, .learn .totline {
    border-top: 1px solid var(--border); padding-top: var(--sp-3);
}
.learn .totline { display: flex; justify-content: space-between; align-items: baseline; }
.learn .totline b { font-size: var(--fs-lg); }
.learn .setrow .bn { font-weight: var(--w-semi); }
.learn .setrow .dv {
    font-family: var(--mono); font-size: var(--fs-xs); text-align: right;
    color: var(--text-dim);
}
.learn .drift { display: grid; gap: var(--sp-3); }
.learn .drift .r {
    display: grid; grid-template-columns: 76px 1fr 128px; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm);
}
@media (max-width: 560px) { .learn .drift .r { grid-template-columns: 64px 1fr 112px; } }
.learn .lgauge {
    position: relative; height: 22px; background: var(--surface-2);
    border-radius: var(--r-sm); overflow: hidden;
}
.learn .lgauge .have { position: absolute; inset: 0 auto 0 0; border-radius: var(--r-sm); }
.learn .lgauge .tgt {
    position: absolute; top: -5px; bottom: -5px; width: 3px; background: var(--text);
    border-left: 1px solid var(--surface); border-right: 1px solid var(--surface);
    border-radius: 1px;
}
.learn .drift .d {
    font-family: var(--mono); font-size: var(--fs-xs); text-align: right; line-height: 1.35;
}
.learn .drift .under { color: var(--gain); }
.learn .drift .over { color: var(--loss); }
.learn .drift .flat { color: var(--text-faint); }
.learn .result {
    border: 1px solid var(--accent-dim); background: var(--surface-2);
    border-radius: var(--r-md); padding: var(--sp-4); display: grid; gap: var(--sp-2);
}
.learn .result .hd {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .1em;
    color: var(--accent); font-weight: var(--w-bold);
}
.learn .lalloc {
    display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2);
    align-items: baseline; font-size: var(--fs-md);
}
.learn .lalloc .bn { font-weight: var(--w-semi); }
.learn .lalloc .amt {
    font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: var(--w-semi);
}
.learn .lalloc.zero { opacity: .4; }

/* ---- the ticker picker ---- */
.learn .lpicker { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.learn .pickrow { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-2); }
.learn .conepick { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.learn .pick {
    font: inherit; font-family: var(--mono); font-weight: var(--w-semi);
    font-size: var(--fs-sm); cursor: pointer; border: 1px solid var(--border);
    background: var(--surface-2); color: var(--text); border-radius: 999px;
    padding: var(--sp-2) var(--sp-4); min-height: 38px;
    transition: border-color .15s, background .15s;
}
.learn .pick:hover { border-color: var(--accent-dim); }
.learn .pick[aria-pressed="true"] { border-color: var(--pc); }
.learn .pick .dot {
    display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    background: var(--pc); margin-right: 6px; vertical-align: middle;
}
.learn .chosen { display: grid; gap: var(--sp-2); }
.learn .chosen .c {
    display: grid; grid-template-columns: 66px 1fr auto auto; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm); border-bottom: 1px solid var(--border);
    padding-bottom: var(--sp-2);
}
.learn .chosen .c .t { font-family: var(--mono); font-weight: var(--w-semi); }
.learn .chosen .c .bn { font-weight: var(--w-semi); }
.learn .chosen .c .sc { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-faint); }
.learn .empty { color: var(--text-faint); font-size: var(--fs-sm); font-style: italic; }

/* ---- the small SVG charts ---- */
.learn .mini { display: block; width: 100%; height: auto; }
.learn .mini .ax { stroke: var(--text-faint); stroke-width: 1; fill: none; opacity: .55; }
.learn .mini text { font-family: var(--mono); font-size: 8px; fill: var(--text-faint); }
.learn .mini text.em { fill: var(--text); font-weight: var(--w-bold); }
.learn .chartbox {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md);
    padding: var(--sp-3); display: grid; gap: var(--sp-2); max-width: 340px;
}
.learn .chartbox.wide { max-width: 460px; }
.learn .chartbox.full { max-width: none; }
.learn .chartcap { font-size: var(--fs-xs); color: var(--text-faint); line-height: 1.45; }
.learn .chartcap b { color: var(--text-dim); }
.learn .ladder { display: grid; grid-template-columns: 1fr; gap: var(--sp-2); }

/* ---- the outcome cones ---- */
.learn .conerow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
@media (max-width: 700px) { .learn .conerow { grid-template-columns: 1fr; } }
.learn .conehd { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.learn .conehd .nm { font-family: var(--mono); font-weight: var(--w-bold); }
.learn .conehd .bd { font-size: var(--fs-xs); font-weight: var(--w-semi); }

/* ---- the quality head-to-head ---- */
.learn .h2h { display: grid; gap: var(--sp-3); }
.learn .h2h .hrow {
    display: grid; grid-template-columns: 1fr 56px 56px; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm);
}
.learn .h2h .hh {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em;
    color: var(--text-faint); font-weight: var(--w-semi); text-align: center;
}
.learn .h2h .w { color: var(--text-faint); font-family: var(--mono); font-size: var(--fs-xs); }
.learn .h2h .sc {
    font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: center;
    border-radius: var(--r-sm); padding: 2px 0;
}
.learn .h2h .win { background: var(--surface-2); font-weight: var(--w-bold); color: var(--gain); }
.learn .h2h .tot { border-top: 1px solid var(--border); padding-top: var(--sp-2); font-weight: var(--w-semi); }

/* ---- the compounding comparison ---- */
.learn .cmpgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
@media (max-width: 760px) { .learn .cmpgrid { grid-template-columns: 1fr; } }
.learn .cmpkey { display: grid; gap: var(--sp-2); }
.learn .cmpkey .row {
    display: grid; grid-template-columns: 12px 1fr auto; gap: var(--sp-3);
    align-items: center; font-size: var(--fs-sm);
}
.learn .cmpkey .dash { height: 3px; border-radius: 2px; }
.learn .cmpkey .sub { font-size: var(--fs-xs); color: var(--text-faint); }
.learn .cmpkey .v { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: var(--w-semi); }


/* Anything wider than the measure still scrolls in its own box rather than taking the
   body sideways with it — the same contract as `.scroll-x`, applied automatically here
   so a table added later cannot reintroduce the bug. */
.learn .card { overflow-x: auto; }

@media print {
    /* Chrome, first. `header.top` is hidden by report.css only inside `.report`, and
       this page is not that. */
    header.top, .noprint, .learn nav.card { display: none !important; }

    body { background: #fff; color: #000; font-size: 10.5pt; }
    .wrap { padding: 0; max-width: none; }

    /* A card is a screen affordance. On paper the border and fill are ink spent on
       nothing, and the rule below the heading does the same job for free. */
    .learn .card { background: none; border: 0; box-shadow: none;
                   padding: 0; margin: 0 0 var(--sp-5); overflow: visible; }
    .learn h2 { border-top: 1px solid #999; padding-top: 6pt; break-after: avoid-page; }
    .learn h3 { break-after: avoid-page; }
    .learn .card { break-inside: avoid-page; }
    .learn table { break-inside: auto; }
    .learn thead { display: table-header-group; }
    .learn tr { break-inside: avoid; }

    /* The disclosure triangle is a control that does nothing on paper, and by the time
       this runs the block has been forced open by the beforeprint handler. */
    .learn details { break-inside: avoid-page; }
    .learn summary { list-style: none; font-weight: var(--w-semi); }
    .learn summary::-webkit-details-marker { display: none; }

    /* `.tiny` is 0.72rem on screen, which is around 7pt on paper — under the size this
       page is meant to be read and annotated at. */
    .learn .tiny { font-size: 9pt; color: #333; }
    /* Same reason as `.tiny` above: `--text-dim` is a screen colour and prints too
       faint to read the question a heading is answering. */
    .learn .section > h2 .q { font-size: 9.5pt; color: #333; }
    .learn a { color: inherit; text-decoration: none; }

    /* ---- the walkthrough on paper ------------------------------------------
       Sam prints this page and writes on it, so paper is not a degraded copy of
       the screen: it is the version with EVERYTHING open. All nine steps, both
       reading levels, every drawer, one step per sheet. The two levels get a
       label each — side by side they read as a brief and its expansion, which is
       exactly what a person studying the method wants and exactly what would be
       clutter on screen.

       A `hidden` drawer is an attribute, and no stylesheet can reach inside one;
       `display: grid !important` beats the browser's own `[hidden]` rule, and
       learn.js opens them for the print as well so the belt has a brace. */
    .rail, .learn .lnav, .learn .lnav, .learn .lswitch, .learn .trythis, .learn .lseg,
    .learn .lnote-nav, .learn #simReset, .learn #pickClear, .learn #pickCalm,
    .learn #pickBold, .learn #conePick, .learn .lpicker { display: none !important; }

    .learn .step { display: block !important; break-inside: auto; page-break-after: always; }
    .learn .step:last-of-type { page-break-after: auto; }

    .learn .plain, .learn .deep { display: block !important; }
    .learn .plain::before { content: "In brief — "; font-weight: var(--w-bold); }
    .learn .deep::before { content: "In full — "; font-weight: var(--w-bold); }

    .learn .rowbody { display: grid !important; }
    /* A printed page has no disclosure triangles — the reader studying this on paper
       wants every walkthrough open, which is the same argument that prints both
       reading levels side by side. */
    .learn details.story > ol { display: grid !important; }
    .learn details.story > summary { margin-bottom: var(--sp-3); }
    /* A drawer's tinted button is a "press me" affordance and costs ink for nothing. */
    .learn .rows > div > button { background: none; }
    .learn .ltip { display: none !important; }
    .learn .tk { color: inherit; }
    .learn .card.sub, .learn .chartbox, .learn .result, .learn .ruler-wrap,
    .learn .band, .learn .lgauge { background: none; }
    .learn .chartbox, .learn .band { break-inside: avoid; }
    .learn .lnote { font-size: 9pt; color: #333; }
    .learn .masthead { margin-bottom: 12pt; }
}

/* ---------------------------------------------------------------- the dossier
 * One ticker, every number, and the arithmetic behind each one — `research/_dossier.html`.
 * Not scoped to `.report`, because the same body renders on screen and on paper; the
 * print-only adjustments live at the bottom of this block behind `@media print`.
 */
.dossier { display: grid; gap: var(--sp-4); }

.dhead { display: grid; gap: var(--sp-2); padding-bottom: var(--sp-3);
         border-bottom: 1px solid var(--border); }
.dhead h1 { font-family: var(--font-display); font-size: var(--fs-xxl); margin: 0;
            letter-spacing: -0.01em; }
.dname { margin: 0; color: var(--text-dim); font-size: var(--fs-md); }
.dhead-scores { display: flex; flex-wrap: wrap; gap: var(--sp-5); }
.dscore { display: grid; gap: 2px; }
.dscore-label { font-size: var(--fs-xs); color: var(--text-faint);
                text-transform: uppercase; letter-spacing: .06em; }
.dscore-value { font-family: var(--mono); font-size: var(--fs-xl);
                font-weight: var(--w-semi); font-variant-numeric: tabular-nums; }
.dscore-sub { display: flex; align-items: center; gap: var(--sp-2); }
.dasof { margin: 0; color: var(--text-faint); }

.dsummary p { margin: 0; line-height: 1.65; }
.dsummary em { font-style: normal; font-weight: var(--w-semi); color: var(--accent); }

/* The derivation tables. `tabular-nums` matters more than it looks: a column of figures
 * that do not line up cannot be scanned for the one that is out of place, which is the
 * only reason to print the working at all. */
table.deriv { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.deriv th, table.deriv td { padding: var(--sp-2) var(--sp-3);
                                 border-bottom: 1px solid var(--border);
                                 text-align: left; vertical-align: top; }
table.deriv thead th { font-size: var(--fs-xs); color: var(--text-faint);
                       text-transform: uppercase; letter-spacing: .05em;
                       font-weight: var(--w-semi); white-space: nowrap; }
table.deriv td.num, table.deriv th.num { text-align: right; font-family: var(--mono);
                                         font-variant-numeric: tabular-nums;
                                         white-space: nowrap; }
table.deriv tbody th { font-weight: var(--w-medium); }
table.deriv tfoot th, table.deriv tfoot td { border-top: 2px solid var(--border);
                                             border-bottom: none;
                                             font-weight: var(--w-semi); }
table.deriv.compact th, table.deriv.compact td { padding: var(--sp-1) var(--sp-3); }
table.deriv tr.row-missing { opacity: .55; }
table.deriv tr.is-me { background: color-mix(in srgb, var(--accent) 12%, transparent); }
table.deriv tr.is-me th { font-weight: var(--w-bold); }

/* The gloss: the plain-English definition that CLAUDE.md requires in the same breath as
 * the term. Block-level under the label rather than a `title` attribute, because a
 * tooltip does not print and does not exist on a touch screen. */
.gloss { display: block; font-size: var(--fs-xs); line-height: var(--lh-xs);
         color: var(--text-dim); font-weight: var(--w-normal); max-width: 46ch;
         margin-top: 2px; }
.gloss em { font-style: italic; color: var(--text-faint); }

.arith { margin: var(--sp-3) 0 0; }
.arith code { font-family: var(--mono); font-size: var(--fs-sm);
              background: var(--surface-2); padding: var(--sp-1) var(--sp-2);
              border-radius: var(--r-sm); border: 1px solid var(--border); }

.pillar { margin-top: var(--sp-4); padding-top: var(--sp-3);
          border-top: 1px solid var(--border); }
.pillar-head { display: flex; align-items: baseline; gap: var(--sp-3);
               flex-wrap: wrap; }
.pillar-head h3 { margin: 0; font-size: var(--fs-lg); }
.pillar-weight { color: var(--text-faint); }
.pillar-score { margin-left: auto; font-family: var(--mono);
                font-size: var(--fs-lg); font-weight: var(--w-semi);
                font-variant-numeric: tabular-nums; }

ul.facts { margin: 0; padding-left: 1.1rem; display: grid; gap: var(--sp-2); }
ul.facts li { line-height: 1.55; }
/* A hard caveat is one that changes what the reader may conclude; a soft one qualifies a
 * figure without invalidating it. Colour is a second channel, never the only one — each
 * note says its own severity in words. */
li.note-hard { border-left: 3px solid var(--warn); padding-left: var(--sp-3);
               list-style: none; margin-left: -1.1rem; }
li.note-soft { color: var(--text-dim); }
li.note-always { color: var(--text-faint); font-size: var(--fs-sm); }

.band-core      { background: color-mix(in srgb, var(--band-core) 22%, transparent);
                  border-color: var(--band-core); }
.band-balanced  { background: color-mix(in srgb, var(--band-balanced) 22%, transparent);
                  border-color: var(--band-balanced); }
.band-growth    { background: color-mix(in srgb, var(--band-growth) 22%, transparent);
                  border-color: var(--band-growth); }
.band-satellite { background: color-mix(in srgb, var(--band-satellite) 22%, transparent);
                  border-color: var(--band-satellite); }

.grade-priority, .grade-eligible { background: color-mix(in srgb, var(--gain) 22%, transparent);
                                   border-color: var(--gain); }
.grade-hold     { background: var(--surface-2); }
.grade-review, .grade-trim { background: color-mix(in srgb, var(--warn) 22%, transparent);
                             border-color: var(--warn); }
.grade-exit     { background: color-mix(in srgb, var(--loss) 22%, transparent);
                  border-color: var(--loss); }

.dplant .plant-form { display: grid; gap: var(--sp-2); margin-top: var(--sp-3);
                      max-width: 46rem; }
.dplant select { max-width: 24rem; }

.report-mast { margin-bottom: var(--sp-5); }
.report-mast h1 { font-family: var(--font-display); margin: 0 0 var(--sp-2); }

@media print {
  /* Sections break as units — a derivation table split across two sheets with its total
     stranded on the second is the one failure that makes the working unreadable. */
  .dossier .card, .pillar, table.deriv { break-inside: avoid; }
  .dossier .card { border: 1px solid #ccc; box-shadow: none; }
  .gloss { color: #444; }
  .toolbar, .rctl { display: none !important; }
}

/* The management score on a portfolio card.
   The number is the thing being read, so it is the only large element; everything beside
   it is the sentence that stops it being misread. Colour is a hint and never the message
   — the words say "ahead" or "behind" too, because a red number means nothing to somebody
   who cannot see red, and this is the one figure on the page a person acts on. */
.pscore { display: flex; gap: var(--sp-3); align-items: flex-start;
          padding-top: var(--sp-2); border-top: 1px solid var(--border); }
.pscore-num { font-size: 1.5rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums;
              white-space: nowrap; }
.pscore.ahead .pscore-num { color: var(--gain); }
.pscore.behind .pscore-num { color: var(--loss); }
.pscore-words { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pscore-words strong { font-size: .8rem; font-weight: 600; }
.pscore-words .tiny { color: var(--text-dim); }
.pscore-words .tiny.idle { color: var(--accent); }

/* ============================================================ research centre
 *
 * Added 2026-09-02 with the Research redesign (docs/tasks/RESEARCH_CENTER.md). The page
 * that used to open on three hardcoded tickers now opens on a search box, so the box is
 * the only large thing on it and everything else is a way of not having to type.
 */

/* The front door. One field, sized so it reads as the point of the page rather than as a
 * filter above a table — which is what it looked like when it sat over the lane grid. */
.rsearch { padding: var(--sp-5) var(--sp-4); margin-bottom: var(--sp-4); }
.rsearch input { font-size: var(--fs-lg); padding: var(--sp-3); }
.rsearch p { margin: var(--sp-3) 0 0; max-width: 60ch; }

.chiprow { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
           margin: var(--sp-2) 0 0; }
.chiprow .chip sup { font-size: .65em; margin-left: 2px; opacity: .8; }

/* A list is a thing you open when you want it, not three buttons across the top. */
.rlist + .rlist { margin-top: var(--sp-3); }
.rlist summary { cursor: pointer; padding: var(--sp-2) 0; }
.rlist summary::marker { color: var(--text-faint); }

ul.rmatches { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: grid;
              gap: var(--sp-1); }
ul.rmatches li { padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
ul.rmatches b { font-family: var(--mono); margin-right: var(--sp-2); }
ul.rmatches .tiny { margin-left: var(--sp-3); color: var(--text-faint); }

/* Identity: industry first, because it is the fact that was missing. Each facet is its own
 * chip so the eye can take them one at a time rather than parsing a run of dot separators. */
.dident { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
          margin: var(--sp-1) 0 0; }
.dfacet { border: 1px solid var(--border); border-radius: var(--r-sm);
          padding: 1px var(--sp-2); color: var(--text-dim); }

/* The earnings line. It only earns emphasis when the date is close: a report next week
 * changes a decision made today, and one in three months does not. */
.dearn { margin: var(--sp-2) 0 0; color: var(--text-dim); font-size: var(--fs-sm);
         line-height: 1.55; max-width: 70ch; }
.dearn .tiny { color: var(--text-faint); }
.dearn.soon { color: var(--text); border-left: 3px solid var(--warn);
              padding-left: var(--sp-3); }

/* The derivations, collapsed. They are reference material — checked when a figure is
 * doubted, not read top to bottom — so they open on demand and the summary says what is
 * inside, because a closed section with an unhelpful label is just a hidden section. */
.dwork > summary { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap;
                   align-items: baseline; gap: var(--sp-3); }
.dwork > summary::-webkit-details-marker { display: none; }
.dwork > summary h2 { display: inline; margin: 0; }
.dwork > summary::before { content: "▸"; color: var(--text-faint); }
.dwork[open] > summary::before { content: "▾"; }
.dwork > summary:hover h2 { color: var(--accent); }

/* Headlines. The demoted ones stay legible — a round-up is sometimes the news — but they
 * are visibly a second tier, so the eye reaches the stories about this company first. */
ul.hlist { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid;
           gap: var(--sp-3); }
.hitem { padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border); }
.hitem a { line-height: 1.45; }
.hitem p { margin: 2px 0 0; display: flex; flex-wrap: wrap; gap: var(--sp-2);
           align-items: center; }
.hitem.demoted { opacity: .68; }
.hitem.demoted a { font-weight: var(--w-normal); }
.tag.warn { border-color: var(--warn); color: var(--warn); }

/* Three columns of headlines on the comparison, one on a phone. */
.rvs-news { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 60rem) {
  .rvs-news { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
}
.rvs-newscol h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-md); font-family: var(--mono); }

@media print {
  .rsearch, .rlist, .chiprow { display: none !important; }
  /* A collapsed section must print open: the whole reason the sheet exists is that the
     working travels with the numbers. */
  .dwork > summary::before { content: ""; }
  details.dwork > * { display: block !important; }
}

/* The risk ruler — the chart that explains the band.
 *
 * Deliberately not a plotting library and not an SVG: it is one bar with one marker, it
 * must print, and it must survive a screen reader. The `aria-label` on the bar carries the
 * whole sentence, and the sentence is under the bar in text as well — a marker on its own
 * is a picture, and a picture with nothing to read is what was wrong with the panels this
 * replaces. */
.druler .ruler { margin-top: var(--sp-4); }
.ruler-head { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: baseline; }
.ruler-head .tiny { max-width: 62ch; }
.ruler-bar { position: relative; height: 10px; margin: var(--sp-2) 0 var(--sp-1);
             border-radius: var(--r-sm); border: 1px solid var(--border);
             background: linear-gradient(to right,
                         color-mix(in srgb, var(--gain) 22%, transparent),
                         var(--surface-2) 45%,
                         color-mix(in srgb, var(--warn) 30%, transparent)); }
.ruler-mark { position: absolute; top: -4px; width: 3px; height: 18px;
              background: var(--text); border-radius: 2px; transform: translateX(-1.5px); }
.ruler-ends { display: flex; justify-content: space-between; gap: var(--sp-3);
              color: var(--text-faint); font-family: var(--mono); }
.ruler-said { font-family: var(--font-body); color: var(--text-dim); text-align: center;
              flex: 1; }
.ruler-said b { color: var(--text); }
.ruler.missing { opacity: .6; }
.ruler.missing p { margin: var(--sp-1) 0 0; max-width: 62ch; }
@media print { .ruler-bar { background: #eee; } .ruler-mark { background: #000; } }

/* The call. It is the output of the page, so it gets the only coloured panel on it —
 * and the colour is a second channel, never the message: the verb is in the sentence.
 * `none` is deliberately unstyled, because a refusal dressed as a recommendation would be
 * the worst thing this page could do. */
.dcall { border-left: 4px solid var(--border); }
.dcall-buy, .dcall-add   { border-left-color: var(--gain); }
.dcall-hold              { border-left-color: var(--accent); }
.dcall-review, .dcall-trim { border-left-color: var(--warn); }
.dcall-exit              { border-left-color: var(--loss); }
.dcall-head { font-size: var(--fs-lg); font-weight: var(--w-semi); margin: 0 0 var(--sp-2);
              line-height: 1.4; }
.dcall-size { margin: 0 0 var(--sp-3); max-width: 72ch; line-height: 1.6;
              color: var(--text-dim); }
.dcall ul.facts { max-width: 76ch; }

/* Notes. Plain on purpose — it is a place to write, not a feature to admire. */
.dnote-form { display: grid; gap: var(--sp-2); max-width: 60rem; margin-bottom: var(--sp-4); }
.dnote-form textarea { width: 100%; font: inherit; padding: var(--sp-3);
                       border: 1px solid var(--border); border-radius: var(--r-sm);
                       background: var(--surface-2); color: var(--text); resize: vertical; }
.dnote-form button { justify-self: start; }
ul.dnote-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-4); }
ul.dnote-list li { border-left: 3px solid var(--border); padding-left: var(--sp-3); }
.dnote-body { margin: 0; white-space: pre-wrap; line-height: 1.6; max-width: 76ch; }
.linkish { background: none; border: 0; padding: 0; color: var(--text-faint);
           font: inherit; cursor: pointer; text-decoration: underline; }
.linkish:hover { color: var(--loss); }
@media print { .dnote-form, .linkish { display: none !important; } }

/* ============================================================================
   The course board — docs/tasks/COURSE.md. Phase 1: the today strip, the two
   dials and the targets table; zones 2–4 drawn dimmed with the phase that builds
   them, never hidden. Colours are the band tokens and the status tokens the rest
   of the app already uses; the zone frame is the only new shape.
   ============================================================================ */
.board { display: grid; gap: var(--sp-5); }

.board-today {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden;
}
@media (max-width: 900px) { .board-today { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.board-today > div {
  padding: var(--sp-3) var(--sp-4); border-right: 1px solid var(--border);
  display: grid; gap: 2px; align-content: start;
}
.board-today > div:last-child { border-right: 0; }
.board-today .v { font-family: var(--mono); font-size: var(--fs-xl); font-weight: var(--w-medium); line-height: 1.15; }
.board-today .v small { font-family: var(--font); font-size: var(--fs-sm); color: var(--text-dim); font-weight: var(--w-normal); }
.board-today .s { font-size: var(--fs-xs); color: var(--text-dim); }
.adj {
  display: inline-block; padding: 0 var(--sp-2); border-radius: 4px;
  font-size: 0.72rem; font-weight: var(--w-semi);
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
}

.zone { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); overflow: hidden; }
.zone > header {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.zone > header .zn {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--accent); color: #fff; font-family: var(--mono); font-weight: var(--w-bold);
  display: grid; place-items: center; font-size: var(--fs-sm);
}
.zone > header h2 { margin: 0; font-size: var(--fs-lg); }
.zone > header .what { margin-left: auto; text-align: right; max-width: 60ch; font-size: var(--fs-sm); color: var(--text-dim); }
.zone.planned { opacity: .55; }
.zone.planned > header .zn { background: var(--text-faint); }
.zone .zbody { padding: var(--sp-4); display: grid; gap: var(--sp-4); }

.dials { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 900px) { .dials { grid-template-columns: 1fr; } }
.dial { display: grid; gap: var(--sp-2); align-content: start; padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); }
.dial .dhead { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
.dial .big { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; font-family: var(--mono); font-size: var(--fs-xxl); line-height: 1.1; font-weight: var(--w-medium); }
.dial .big .dname { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: var(--w-semi); color: var(--accent); }
.dial .sub { font-size: var(--fs-sm); color: var(--text-dim); }
.dial .tolhint.out, .dial .big .dname.out { color: var(--loss); font-weight: var(--w-semi); }
.dial input[type=range].out { outline: 2px solid var(--loss); outline-offset: 6px; border-radius: var(--r-sm); accent-color: var(--loss) !important; }
.dial .tolhint.out b { color: var(--loss); }
.dial input[type=range] { width: 100%; }
.dial .scale { display: flex; justify-content: space-between; font-family: var(--mono); font-size: 0.68rem; color: var(--text-faint); }
.dial .dnote { font-size: var(--fs-sm); color: var(--text-dim); border-left: 3px solid var(--border); padding: 2px var(--sp-3); }
.dial .dnote b { color: var(--text); font-family: var(--mono); font-weight: var(--w-medium); }
.dial .dnote.bad { border-color: var(--loss); } .dial .dnote.warn { border-color: var(--warn); }
.dial .dactions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.locked { font-size: 0.72rem; color: var(--warn); border: 1px dashed var(--warn); border-radius: 4px; padding: 1px var(--sp-2); }

.btargets { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.btargets th { text-align: left; font-weight: var(--w-medium); color: var(--text-faint); font-size: 0.7rem; letter-spacing: .06em; text-transform: uppercase; padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); }
.btargets td { padding: var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: middle; }
.btargets td.num, .btargets th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.btargets td.arrow { color: var(--text-faint); text-align: center; width: 2rem; }
.btargets .up { color: var(--gain); } .btargets .down { color: var(--loss); }
.sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: var(--sp-2); vertical-align: 0; }
.sw.core { background: var(--band-core); } .sw.balanced { background: var(--band-balanced); }
.sw.growth { background: var(--band-growth); } .sw.satellite { background: var(--band-satellite); }
.sw.cash { background: var(--band-none); }
.whatif { display: flex; gap: var(--sp-3); align-items: end; flex-wrap: wrap; font-size: var(--fs-sm); }
.whatif label { display: grid; gap: 2px; color: var(--text-dim); }
.whatif input[type=number] { width: 8rem; font-family: var(--mono); text-align: right; }
.solve { font-size: var(--fs-sm); color: var(--text-dim); border-left: 3px solid var(--warn); padding: 2px var(--sp-3); }
.solve b { color: var(--text); font-family: var(--mono); font-weight: var(--w-medium); }
.eyebrow { font-size: 0.72rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint); font-weight: var(--w-semi); }
.btn.secondary { background: var(--surface-2); color: var(--text-dim); border: 1px solid var(--border); }
.btn.secondary:hover { color: var(--text); border-color: var(--text-faint); }

/* ---- phase 2: the trail on the left, zone 2, the saved course ---------------- */
.board-grid { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: var(--sp-5); align-items: start; }
/* Under 1280px (a laptop at 125% zoom) the trail goes above the board so the band
   tables get the whole width; the Add and Sell boxes were the first thing cut off. */
@media (max-width: 1280px) { .board-grid { grid-template-columns: 1fr; } .trail { position: static !important; } .trail-form { grid-template-columns: 1fr 1fr; display: grid; gap: var(--sp-3) var(--sp-5); } .trail-form > .tname, .trail-form > .trail-steps { grid-column: 1 / -1; } }
@media (max-width: 760px) { .trail-form { grid-template-columns: 1fr; } }
.trail {
  position: sticky; top: var(--sp-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow); padding: var(--sp-3) var(--sp-4); display: grid; gap: var(--sp-3);
}
.trail-form { display: grid; gap: var(--sp-3); }
.trail .tname { font: inherit; font-weight: var(--w-semi); background: transparent; border: 0; border-bottom: 1px dashed var(--border); color: var(--text); padding: 2px 0; width: 100%; }
.trail .tname:focus { outline: 0; border-bottom-color: var(--accent); }
.trail .tnote { font: inherit; font-size: var(--fs-sm); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text); padding: var(--sp-2); width: 100%; resize: vertical; }
.trail-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); counter-reset: st; }
.tstep {
  display: grid; grid-template-columns: 22px 1fr auto; gap: var(--sp-2); align-items: start;
  font-size: var(--fs-sm); padding: var(--sp-2); border-radius: var(--r-sm);
  background: var(--surface-2); border-left: 3px solid var(--sc, var(--border));
}
.tstep::before { counter-increment: st; content: counter(st); font-family: var(--mono); color: var(--text-faint); font-size: var(--fs-xs); padding-top: 1px; }
.tstep .k { font-weight: var(--w-semi); } .tstep .d { color: var(--text-dim); font-size: var(--fs-xs); }
.tstep .x { color: var(--text-faint); text-decoration: none; font-size: 1rem; line-height: 1; padding: 0 2px; }
.tstep .x:hover { color: var(--loss); }
.tstep.deposit { --sc: var(--gain); } .tstep.withdraw { --sc: var(--loss); } .tstep.sell { --sc: var(--warn); } .tstep.buy { --sc: var(--accent); }
.trail-steps .empty { font-size: var(--fs-sm); color: var(--text-faint); padding: var(--sp-3); border: 1px dashed var(--border); border-radius: var(--r-sm); text-align: center; }
.tfoot { display: grid; gap: var(--sp-1); border-top: 1px solid var(--border); padding-top: var(--sp-2); font-size: var(--fs-sm); }
.tfoot .l { display: flex; justify-content: space-between; } .tfoot .l b { font-family: var(--mono); font-weight: var(--w-medium); }
.tfoot .l.warn { color: var(--warn); }
.saved-list { display: grid; gap: var(--sp-2); border-top: 1px solid var(--border); padding-top: var(--sp-3); }
.saved-row { display: grid; gap: 1px; padding: var(--sp-2); border-radius: var(--r-sm); background: var(--surface-2); text-decoration: none; color: inherit; }
.saved-row:hover { outline: 1px solid var(--accent); text-decoration: none; }
.saved-row .n { font-weight: var(--w-semi); } .saved-row .m { font-size: var(--fs-xs); color: var(--text-dim); }
.st { display: inline-block; font-size: 0.7rem; padding: 0 var(--sp-2); border-radius: 999px; border: 1px solid var(--border); color: var(--text-dim); vertical-align: middle; }
.st-saved { border-color: var(--gain); color: var(--gain); } .st-superseded, .st-abandoned { border-style: dashed; }
.st-branched { border-color: var(--accent); color: var(--accent); } .st-executed { background: var(--gain); color: var(--bg); border-color: var(--gain); }

.money { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); align-items: start; }
@media (max-width: 900px) { .money { grid-template-columns: 1fr; } }
.mbox { display: grid; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); }
.mbox .row { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); }
.mbox .row .l { color: var(--text-dim); } .mbox .row .l b { color: var(--text); font-weight: var(--w-semi); }
.mbox input[type=number], .mbox input[type=text] { font-family: var(--mono); width: 8rem; text-align: right; }
.mbox input[type=text] { text-align: left; }
.mbox .hint { font-size: var(--fs-xs); color: var(--text-faint); }
.mbox .sum { display: grid; grid-template-columns: 1fr auto; gap: 3px var(--sp-3); font-size: var(--fs-sm); }
.mbox .sum .v { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; }
.mbox .sum .tot { border-top: 1px solid var(--border); padding-top: var(--sp-1); margin-top: 2px; font-weight: var(--w-semi); }
.mbox .sum .pos { color: var(--gain); } .mbox .sum .neg { color: var(--loss); }
.addbuy { display: grid; gap: var(--sp-2); border-top: 1px solid var(--border); padding-top: var(--sp-3); margin-top: var(--sp-2); }
.addbuy .row { justify-content: flex-start; }

/* the saved course, and its printable worklist */
.worklist .cactions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; margin: var(--sp-3) 0; }
.worklist .cnote { border-left: 3px solid var(--border); padding: 2px var(--sp-3); color: var(--text-dim); }
.worklist h2 { font-size: var(--fs-lg); margin: var(--sp-4) 0 var(--sp-2); }
table.legs { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.legs th { text-align: left; font-weight: var(--w-medium); color: var(--text-faint); font-size: 0.7rem; letter-spacing: .06em; text-transform: uppercase; padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); }
table.legs td { padding: var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: top; }
table.legs td.num, table.legs th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
table.legs .tick { width: 2.4rem; }
table.legs .box { display: inline-block; width: 16px; height: 16px; border: 1.5px solid var(--text-dim); border-radius: 3px; }
table.legs tr.sell td:nth-child(3) b { color: var(--warn); } table.legs tr.buy td:nth-child(3) b { color: var(--accent); }
.cgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 900px) { .cgrid { grid-template-columns: 1fr; } }
table.kv { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.kv td { padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); }
table.kv td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
@media print {
  .worklist .noprint, header.top, .flash { display: none !important; }
  .worklist { color: #000; }
  .worklist table.legs td, .worklist table.legs th, .worklist table.kv td { border-color: #999; }
  .worklist .box { border-color: #000; }
}

/* ---- phase 3: the bands and before/after ---------------------------------------- */
.bandrows { display: grid; gap: var(--sp-4); }
.bandrow { border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; border-left: 5px solid var(--bc); background: var(--surface); }
.bandrow > summary { list-style: none; cursor: pointer; display: grid; grid-template-columns: 150px 1fr 1fr 1.2fr 1fr 96px; gap: var(--sp-3); align-items: center; padding: var(--sp-3) var(--sp-4); }
.bandrow > summary::-webkit-details-marker { display: none; }
.bandrow > summary:hover { background: var(--surface-2); }
.bandrow > summary h3 { margin: 0; color: var(--bc); font-size: var(--fs-lg); }
.bandrow .k { display: block; font-size: 0.7rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); }
.bandrow .v { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: var(--fs-md); }
.bandrow .v.after { color: var(--warn); font-style: italic; font-size: var(--fs-sm); }
.bandrow .todo { font-weight: var(--w-semi); } .todo.add { color: var(--gain); } .todo.over { color: var(--loss); } .todo.ok { color: var(--text-dim); font-weight: var(--w-normal); }
.bandrow .chips { display: flex; flex-wrap: wrap; gap: 4px; }
.chip { font-family: var(--mono); font-size: 0.7rem; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); background: var(--surface-2); }
.chip.new { border-style: dashed; color: var(--warn); }
.bandrow .open { justify-self: end; font-size: var(--fs-sm); color: var(--accent); }
.bandrow[open] > summary .open::before { content: "Close ▴"; } .bandrow:not([open]) > summary .open::before { content: "Open ▾"; }
@media (max-width: 900px) { .bandrow > summary { grid-template-columns: 1fr 1fr; } }
.bandbody { border-top: 1px solid var(--border); padding: var(--sp-3) var(--sp-4); display: grid; gap: var(--sp-3); }
.bandbody h4 { margin: var(--sp-2) 0 0; font-size: 0.78rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); font-weight: var(--w-semi); }
table.hold { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.hold th { text-align: left; font-weight: var(--w-medium); color: var(--text-faint); font-size: 0.7rem; letter-spacing: .06em; text-transform: uppercase; padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); white-space: nowrap; }
table.hold td { padding: var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: middle; }
table.hold td.num, table.hold th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
table.hold .t { font-family: var(--mono); font-weight: var(--w-semi); } table.hold .t a { text-decoration: none; }
table.hold tr.cand td { color: var(--text-dim); } table.hold tr.cand .t { color: var(--text); }
table.hold input[type=number] { width: 6.5rem; font-family: var(--mono); text-align: right; }
.vtag { display: inline-block; font-size: 0.68rem; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); font-weight: var(--w-semi); white-space: nowrap; color: var(--text-dim); }
.vtag.v-priority, .vtag.v-eligible { color: var(--gain); border-color: var(--gain); }
.vtag.v-review { color: var(--warn); border-color: var(--warn); }
.vtag.v-trim, .vtag.v-exit { color: var(--loss); border-color: var(--loss); }
.vtag.v-index, .vtag.v-none { color: var(--text-faint); border-style: dashed; }
.cap { font-size: 0.78rem; } .cap.room { color: var(--gain); } .cap.at { color: var(--warn); } .cap.over { color: var(--loss); } .cap.exempt { color: var(--text-faint); }
.pickfoot { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); flex-wrap: wrap; font-size: var(--fs-sm); color: var(--text-dim); }
.reco { border: 1px dashed var(--loss); border-radius: var(--r-md); padding: var(--sp-3); display: grid; gap: var(--sp-1); font-size: var(--fs-sm); background: var(--surface-2); }
.reco .h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.reco .h b { font-size: var(--fs-md); }
.reco .why { color: var(--text-dim); }
.reco .math { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-dim); display: grid; grid-template-columns: auto 1fr; gap: 1px var(--sp-3); max-width: 460px; }
.reco .math b { color: var(--text); font-weight: var(--w-medium); text-align: right; }
.reco .wait { color: var(--warn); }
.reco .act { display: flex; gap: var(--sp-2); align-items: center; margin-top: var(--sp-1); flex-wrap: wrap; }
.ba { display: grid; gap: var(--sp-3); align-content: start; }
.crash { margin-top: var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-2); display: grid; gap: var(--sp-2); }
.crash h4 { margin: 0; }
.crash table.ba-t { background: var(--surface); }
.dial-pts { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--text-dim); }
.dial-pts li { display: grid; grid-template-columns: 1.4rem 1fr; gap: var(--sp-2); align-items: start; }
.dial-pts li > i { font-style: normal; font-size: 1rem; line-height: 1.3; text-align: center; }
.dial-pts li.now > i { color: var(--text-faint); } .dial-pts li.ask > i { color: var(--accent); }
.dial-pts li.ok > i { color: var(--gain); } .dial-pts li.warn > i { color: var(--loss); }
.dial-pts li.alt > i { color: var(--warn); } .dial-pts li.forces > i, .dial-pts li.bands > i { color: var(--text-dim); }
.dial-pts li.warn span > b:first-child { color: var(--loss); }
.dial-pts b { color: var(--text); font-family: var(--mono); font-weight: var(--w-medium); }
.dial-pts .moves { display: grid; gap: 2px; margin-top: var(--sp-1); }
.dial-pts .mv { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text); }
.dial-pts .mv em { font-style: normal; } .dial-pts .mv.down em { color: var(--loss); } .dial-pts .mv.up em { color: var(--gain); } .dial-pts .mv.flat em { color: var(--text-faint); }
.dial-pts .mv small { color: var(--text-faint); font-family: var(--font-body); }
.dial-how { margin-top: var(--sp-2); } .dial-how summary { cursor: pointer; color: var(--accent); }
/* the shower detent: press, then turn */
.dial .detent { justify-self: start; font: inherit; font-size: var(--fs-xs); padding: 2px var(--sp-2); border-radius: var(--r-sm); cursor: pointer;
                border: 1px dashed var(--border); background: transparent; color: var(--text-dim); }
.dial .detent:hover { border-color: var(--loss); color: var(--loss); }
.dial .detent.on { border-style: solid; border-color: var(--loss); color: var(--loss); font-weight: var(--w-semi); }
.dial .detent:disabled { opacity: .5; cursor: default; }
.ba .row { display: grid; grid-template-columns: 62px 1fr; gap: var(--sp-3); align-items: center; font-size: var(--fs-sm); }
.ba .row .lbl { color: var(--text-dim); } .ba .row.after .lbl { color: var(--warn); font-style: italic; }
.splitbar { display: flex; height: 22px; border-radius: 6px; overflow: hidden; gap: 2px; background: var(--track, var(--surface-2)); }
.splitbar > i { display: flex; align-items: center; justify-content: center; height: 100%; min-width: 0; font-family: var(--mono); font-size: 0.7rem; color: #fff; font-style: normal; overflow: hidden; white-space: nowrap; text-shadow: 0 0 2px rgba(0,0,0,.5); }
.splitbar > i.cash { background: var(--band-none); } .splitbar > i.core { background: var(--band-core); } .splitbar > i.balanced { background: var(--band-balanced); } .splitbar > i.growth { background: var(--band-growth); } .splitbar > i.satellite { background: var(--band-satellite); }
.ba .row.after .splitbar { outline: 1px dashed var(--warn); outline-offset: 1px; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: var(--fs-xs); color: var(--text-dim); }
.bagrid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--sp-4); }
@media (max-width: 900px) { .bagrid { grid-template-columns: 1fr; } }
table.ba-t { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.ba-t th { text-align: left; font-weight: var(--w-medium); color: var(--text-faint); font-size: 0.7rem; letter-spacing: .06em; text-transform: uppercase; padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); }
table.ba-t td { padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); }
table.ba-t td.num, table.ba-t th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
table.ba-t td.after { font-style: italic; color: var(--warn); }
.worse { margin: 0; padding: 0; display: grid; gap: var(--sp-1); font-size: var(--fs-sm); }
.worse li { list-style: none; padding-left: 14px; position: relative; }
.worse li::before { content: ""; position: absolute; left: 0; top: .55em; width: 7px; height: 7px; border-radius: 50%; }
.worse li.w::before { background: var(--loss); } .worse li.b::before { background: var(--gain); } .worse li.s::before { background: var(--text-faint); }

/* the saved course, graded against the next import */
.seen { display: inline-block; font-size: 0.68rem; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); white-space: nowrap; color: var(--text-dim); }
.seen.s-seen { color: var(--gain); border-color: var(--gain); }
.seen.s-partly-seen { color: var(--warn); border-color: var(--warn); }
.seen.s-more-than-asked { color: var(--warn); border-color: var(--warn); }
.seen.s-not-seen { color: var(--text-faint); border-style: dashed; }

/* ---- the board's own look: one primary colour, band colour where a band is meant,
   bigger figures, tinted zone headers. Scoped so nothing else on the site moves. ---- */
.board-grid .btn, .worklist .btn { background: var(--accent-dim); border-color: var(--accent-dim); color: #fff; font-weight: var(--w-semi); }
.board-grid .btn:hover, .worklist .btn:hover { filter: brightness(1.12); }
.board-grid .btn.secondary, .worklist .btn.secondary { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
.board-grid .btn.secondary:hover, .worklist .btn.secondary:hover { background: color-mix(in srgb, var(--accent) 22%, var(--surface)); filter: none; }
.board-grid .btn:disabled, .worklist .btn:disabled { opacity: .45; filter: none; }
.board-grid .btn.done { background: var(--surface-2); color: var(--text-dim); border-color: var(--border); }
.board-today .v { font-size: 1.75rem; }
.board-today .eyebrow { color: var(--accent); }
.zone > header { background: color-mix(in srgb, var(--accent) 9%, var(--surface)); }
.zone > header h2 { font-size: 1.25rem; letter-spacing: -0.01em; }
.zone > header .zn { width: 32px; height: 32px; font-size: var(--fs-md); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent); }
.dial { background: color-mix(in srgb, var(--surface-2) 60%, var(--surface)); }
.dial .big { font-size: 2.2rem; }
.bandrow { border-left-width: 6px; }
.bandrow > summary { background: color-mix(in srgb, var(--bc) 7%, var(--surface)); }
.bandrow > summary:hover { background: color-mix(in srgb, var(--bc) 14%, var(--surface)); }
.bandrow > summary h3 { font-size: 1.2rem; }
.bandrow .v { font-size: var(--fs-lg); }
.bandbody { background: color-mix(in srgb, var(--bc) 3%, var(--surface)); }
.trail { border-top: 4px solid var(--accent); }
.trail .tname { font-size: var(--fs-lg); }
.tstep { background: color-mix(in srgb, var(--sc, var(--border)) 8%, var(--surface-2)); }
.tstep .k { font-size: var(--fs-md); }
.saved-row:hover { background: color-mix(in srgb, var(--accent) 10%, var(--surface-2)); }
.mbox { background: color-mix(in srgb, var(--surface-2) 50%, var(--surface)); }
.mbox .sum .tot { font-size: var(--fs-lg); }
.bandrow .todo.add { text-shadow: 0 0 12px color-mix(in srgb, var(--gain) 40%, transparent); }
.bandrow .todo.over { text-shadow: 0 0 12px color-mix(in srgb, var(--loss) 40%, transparent); }
.vtag.v-priority, .vtag.v-eligible { background: color-mix(in srgb, var(--gain) 14%, transparent); }
.vtag.v-trim, .vtag.v-exit { background: color-mix(in srgb, var(--loss) 14%, transparent); }
.vtag.v-review { background: color-mix(in srgb, var(--warn) 14%, transparent); }
.reco { border-color: color-mix(in srgb, var(--loss) 60%, var(--border)); background: color-mix(in srgb, var(--loss) 5%, var(--surface-2)); }
.worklist h1 { font-size: 1.8rem; }

/* the theme toggle in the nav */
.theme-btn { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-sm); display: inline-flex; gap: var(--sp-2); align-items: center; }
.theme-btn svg { width: 16px; height: 16px; }
.theme-btn .when-dark, :root[data-theme="light"] .theme-btn .when-light { display: none; }
:root[data-theme="light"] .theme-btn .when-dark { display: inline; }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .theme-btn .when-light { display: none; } :root:not([data-theme="dark"]) .theme-btn .when-dark { display: inline; } }

/* a ticker opens its dossier in a layer over the board, never in place of it */
dialog.layer.ticker { width: min(72rem, calc(100vw - 2 * var(--sp-4))); max-height: min(60rem, calc(100dvh - 2 * var(--sp-4))); }
dialog.layer.ticker iframe { width: 100%; height: min(54rem, calc(100dvh - 8rem)); border: 0; background: var(--bg); display: block; }
.vtag.v-pinned { color: var(--warn); border-color: var(--warn); border-style: dashed; }

/* The band table must never push the page wider: grid items default to min-width:auto,
   which lets a wide table widen its column instead of scrolling inside it; and the
   site's global th/td nowrap kept every cell on one line. Both undone here, scoped. */
.board-grid > *, .board > *, .zone .zbody > *, .bandrows > .bandrow, .bandbody > * { min-width: 0; }
.bandrow { overflow: hidden; }
table.hold th, table.hold td { white-space: normal; }
table.hold td.num, table.hold th.num, table.hold .t { white-space: nowrap; }
table.hold input[type=number] { width: 5rem; }
table.hold .cap { display: inline-block; max-width: 16rem; font-size: 0.75rem; line-height: 1.3; }
table.hold td:first-child { min-width: 11rem; }
/* `display: grid` on dialog.layer overrode the browser's own `dialog:not([open]) { display: none }`,
   so a closed layer stayed on the page as a grey block. Sam, 2026-09-07. */
dialog.layer:not([open]) { display: none; }

/* ============================================================ the portfolio list
 *
 * Added 2026-09-09 with the lineage strip (docs/06_APP_SPEC.md §11z). Three things:
 * a filter row that replaced the idea of a heading per brokerage, a card that can hold
 * the what-ifs cut from it, and a mark that stops a hypothetical reading as real money.
 */

/* ---- narrow and reorder -----------------------------------------------------
 * Hidden until plist.js stamps `data-enhanced`. A control that does nothing when
 * clicked is worse than no control, so it does not exist without its script. */
.plist-ctl { display: none; }
.plist-ctl[data-enhanced] {
    display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
    margin: var(--sp-4) 0 var(--sp-3);
}
.plist-ctl-gap { flex: 1 1 auto; }
.chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px; min-height: 34px;
    border: 1px solid var(--border); border-radius: 999px;
    background: transparent; color: var(--text);
    font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.chip:hover { border-color: var(--accent); }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Pressed is not carried by colour alone: `aria-pressed` says it to a screen reader and
   the weight change says it to somebody who cannot separate the two backgrounds. */
.chip[aria-pressed="true"] {
    background: var(--accent); border-color: var(--accent);
    color: var(--bg); font-weight: var(--w-semi);
}
.chip .n { font-variant-numeric: tabular-nums; opacity: .7; }
.plist-ctl select {
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-sm);
    padding: 6px 10px; min-height: 34px; font: inherit; font-size: var(--fs-sm);
}
.plist-h {
    font-size: var(--fs-md); font-weight: var(--w-semi);
    color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em;
    margin: var(--sp-6) 0 var(--sp-3);
}
.plist-empty { margin-top: var(--sp-4); }
/* `hidden` is an attribute and this file gives all four of these a `display`, which beats
   the browser's own `[hidden]` rule — so a filtered-out card would stay on the screen.
   Scoped rather than a global `[hidden] { display: none !important }`, which would reach
   into the learn page's print rules that deliberately open hidden drawers. */
.plist-grid[hidden], .plist-grid > [hidden], .plist-h[hidden], .plist-empty[hidden] {
    display: none !important;
}
.linkish {
    background: none; border: 0; padding: 0; font: inherit;
    color: var(--accent); cursor: pointer; text-decoration: underline;
}

/* ---- a model is visibly not real money --------------------------------------
 * A dashed edge and no fill. Never the only signal: the MD badge, the subtitle and
 * the `model` tag all say it in words, because a border style is invisible to
 * somebody scanning and useless to a screen reader. */
.pcard.is-model { border-style: dashed; background: transparent; }

/* ---- the whole-card click, without wrapping the card in a link --------------
 * The card used to BE an `<a>`. It cannot be any more: a branch row is its own link,
 * and a link inside a link is invalid HTML that browsers silently unnest, sending the
 * strip's clicks to the parent. So the anchor covers the head and stretches over the
 * card, and the strip is lifted above it. */
.pcard { position: relative; }
.pcard-hit { color: inherit; text-decoration: none; }
.pcard-hit::after { content: ""; position: absolute; inset: 0; z-index: 1;
                    border-radius: inherit; }
.pcard:hover { border-color: var(--accent); }
.pcard-hit:focus-visible::after { outline: 2px solid var(--accent); outline-offset: -2px; }
.pcard .plineage { position: relative; z-index: 2; }

/* ---- the what-ifs cut from this account -------------------------------------
 * One line each, hanging off a rail. No sparkline on a branch, and that is the
 * performance story: a curve costs a ledger replay (see `portfolios.spark`), so
 * twenty what-ifs add twenty rows and not one extra request. */
.plineage {
    border-top: 1px dashed var(--border);
    margin-top: var(--sp-1); padding-top: var(--sp-2);
    display: flex; flex-direction: column; gap: 1px;
}
.plineage-h {
    font-size: var(--fs-xs); color: var(--text-faint);
    text-transform: uppercase; letter-spacing: .04em; margin-bottom: var(--sp-1);
}
.pbranch {
    display: flex; align-items: center; gap: var(--sp-2);
    min-height: var(--tap);                      /* a thumb has to be able to hit it */
    padding: var(--sp-2) var(--sp-2) var(--sp-2) 16px;
    border-radius: var(--r-sm); position: relative;
    color: inherit; text-decoration: none;
}
.pbranch:hover { background: var(--surface-2); text-decoration: none; }
.pbranch:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pbranch::before { content: ""; position: absolute; left: 5px; top: 0; bottom: 0;
                   width: 1px; background: var(--border); }
.pbranch:last-of-type::before { bottom: 50%; }
.pbranch::after { content: ""; position: absolute; left: 5px; top: 50%;
                  width: 6px; height: 1px; background: var(--border); }
.pbranch .bname {
    font-size: var(--fs-sm); font-weight: var(--w-semi); flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pbranch .bkind {
    font-size: 0.6875rem; text-transform: uppercase; letter-spacing: .05em;
    color: var(--text-faint); border: 1px solid var(--border);
    border-radius: 4px; padding: 1px 5px; white-space: nowrap;
}
.pbranch .bwhen { font-size: var(--fs-xs); color: var(--text-faint); white-space: nowrap; }
/* Tabular figures and a fixed column: the numbers have to line up down the card, and
   with the parent's, because reading one against the other is the point of the strip. */
.pbranch .bpts {
    font-family: var(--mono); font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm); font-weight: var(--w-bold);
    min-width: 3.4em; text-align: right; white-space: nowrap;
}
.pbranch .bpts.ahead  { color: var(--gain); }
.pbranch .bpts.behind { color: var(--loss); }
.pbranch .bpts.none   { color: var(--text-faint); }
.plineage .more {
    font-size: var(--fs-xs); color: var(--text-dim);
    padding: var(--sp-2) var(--sp-2) 0 16px; text-decoration: none;
}
.plineage .more:hover { color: var(--accent); text-decoration: underline; }

/* On a phone the row would squash the name to nothing. The name takes its own line and
   the rest drops beneath it; the row stays a full tap target either way. */
@media (max-width: 560px) {
    .pbranch { flex-wrap: wrap; row-gap: 2px; }
    .pbranch .bname { flex: 1 0 100%; }
    .pbranch .bpts { margin-left: auto; }
}

/* On paper the controls are meaningless and the strip is still worth having. */
@media print {
    .plist-ctl { display: none !important; }
    .pbranch:hover { background: none; }
}

/* ============================================================ the brand, and the door
 *
 * Added 2026-09-11 with the rename to Compass (docs/06_APP_SPEC.md §11ab). The mark is
 * app/static/img/compass.svg everywhere; nothing here redraws it.
 */
header.top .brand a { display: inline-flex; align-items: center; gap: 8px; }
header.top .brand img { display: block; width: 22px; height: 22px; flex: none; }

/* The signed-out pages: mark, name and one sentence above a narrow card. */
.door {
    max-width: 420px; margin: var(--sp-8) auto var(--sp-6);
    display: flex; flex-direction: column; gap: var(--sp-5);
}
.door-brand {
    display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
    text-align: center;
}
.door-brand img { display: block; width: 56px; height: 56px; }
.door-name {
    font-family: var(--font-display); font-weight: var(--w-semi);
    font-size: var(--fs-xl); line-height: var(--lh-xl); letter-spacing: -0.01em;
}
.door-line {
    margin: 0; max-width: 36ch;
    color: var(--text-dim); font-size: var(--fs-sm); line-height: var(--lh-sm);
}
.door-card { display: flex; flex-direction: column; gap: var(--sp-3); }
.door-card h1 { margin: 0; font-size: var(--fs-lg); line-height: var(--lh-lg); }
.door-card .tiny { margin: 0; }
/* A visible label on every field, stacked above it — never the placeholder alone. */
.door-card label {
    display: flex; flex-direction: column; gap: 6px;
    font-size: var(--fs-sm); font-weight: var(--w-medium); color: var(--text);
}
.door-card label .hint {
    font-weight: var(--w-normal); font-size: var(--fs-xs); color: var(--text-faint);
}
.door-card input, .door-card textarea { width: 100%; margin: 0; box-sizing: border-box; }
.door-card button.primary { width: 100%; min-height: var(--tap); margin-top: var(--sp-1); }

/* ============================================================ privacy, archive, delete
 *
 * Added 2026-09-11 (docs/06_APP_SPEC.md §11ac).
 */

/* A radio or a checkbox is never the 44px, full-width box every other input is. The global
   `input` rule and `.setup label > input { width: 100% }` both caught them, which is why the
   import form's tax-treatment choice drew as three huge white discs. The touch target stays
   44px through `label.checkline`, which is the thing a thumb actually hits. */
input[type="radio"], input[type="checkbox"] {
    min-height: 0; width: 18px; height: 18px; padding: 0; flex: none;
    accent-color: var(--accent);
}
.setup label > input[type="radio"], .setup label > input[type="checkbox"],
.stack > label > input[type="radio"], .stack > label > input[type="checkbox"] { width: 18px; }

/* The one destructive button in the app, and the quiet link that leads to it. Red is the
   hint, never the message: the words say "Delete permanently" on both. */
button.danger { background: var(--loss); border-color: var(--loss); color: #fff; }
.btn.danger-quiet { color: var(--loss); }
.btn.danger-quiet:hover { border-color: var(--loss); text-decoration: none; }

.delete-page { max-width: 44rem; }
.delete-page h1 { margin: 0; font-size: var(--fs-xl); line-height: var(--lh-xl); }
.delete-page ul.gone { margin: var(--sp-2) 0 0; padding-left: 1.2em;
                       display: flex; flex-direction: column; gap: var(--sp-1); }
.delete-page .alt { border: 1px dashed var(--border); border-radius: var(--r-md);
                    padding: var(--sp-3) var(--sp-4); display: flex; gap: var(--sp-3);
                    align-items: center; justify-content: space-between; flex-wrap: wrap; }
.delete-page .alt form { margin: 0; }
.delete-page label { display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-sm); }
.delete-page input[name="confirm_name"] { width: 100%; box-sizing: border-box; }

/* Archived: folded shut at the foot of the list. */
.parchive { margin-top: var(--sp-5); border: 1px dashed var(--border);
            border-radius: var(--r-md); padding: var(--sp-1) var(--sp-4); }
.parchive summary { cursor: pointer; padding: var(--sp-3) 0; font-size: var(--fs-sm);
                    color: var(--text-dim); }
.parchive ul { list-style: none; margin: 0; padding: 0 0 var(--sp-3);
               display: flex; flex-direction: column; gap: var(--sp-2); }
.parchive li { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.parchive .aname { flex: 1 1 12rem; font-weight: var(--w-medium); }
.parchive form { margin: 0; }

/* Where your statement goes — three short columns, not a page of prose. */
.privacy { display: flex; flex-direction: column; gap: var(--sp-4); margin-bottom: var(--sp-5);
           border-color: color-mix(in srgb, var(--band-balanced) 45%, var(--border)); }
.privacy-head { display: flex; gap: var(--sp-3); align-items: flex-start; }
.privacy-head > svg { width: 28px; height: 28px; flex: none; color: var(--band-balanced);
                      margin-top: 2px; }
.privacy-head h2 { margin: 0 0 4px; font-size: var(--fs-lg); }
.privacy-head p { margin: 0; }
.privacy-cols { display: grid; gap: var(--sp-3); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 860px) { .privacy-cols { grid-template-columns: 1fr; } }
.pcol { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-md);
        padding: var(--sp-3) var(--sp-4); }
.pcol h3 { display: flex; align-items: center; gap: 8px; margin: 0 0 var(--sp-2);
           font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: .05em;
           color: var(--text-dim); }
.pcol h3 svg { width: 18px; height: 18px; flex: none; }
.pcol ul { margin: 0; padding-left: 1.1em; display: flex; flex-direction: column; gap: var(--sp-2); }
.pcol li { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text); }
.pcol li .tiny { display: block; }
.privacy-out { display: flex; gap: var(--sp-3); align-items: flex-start;
               border-top: 1px dashed var(--border); padding-top: var(--sp-3); }
.privacy-out svg { width: 22px; height: 22px; flex: none; color: var(--text-dim); margin-top: 2px; }
.privacy-out p { margin: 0; font-size: var(--fs-sm); }

/* The tax-treatment list is `label.checkline` inside `fieldset.stack`, and `.stack > label`
   stacks its children in a column — which put every radio on its own line above the words
   that explain it. A checkline is a row: the control, then what it means. */
label.checkline { flex-direction: row; }
/* Native controls — a radio, a select's arrow, a scrollbar — follow the theme the reader
   chose, not only the one the OS reports. Without this a dark-stamped page on a light OS
   drew bright white radio buttons. */
:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* Your own name in the nav is the way in to your account (06 §11ad). It is a link, so it
   says so on hover and when focused — but quietly: it is not a navigation item. */
.navme { color: var(--text-dim); text-decoration: none; }
.navme:hover { color: var(--accent); text-decoration: underline; }
.navme[aria-current="page"] { color: var(--text); }
