/* ============================================================
   Tactical Asset Allocation — Shared Design System v3
   ============================================================
   Persistent header is a THREE-ROW BLOCK, not sticky:
     Row 1: site brand wordmark left, Cambria logo right
     Row 2: tool title + tagline (left-aligned, same x as wordmark)
     Row 3: tool switcher pills (left-aligned, same x as wordmark)
   Single border under the whole block separates it from page content.
   Each tool's existing inline <header> should be removed.

   Layout:
     <body>
       <div id="cambria-header-mount"
            data-tool-name="Strategy Lab"
            data-tool-tagline="TAA · Sector Rotation · Daily Data"></div>
       … existing tool content …
============================================================ */

/* ── Webfonts ────────────────────────────────────────────────────────────
   Self-hosted, and here rather than in a separate file because every tool
   already links this one. Before this, macro.html, ef.html and the weekly
   report named 'Playfair Display' and loaded nothing, so they rendered in
   whatever serif the browser defaults to; the rest pulled Playfair and DM
   from fonts.googleapis.com, which a corporate proxy is free to block --
   and several of the networks this is sold into do.

   TAA Serif is a renamed, subset, metrics-normalised derivative of Adobe's
   Source Serif 4 (SIL OFL 1.1, Reserved Font Name 'Source' -- the rename
   is a licence requirement, not a flourish). Licences and provenance ship
   beside the files in /static/fonts/. Built by macro/../taa-serif/build.py.
   ──────────────────────────────────────────────────────────────────────── */

/* Financier — Klim Type Foundry, licensed 2 Sep 2026 (web, 5,000 MUU).
   NOT an open licence: these two files may be served from our own domains
   and nowhere else. Do not copy them into an SVG, an emailed asset or a
   third party's page -- that is a different licence from the one we hold.

   Two families rather than one with an optical-size axis, which is Klim's
   choice and the reason the Collection is sold as a pair: Financier has no
   opsz axis, so Display IS the large-size cut and Text IS the small one.
   Display carries wght 300-900, Text 400-900.

   x-height is 0.426 em against TAA Serif's 0.475, so Financier sets about
   10% smaller at the same font-size. The type scale below compensates
   rather than leaving the hero looking shrunk. */
@font-face{
  font-family:'Financier Display';
  src:url('/static/fonts/financier-display-vf-roman.woff2') format('woff2-variations');
  font-weight:300 900; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Financier Display';
  src:url('/static/fonts/financier-display-vf-italic.woff2') format('woff2-variations');
  font-weight:300 900; font-style:italic; font-display:swap;
}
@font-face{
  font-family:'Financier Text';
  src:url('/static/fonts/financier-text-vf-roman.woff2') format('woff2-variations');
  font-weight:400 900; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Financier Text';
  src:url('/static/fonts/financier-text-vf-italic.woff2') format('woff2-variations');
  font-weight:400 900; font-style:italic; font-display:swap;
}

@font-face{
  font-family:'TAA Serif';
  src:url('/static/fonts/taa-serif-roman.woff2') format('woff2-variations');
  font-weight:400 700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'TAA Serif';
  src:url('/static/fonts/taa-serif-italic.woff2') format('woff2-variations');
  font-weight:400; font-style:italic; font-display:swap;
}
/* 300-700, not 300-500. The earlier subset clipped the axis at 500 while
   the UI asks for 600 and 700 -- the macro gauge at 52px is a 700 -- so
   Chromium clamped to the 500 master and painted synthetic bold over it.
   Measured: 700 and 500 came back at identical advance widths. Renamed
   rather than replaced because /static/ is served immutable for a year. */
@font-face{
  font-family:'DM Sans';
  src:url('/static/fonts/dm-sans-vf.woff2') format('woff2-variations');
  font-weight:300 700; font-style:normal; font-display:swap;
}
@font-face{
  /* THE ITALIC IS A FILE, NOT A SLANT. Until 3 Sep 26 nothing declared one,
     so every <em> in sans body copy -- and there are dozens, across the EF
     notes, the method blurbs and the holdings captions -- was Chromium
     shearing the roman by an oblique transform. That is not what DM Sans's
     italic looks like: the real one is a cursive design with a single-storey
     a and different g, e and f. The capture gate caught it as
     `italic 400 14px "DM Sans"` with no master, which is exactly what it is.
     Same 4.004, same latin subset, same opsz axis, wght clamped 300-700 to
     match the roman. */
  font-family:'DM Sans';
  src:url('/static/fonts/dm-sans-vf-italic.woff2') format('woff2-variations');
  font-weight:300 700; font-style:italic; font-display:swap;
}
@font-face{
  font-family:'DM Mono';
  src:url('/static/fonts/dm-mono-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'DM Mono';
  src:url('/static/fonts/dm-mono-500.woff2') format('woff2');
  font-weight:500; font-style:normal; font-display:swap;
}

/* Metric-matched fallbacks. font-display:swap reflows a heading visibly
   without them. size-adjust is derived from measured x-heights: TAA Serif
   0.475 em against Georgia's 0.484, DM Sans 0.526 against Arial's 0.519.
   The Linux entries matter because a share of readers have neither
   Georgia nor Arial installed. */
@font-face{
  font-family:'TAA Serif Fallback';
  src:local('Georgia'), local('Times New Roman'), local('Liberation Serif'),
      local('DejaVu Serif'), local('Nimbus Roman');
  size-adjust:98.1%; ascent-override:103.6%; descent-override:33.5%;
  line-gap-override:0%;
}
@font-face{
  font-family:'DM Sans Fallback';
  src:local('Arial'), local('Helvetica'), local('Liberation Sans'),
      local('DejaVu Sans'), local('Nimbus Sans');
  size-adjust:101.4%; ascent-override:99.2%; descent-override:31%;
  line-gap-override:0%;
}


:root {
  /* Surface palette */
  --bg: #0b0d10;
  --surface: #12161c;
  --surface2: #1a2030;
  --border: #232b3a;

  /* Accent palette */
  /* Amber, not the mint that was here until 2 Sep 26. Not a contrast
     argument -- mint measured HIGHER (11.15:1 on the hero navy against
     amber's 8.38:1). Mint reads terminal against a navy-and-white brand,
     and warm-on-navy reads editorial, which is the register this is
     actually in. Light theme keeps Cambria blue below; an accent that
     works on both grounds in one hue does not exist here. */
  --accent: #ffb84f;
  /* CAMBRIA'S PURPLE, LIGHTENED FOR THIS GROUND -- not Tailwind's
     purple-400, which is what #c084fc is and what sat here until 2 Sep 26.
     Same hue as the light theme's #584389 (258deg), lifted to carry label
     text on the dark ground at 6.55:1. Read by the backtester's Portfolio B
     comparison and EF's spread mean, so it cannot simply be deleted. */
  --purple: #a393c8;

  /* ONE RADIUS SCALE. There were seven values across these two files --
     3, 5, 6, 7, 9, 14 and 20px -- none of them a decision. Three tokens
     and a pill; 50% stays literal where the shape is a circle.
     Softened one notch from 3/6/10 on 2 Sep -- square read colder
     than the page wants, and a scale can be gentle and still be a
     scale. The point was never that corners be sharp. */
  --r-sm: 4px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-pill: 999px;
  --accent2: #4f8fff;
  --warn: #ff6b4f;
  --amber: #ffb84f;
  --teal: #06d6a0;

  /* Text */
  --text: #e8eaf0; --slider-thumb: #ffb84f;
  /* MEASURED AGAINST THE DARKEST BACKGROUND THESE LAND ON, not just --bg.
     Every previous pass here checked one background and shipped: --muted was
     fixed on the landing page in August against --bg and left at 2.75:1
     everywhere else, and --fineprint's own comment still claimed a figure
     true only on white. A token used on three surfaces has to clear the
     floor on the worst of them, which is --surface2. */
  --muted: #8b93a5;         /* 5.27:1 on --surface2, the worst case */

  /* Signed deltas rendered as bare text on the page, not inside a chip.
     They were one hardcoded pair used in BOTH themes, which cannot work:
     #3d8a4e is 4.25:1 on white and 3.82:1 on --surface2, and darkening it
     to fix the first makes the second worse. A value light enough to read
     on a dark ground and dark enough to read on a light one does not
     exist, so the pair has to be theme-aware like everything else here. */
  --delta-pos: #5cb371;     /* 6.30:1 on --surface2 */
  --delta-neg: #d9803f;     /* 5.50:1 on --surface2 */
  --muted2: #a1a9ba;        /* readable secondary — 6.88:1 on --surface2 */

  /* Fine print — disclosures, footnotes, provenance notes.
     MEASURED, not chosen. A tester asked for these to be "smaller and a
     lighter color" so they stop competing with the data, and he is right about
     the goal, but colour alone cannot get there: small text needs 4.5:1 and on
     white the lightest passing grey is #6b7480 at 4.74:1. #767f8c is 4.05:1
     and #808a97 is 3.50:1 — both fail.
     So the recession is done with SIZE, WEIGHT and SPACE, and the colour stops
     at the floor. On the dark surface the direction reverses: receding means
     moving toward the background, which loses contrast, so dark mode uses a
     value with headroom instead (#8b93a7, 6.15:1).

     The floor is set against the CARD surface, not the page. #6b7480 clears
     4.74:1 on white and only 4.46:1 on #f7f8fa, and almost all of this text
     lives inside cards -- so measuring against the page would have shipped a
     failure that looked like a pass. check_shared_chrome.js caught it; nobody
     would have caught it by looking. */
  --fineprint: #8b93a7;
  --fineprint-size: .72rem;
  --fineprint-lh: 1.55;

  /* Type */
  /* DM Mono has no U+2192; 'DM Sans' next in the chain supplies the arrow
     the card and table CTAs use, from a font we ship. */
  --mono: 'DM Mono', 'DM Sans', ui-monospace, Menlo, Consolas, monospace;
  /* TWO SERIF ROLES NOW, not one. --display is the large cut and --serif
     the small; Financier has no optical-size axis, so the family IS the
     optical size. TAA Serif stays in both chains as a real fallback rather
     than dropping to Georgia -- it is self-hosted, already loaded by the
     tools, and closer to Financier than anything on the system. */
  --display: 'Financier Display', 'Financier Text', 'TAA Serif', 'TAA Serif Fallback', Georgia, serif;
  --serif: 'Financier Text', 'TAA Serif', 'TAA Serif Fallback', Georgia, 'Times New Roman', serif;
  --sans: 'DM Sans', 'DM Sans Fallback', -apple-system, BlinkMacSystemFont,
          'Segoe UI', Arial, sans-serif;

  /* Layout */
  --header-padding-x: 24px;
  --rail-width: 200px;
  --rail-gap: 24px;
  --rail-breakpoint: 1280px;
}

/* ─── Reset ────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  min-height: 100vh;
  /* No ambient radial washes. Two ellipse gradients sat on body here, so
     every tool page carried them -- and a low-alpha glow bleeding from a
     corner is the single most recognisable tell of a generated page. The
     bands do the sectioning; the ground stays flat. */
}

a { color: inherit; text-decoration: none; }
img { display: block; }

/* ─── Header block (three rows, full width, NOT sticky) ─── */
.cambria-header {
  width: 100%;
  border-bottom: 1px solid var(--border);
  background: rgba(18, 22, 28, 0.96);
  backdrop-filter: blur(8px);
  position: sticky;
  top: 0;
  z-index: 100;
}

/* Row 1: brand wordmark + Cambria logo */
.cambria-header .row-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px var(--header-padding-x) 4px;
}

.cambria-header .brand {
  display: flex;
  align-items: center;
  gap: 13px;
  font-family: var(--display);
  /* DISPLAY, NOT TEXT. Every tool loads this file, so the wordmark and the
     tool title were the only Financier on a tool page -- and both were set
     in the body cut at display sizes. Klim sells the two separately
     because the family IS the optical size.

     SIZE UNCHANGED, and that was measured rather than reasoned. The OS/2
     cap heights in the two files say .582em and .606em, a 4.1% gap that
     would need 2.79rem here to keep the wordmark sitting against the 38px
     θαα mark. Rendered, the gap is 2.5% and the 'H' comes out at 28px
     either way at this size, so compensating would have moved the thing
     it was meant to hold still. The tool title is the one that shifts.
     (The older note here claimed Playfair at .7em, two typefaces ago.) */
  font-size: 2.9rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -.01em;
  line-height: 1;
  transition: opacity .15s;
  white-space: nowrap;
  text-decoration: none;
}
.cambria-header .brand-mark {
  height: 38px;
  width: auto;
  flex-shrink: 0;
}
.cambria-header .brand:hover { opacity: .85; }

.cambria-header .cambria-logo img {
  height: 40px;
  width: auto;
  opacity: .9;
}

/* Row 2: tool title + tagline */
.cambria-header .row-tool {
  display: flex;
  align-items: baseline;
  gap: 18px;
  padding: 5px var(--header-padding-x) 12px;
  flex-wrap: wrap;
}

.cambria-header .tool-title {
  font-family: var(--display);
  /* 1.72rem put Display's caps at 17px where Text sat at 16. 1.65rem
     (26.4px) measures 16 again -- the only size that needed touching. */
  font-size: 1.65rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -.01em;
  line-height: 1.15;
  white-space: nowrap;
}

.cambria-header .tool-tagline {
  font-family: var(--mono);
  font-size: 0.9rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

/* Row 3: tool switcher pills */
.cambria-header .row-tools {
  display: flex;
  gap: 8px;
  padding: 5px var(--header-padding-x) 17px;
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.cambria-header .row-tools::-webkit-scrollbar { height: 3px; }
.cambria-header .row-tools::-webkit-scrollbar-thumb { background: var(--border); }

.cambria-header .row-tools a,
.cambria-header .row-tools .tool-placeholder {
  display: inline-flex;
  align-items: center;
  padding: 8px 15px;
  font-family: var(--mono);
  font-size: .82rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  white-space: nowrap;
  transition: all .15s;
  flex-shrink: 0;
}

.cambria-header .row-tools a:hover {
  color: var(--text);
  border-color: var(--muted);
}

.cambria-header .row-tools a.active {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.cambria-header .row-tools .tool-placeholder {
  color: #3a4254;
  border-color: #1a2030;
  border-style: dashed;
  cursor: default;
}

/* Workflow groups within the nav row */
.cambria-header .row-tools .tool-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.cambria-header .row-tools .tool-group-label {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: .75;
  margin-right: 2px;
  flex-shrink: 0;
}
.cambria-header .row-tools .tool-group-sep {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  margin: 2px 10px;
  flex-shrink: 0;
}
.cambria-header .by-cambria {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 8px;
}

/* Narrow viewports */
@media (max-width: 720px) {
  .cambria-header { --header-padding-x: 14px; }
  .cambria-header .brand,
  .cambria-header .tool-title { font-size: 1.7rem; }
  .cambria-header .cambria-logo img { height: 32px; }
  .cambria-header .row-tools a,
  .cambria-header .row-tools .tool-placeholder {
    padding: 6px 11px;
    font-size: .72rem;
    letter-spacing: .04em;
  }
}

/* ─── Within-tool tab bars ─────────────────────────────── */
.tab-bar {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  padding: 0 28px;
  flex-shrink: 0;
}

.tab {
  padding: 11px 18px;
  font-family: var(--mono);
  font-size: .65rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all .15s;
  white-space: nowrap;
}

.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.placeholder h2 { font-family: var(--serif); font-size: 1.75rem; color: var(--text); font-weight: 400; }
/* SANS, NOT MONO. Mono earns its place where digits line up -- a
   ticker, a figure, a column. A row of three uppercase links is
   none of those, and DM Mono's slab terminals read as a serif at
   13px letterspaced, which is what it looked like beside
   Financier. Spec section 6, 'mono discipline'. */
.cambria-footer-legal{display:flex;justify-content:center;flex-wrap:wrap;gap:8px 20px;font-family:var(--sans);font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;padding:26px 20px 32px;border-top:1px solid var(--border);}
.cambria-footer-legal a{color:var(--muted2,#636363);text-decoration:none;transition:color .15s;}
.cambria-footer-legal a:focus-visible{outline:2px solid var(--accent2);outline-offset:3px;}
.cambria-footer-legal a:hover{color:var(--accent);}
/* --border measured 1.13:1 here: not a quiet separator, an invisible one. */
.cambria-footer-legal .sep{color:var(--muted);}

/* ─── Theme switching must not animate ──────────────────────────────
   Applied by cambria-nav.js for one frame around a theme change.

   WHY THIS EXISTS. The tools paint in the dark :root default and then flip
   to light when cambria-nav.js runs. Any element carrying `transition: all`
   animates that flip -- and on the Stress Tester two of them never finished:
   eight seconds after load the Run button still rendered the DARK --warn
   (#ff6b4f, 2.81:1 with white on it) and the Reset button still had dark
   background, text AND border on a light page. Not a flash; the end state.
   A contrast audit cannot catch that second one, because a stuck element
   keeps a self-consistent dark PAIR and measures fine.
   `transition: all` is the hazard -- it sweeps colour into an animation
   meant for transform and filter. Killing transitions for the frame in
   which the theme changes removes the whole class of bug at once. */
html.taa-theme-switching,
html.taa-theme-switching *,
html.taa-theme-switching *::before,
html.taa-theme-switching *::after{
  transition:none !important;
}
.has-left-panel ~ .cambria-footer-legal{padding-left:340px;}

/* ─── Cards ────────────────────────────────────────────── */
.chart-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 16px 20px;
}

.chart-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--mono);
  font-size: .65rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 12px;
}

.chart-legend { display: flex; gap: 14px; align-items: center; }

.legend-item {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: .72rem;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}

.legend-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }

/* ─── Data tables ──────────────────────────────────────── */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--mono);
  font-size: .72rem;
}

.data-table th {
  text-align: left;
  padding: 6px 10px;
  color: var(--muted);
  font-weight: 400;
  font-size: .57rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
}

.data-table th:not(:first-child) { text-align: right; }

.data-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }
.data-table td:not(:first-child) { text-align: right; }
.data-table tr:last-child td { border-bottom: none; }

.data-table .pos { color: var(--accent); }
.data-table .neg { color: var(--warn); }

/* ─── Loading / status bars ────────────────────────────── */
.loading {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--muted);
  margin: 8px 0;
}

.spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.warn-bar {
  padding: 8px 14px;
  background: rgba(255, 184, 79, 0.08);
  border: 1px solid rgba(255, 184, 79, 0.3);
  border-radius: var(--r-md);
  color: var(--amber);
  font-family: var(--mono);
  font-size: .72rem;
  margin: 8px 0;
}

.error-bar {
  padding: 8px 14px;
  background: rgba(255, 107, 79, 0.1);
  border: 1px solid rgba(255, 107, 79, 0.35);
  border-radius: var(--r-md);
  color: var(--warn);
  font-family: var(--mono);
  font-size: .72rem;
  margin: 8px 0;
}

/* ─── Right rail (ad gutter) — position:absolute approach ─── */
/*
   At ≥1440px viewport, body gets 224px of right padding (200px rail + 24px gap).
   The rail is positioned absolutely on the right edge of the viewport.
   Below 1440px, no padding shift, rail is display:none — tools render
   exactly as they did before.

   Usage: add ONE element to each tool's HTML, immediately before </body>:
     <aside class="cambria-rail">
       <div class="rail-card">Sponsored content space — Available</div>
     </aside>
*/

.cambria-rail {
  display: none;
}

@media (min-width: 1700px) {
  body {
    padding-right: 224px;
  }
  .cambria-header {
    margin-right: -224px;
    width: 100vw;
  }
  .cambria-rail {
    display: block;
    position: absolute;
    top: 190px;
    right: 12px;
    width: 200px;
  }
}

.rail-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 14px 12px;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  min-height: 600px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1.6;
}

/* ─── Asset-class categories ─────────────────────────────────
   The four groups the fourteen PORTFOLIO_KEYS sleeves roll up into.
   Backtester, Stress and the Efficient Frontier all group the sleeves
   the same way and all three must colour them the same way, or the same
   portfolio changes colour as it moves between them on the bus.

   These lived in backtester/index.html and stress/stress.html -- twice
   each, screen and print -- and nowhere else, so EF used --accent,
   --accent2, --amber and --purple instead. When --accent moved from mint
   to amber on 1 Sep it collided with --amber, and EF started drawing
   Stocks and Real Assets in the same colour in dark theme.

   VALIDATED, NOT PICKED. Both previous sets failed measurably. Light put
   Stocks #2e57a0 and Bonds #12719a at normal-vision deltaE 7.8 against a
   floor of 15 -- the two largest sleeves in most portfolios, adjacent in
   every stacked bar, and hard to tell apart with full colour vision. Dark
   put three of four outside the lightness band (mint L .89, amber .83,
   violet .72 against .48-.67), which is what "neon on black" measures as.

   And each borrowed a UI token: light --cat-stocks WAS --accent #2e57a0,
   dark --cat-real WAS --accent #ffb84f. A categorical colour must never be
   the accent -- that hue is what the eye reads as "clickable".

   These four clear all six checks in both modes. Worst normal-vision
   adjacent pair deltaE 24.0 light / 20.9 dark; CVD-safe under protanopia
   and deuteranopia; lightness and chroma in band. One warning stands:
   light jade is 2.82:1 on white, under the 3:1 mark floor. That is the
   documented relief case -- legal where values are readable another way --
   FOUR GROUPS, FIFTEEN SLEEVES, AND THE SPLIT WAS FORCED BY ARITHMETIC.
   Bonds used to hold seven of the fifteen sleeves. Giving each sleeve its
   own shade means an ordinal ramp per group -- hue carries the group,
   lightness carries the member -- and an ordinal ramp needs adjacent steps
   0.06 apart in OKLCH lightness inside a band 0.19 wide on dark. Seven
   steps need 0.36. It is not a matter of choosing better greens; they do
   not exist. Four steps need 0.18, which fits with 0.01 to spare.

   So Bonds became Treasuries (T-bills, 10yr, 30yr, TIPS) and Credit
   (corporates, aggregate, foreign developed), and Alternatives absorbed
   the single managed-futures sleeve alongside commodities, gold and REITs.
   Four groups of 4/4/3/4. That is also the more conventional taxonomy, and
   TIPS under Treasuries is correct where "Credit & Inflation-Linked" was a
   fudge.

   CREDIT TOOK THE INDIGO AND ALTERNATIVES THE ORANGE so gold and
   commodities stay warm. Gold in a shade of purple was the thing that made
   every five-hue arrangement unappealing.

   Jade's ramp runs at 60% chroma on light and 80% on dark. A saturated hue
   near the sRGB gamut edge cannot reach the lightness asked of it -- the
   conversion clips and the steps compress to delta-L 0.058 against a 0.06
   floor. Chroma buys lightness range.

   and every allocation segment here carries its name and percentage. */
:root{
  --cat-stocks:     #3987e5;   /* blue   */
  --cat-treasuries: #199e70;   /* jade   */
  --cat-credit:     #9085e9;   /* indigo */
  --cat-alts:       #d95926;   /* orange */

  /* Hue is the group, lightness is the sleeve. */
  --sleeve-us-large-cap: #005bb5;
  --sleeve-us-small-cap: #1d6eca;
  --sleeve-foreign-developed: #3482e0;
  --sleeve-foreign-emerging: #4996f5;
  --sleeve-tbills: #036f4d;
  --sleeve-us-bonds-10yr: #26825e;
  --sleeve-us-bonds-30yr: #3d9570;
  --sleeve-tips: #52a983;
  --sleeve-corp-bonds: #5a4caa;
  --sleeve-us-bonds-agg: #7568c9;
  --sleeve-foreign-bonds-10yr: #9085e9;
  --sleeve-commodities: #a82900;
  --sleeve-gold: #be3f00;
  --sleeve-reits: #d45420;
  --sleeve-managed-futures: #ea6837;
}
html:not([data-theme="dark"]){
  --cat-stocks:     #2a78d6;
  --cat-treasuries: #1baf7a;
  --cat-credit:     #4a3aa7;
  --cat-alts:       #eb6834;

  --sleeve-us-large-cap: #004ba6;
  --sleeve-us-small-cap: #1e6ecb;
  --sleeve-foreign-developed: #4592f2;
  --sleeve-foreign-emerging: #69b6ff;
  --sleeve-tbills: #155e41;
  --sleeve-us-bonds-10yr: #3c7f61;
  --sleeve-us-bonds-30yr: #5ea281;
  --sleeve-tips: #82c5a4;
  --sleeve-corp-bonds: #4939a6;
  --sleeve-us-bonds-agg: #766ddf;
  --sleeve-foreign-bonds-10yr: #a8a3ff;
  --sleeve-commodities: #971200;
  --sleeve-gold: #be3e00;
  --sleeve-reits: #e6632f;
  --sleeve-managed-futures: #ff8855;
}

/* ─── Data semantics ─────────────────────────────────────────
   Positive, negative and the neutral middle. A DIVERGING PAIR PLUS A GREY
   MIDPOINT, which is what polarity wants: two hues that read as opposite,
   and a middle that reads as "nothing happened".

   These exist because the charts had been borrowing --accent to mean
   "good". #ffb84f was the single most-used hardcoded colour on the
   platform -- nine sites in the Backtester, seven in Stress, four in
   Factor -- carrying "positive return", "gold", "recovery", "correlation
   fell" and "this is a button" at once. It also silently falsified a
   caption: the Stress correlation-delta footnote says "Green: correlation
   fell. Red: it rose", which was true while --accent was mint and became
   a lie the day it turned amber. The chart is green again, so the words
   are true again.

   THE GREY MIDPOINT FAILS A CATEGORICAL CHROMA FLOOR ON PURPOSE. A
   categorical hue must carry identity; a diverging midpoint must carry
   the absence of it.

   Green/red rather than the safer blue/red because this is finance and
   the convention is load-bearing -- but it costs something real: the pair
   separates by deltaE 7.2 under protanopia on the light surface, in the
   6-8 band that is legal ONLY where a second cue carries the same
   meaning. Every chart using these must keep one: bars signed around
   zero, or values printed. The Stress donut is the exception that needs
   watching -- wedges have no sign, so its legend has to carry the number. */
:root{
  --data-pos: #008300;
  --data-neg: #e66767;
  --data-mid: #7b8594;
}
html:not([data-theme="dark"]){
  --data-pos: #008300;
  --data-neg: #e34948;
  --data-mid: #6f7785;
}

/* ─── Regime chips ─────────────────────────────────────────
   Three copies of this lived in macro.html, country.html and
   build_pages.py, and they had already drifted apart: the generated
   chart pages still carried #9a6700 and #888, the two colours
   macro.html had fixed for contrast. country.html's copy carried a
   comment saying it belonged here.

   None of the three had a dark theme. The chips rendered as pale
   light-mode pills on a dark card, which is where it was finally
   noticed -- baked into the landing hero screenshot, in the shot that
   exists BECAUSE a light image on a dark band looks pasted in.

   .chip is the generated pages' name for the same object.

   LIGHT IS THE UNSTAMPED STATE, since 2 Sep 26. It used to be the other
   way round -- this block still said so -- and the comment outlived the
   change by a day. Light needs no attribute and therefore no JS, dark is
   opt-in via data-theme="dark", and html:not([data-theme="dark"]) carries
   the light values. */
.state, .chip {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 3px 10px; border-radius: var(--r-lg, 12px); white-space: nowrap;
  font-size: 13px; font-weight: 600;
}
.chip { display: inline-block; margin: 2px 4px 2px 0; }
/* fixed-size variant for the intl table: every chip identical, no
   shrink-to-fit or two-line wrap (fits "Strong Windfall" at 11px) */
.state.sm { width: 108px; padding: 3px 0; font-size: 11px; }

/* Tints rather than flat fills, so a chip on a nested surface still reads
   as the same object. Contrast measured against the card at #12161c:
   8.5, 7.5, 7.2, 6.9, 5.9 -- every one above the light theme's own
   4.6-5.3, which is the right direction on a dark ground. */
.pos     { background: rgba(45, 155, 85, .20);   color: #74dfa2; }
.mildpos { background: rgba(45, 155, 85, .13);   color: #63c78d; }
.mildneg { background: rgba(214, 158, 46, .16);  color: #e2b45c; }
.neg     { background: rgba(214, 60, 50, .18);   color: #f2938c; }
.na      { background: rgba(255, 255, 255, .07); color: #97a3b0; }

html:not([data-theme="dark"]) .pos     { background: #e6f4ea; color: #137333; }
html:not([data-theme="dark"]) .mildpos { background: #eef7f0; color: #2e7d4f; }
html:not([data-theme="dark"]) .mildneg { background: #fdf3e1; color: #8a5c00; }
html:not([data-theme="dark"]) .neg     { background: #fce8e6; color: #c5221f; }
html:not([data-theme="dark"]) .na      { background: #eee;    color: #666; }

/* ─── Light theme — Cambria brand palette ──────────────────
   White / blue / grey, colors from the Cambria style guide:
   navy 022a53, blue 2e57a0, sky 33b1e7, teal 00a99d,
   purple 584389, greys 636363 / 949598 / adafb2 / e0e1e2.
   Activated by html:not([data-theme="dark"]) (set by cambria-nav.js,
   persisted in localStorage 'taa_theme'). The attribute selector
   out-specifies each page's local :root block, so per-page
   palettes inherit these overrides without page edits. */
html:not([data-theme="dark"]) {
  --bg: #f4f5f7;
  --surface: #ffffff;
  --surface2: #eceef1;
  --border: #e0e1e2;

  --accent: #2e57a0;        /* Cambria blue */
  /* Darkened for legibility; hue and saturation preserved. The Cambria
     values were unusable as text on a light ground -- sky #33b1e7 measured
     2.10:1 on --surface2, teal #00a99d 2.52:1 -- and white on a fill of
     either was worse still. These are the LIGHTEST values of the same hue
     that clear 4.5:1 on white, --bg and --surface2 and also carry white
     text. Every dark-theme accent was measured in the same pass; they all
     already pass, so only the light values move. */
  --accent2: #12719a;       /* was #33b1e7, 2.10:1 on --surface2 */
  --teal: #00766e;          /* was #00a99d, 2.52:1 */
  --purple: #584389;        /* Cambria purple */
  --warn: #c0392b;          /* functional red (not brand) */
  --amber: #995c00;         /* was #a86500, 3.99:1 on --surface2 */

  --text: #022a53;          /* Cambria navy as body text */
  /* MEASURED AGAINST THE DARKEST BACKGROUND THESE LAND ON, not just --bg.
     Every previous pass here checked one background and shipped: --muted was
     fixed on the landing page in August against --bg and left at 2.75:1
     everywhere else, and --fineprint's own comment still claimed a figure
     true only on white. A token used on three surfaces has to clear the
     floor on the worst of them, which is --surface2. */
  --muted: #696a6f;         /* 4.64:1 on --surface2 (was #949598, 2.58:1) */
  --delta-pos: #337a43;     /* 4.81:1 on --bg (was #3d8a4e, 3.89:1) */
  --delta-neg: #a84c14;     /* 5.19:1 on --bg (was #b5541a, 4.53:1) */
  --muted2: #636363;        /* brand grey - readable secondary, 5.17:1 */
  --fineprint: #626b76;     /* 4.65:1 on --surface2 (was #6a737f, 4.13:1) */
  --slider-thumb: #a86500;

  /* text color for solid accent buttons (dark theme uses near-black) */
  --btn-text: #ffffff;
}

/* The light theme carried the same two washes. Removed with the dark
     pair on 2 Sep 26 -- flat ground, bands do the sectioning. */

html:not([data-theme="dark"]) .cambria-header {
  background: rgba(255, 255, 255, 0.95);
}

/* Swap the brand mark for the navy/blue version in light mode
   (dark-mode file stays the default; nav.js needs no changes) */
html:not([data-theme="dark"]) .cambria-header .brand-mark {
  content: url('/taa-mark-header-light.svg?v=2');
}

html:not([data-theme="dark"]) .row-tools a.active {
  background: rgba(46, 87, 160, 0.07);
}

html:not([data-theme="dark"]) .row-tools .tool-placeholder {
  color: #adafb2;
  border-color: #e0e1e2;
}

/* ─── Theme toggle pill (rendered by cambria-nav.js) ─────── */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 14px;
  margin-left: 16px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--muted);
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all .15s;
  white-space: nowrap;
}
.theme-toggle:hover { color: var(--text); border-color: var(--muted); }
.theme-toggle .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
/* ─── Export buttons (rendered by cambria-export.js) ─────── */
.export-btns { display: inline-flex; gap: 5px; margin-left: auto; }
.export-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: var(--r-md);
  padding: 3px 8px;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .08em;
  cursor: pointer;
  transition: all .15s;
  line-height: 1.4;
}
.export-btn:hover { color: var(--accent); border-color: var(--accent); }

/* Shown only on paper */
.print-only { display: none; }
.print-band { }

/* ─── Print / PDF — formal report layout ───────────────────
   CambriaExport.printPDF() adds .printing to <body>, .print-target to
   the section being printed, and (optionally) a .print-cover block.

   Design goals, learned from v1 looking cobbled together:
     · single-column flow — CSS grids reserve empty cells when a sibling
       card breaks to the next page, which is what produced the large
       blank rectangles. Grids become block or tight flex on paper.
     · shorter charts so two or three fit a page instead of one plus a gap
     · repeating table headers across page breaks
     · ink-on-white regardless of screen theme
------------------------------------------------------------ */
@media print {
  @page { margin: 13mm 12mm; }

  html, body { background: #fff !important; background-image: none !important; }

  /* force the light palette on paper whatever the screen theme is */
  :root, html[data-theme="dark"] {
    --bg: #ffffff; --surface: #ffffff; --surface2: #f7f8fa;
    --border: #ccd2da; --text: #022a53; --muted: #5c6470;
    --muted2: #3b4757; --accent: #2e57a0; --accent2: #12719a;
    --fineprint: #6a737f;
    --teal: #00766e; --purple: #584389; --warn: #c0392b; --amber: #995c00;
    --data-pos: #008300; --data-neg: #e34948; --data-mid: #6f7785;
    --cat-stocks: #2a78d6; --cat-treasuries: #1baf7a;
    --cat-credit: #4a3aa7; --cat-alts: #eb6834;
  }

  body * { visibility: hidden; }
  .print-target, .print-target * { visibility: visible; }
  .print-target { position: absolute; left: 0; top: 0; width: 100%; margin: 0; padding: 0; }

  /* page chrome that should never print */
  .cambria-header, .cambria-rail, .cambria-footer-legal,
  .input-band, .view-tabs, .tab-bar, .mode-toggle, .export-btns,
  .theme-toggle, .send-row, .risk-controls, .exp-btn, .modal-backdrop,
  .loading, .saved-pills, button, .rep-link, .add-menu, .file-drop {
    display: none !important;
  }

  .print-only { display: block; }

  /* ── cover page ── */
  /* in normal flow inside .print-target — an absolutely positioned cover
     overlays page 1 rather than preceding it */
  .print-cover {
    position: static; width: 100%;
    break-after: page; page-break-after: always;
    padding: 4mm 0 0;
    /* fill the sheet so the break lands cleanly */
    min-height: 235mm;
  }
  .print-cover .pc-brand {
    display: flex; align-items: center; gap: 8pt;
    border-bottom: 1.5pt solid #022a53; padding-bottom: 7pt;
  }
  .print-cover .pc-brand img { height: 26pt; width: auto; }
  .print-cover .pc-brand span {
    font-family: var(--serif); font-size: 15pt; font-weight: 700; color: #022a53;
  }
  .print-cover h1 {
    font-family: var(--serif); font-size: 27pt; line-height: 1.15;
    color: #022a53; margin: 26pt 0 5pt; font-weight: 700;
  }
  .print-cover .pc-sub {
    font-family: var(--mono); font-size: 8.5pt; letter-spacing: .12em;
    text-transform: uppercase; color: #5c6470;
  }
  .print-cover .pc-date {
    font-family: var(--mono); font-size: 8.5pt; color: #5c6470; margin-top: 3pt;
  }
  .print-cover .pc-stats {
    display: flex; flex-wrap: wrap; gap: 7pt; margin: 26pt 0 0;
  }
  .print-cover .pc-stat {
    flex: 0 0 calc(33.333% - 5pt);
    border: .5pt solid #ccd2da; border-radius: 3pt; padding: 7pt 9pt;
  }
  .print-cover .pc-stat .k {
    font-family: var(--mono); font-size: 6.5pt; letter-spacing: .1em;
    text-transform: uppercase; color: #5c6470; margin-bottom: 3pt;
  }
  .print-cover .pc-stat .v {
    font-family: var(--serif); font-size: 15pt; font-weight: 700; color: #022a53;
  }
  .print-cover .pc-stat .s {
    font-family: var(--mono); font-size: 6.5pt; color: #5c6470; margin-top: 2pt;
  }
  .print-cover .pc-disc {
    margin-top: 30pt; padding-top: 8pt; border-top: .5pt solid #ccd2da;
    font-size: 7.5pt; line-height: 1.5; color: #5c6470;
  }

  /* ── running header on content pages ── */
  .print-band { border-bottom: 1pt solid #022a53; padding-bottom: 5pt; margin-bottom: 11pt; }
  .print-band strong {
    display: block; font-family: var(--serif); font-size: 13pt; color: #022a53;
  }
  .print-band span {
    font-family: var(--mono); font-size: 7pt; color: #5c6470; letter-spacing: .06em;
  }

  /* ── SINGLE-COLUMN FLOW: the fix for the blank rectangles ──
     Any multi-column grid becomes block on paper, so a card that breaks
     to the next page can't leave an empty cell behind it. */
  .charts-grid, .two-col, .three-col, .grid-top, .upcoming-grid,
  .chart-grid, .cal-grid {
    display: block !important;
  }
  .charts-grid > *, .two-col > *, .three-col > *, .grid-top > *,
  .chart-grid > * { margin-bottom: 9pt !important; }

  /* stat rows: tight flex packs without reserving cells */
  .cards, .rep-grid, .stats-grid {
    display: flex !important; flex-wrap: wrap !important; gap: 6pt !important;
  }
  .cards > *, .rep-grid > *, .stats-grid > * {
    flex: 0 0 calc(25% - 5pt) !important; min-width: 0 !important;
  }

  /* cards: quieter borders, no shadows */
  .chart-card, .card, .metric-card, .stat-card, .rep-stat, .ib-block {
    border: .5pt solid #ccd2da !important;
    box-shadow: none !important;
    background: #fff !important;
  }

  /* Splitting policy: cards stay whole. A chart cut in half is useless
     and a table continued across a page break reads as an error in a
     client-facing report, so density is solved by layout (see each
     tool's own @media print block) rather than by splitting cards.
     Very long tables (>1 page) still break, because they must. */
  .chart-card, .card, .metric-card, .chart-wrap, .chart-tall, .mini,
  .stat-card, .rep-stat, .pc-stat {
    break-inside: avoid; page-break-inside: avoid;
  }
  .stat-card, .rep-stat { padding: 6pt 8pt !important; }
  .stat-card .v, .rep-stat .v { font-size: 13pt !important; }
  .stat-card .k, .rep-stat .k { font-size: 6.5pt !important; }
  .stat-card .s, .rep-stat .s { font-size: 6.5pt !important; }

  /* Canvas sizing on paper.
     Chart.js writes inline width/height styles on the canvas, so CSS must
     use !important to win. Setting width:100% + height:auto makes the
     browser scale the bitmap by its own aspect ratio: no clipping (which
     cut the donut off), no distortion (which turned it into an oval), and
     no overflow into the card below. The wrapper must be auto-height and
     not hidden, or it re-crops what we just fixed. */
  canvas { width: 100% !important; height: auto !important; max-width: 100% !important; }
  .chart-wrap, .chart-tall, .mini {
    height: auto !important; overflow: visible !important;
  }
  .chart-title { font-size: 7.5pt !important; margin-bottom: 6pt !important; }

  /* tables: repeat headers, tighten, avoid orphan rows */
  table { break-inside: auto; page-break-inside: auto; width: 100% !important; }
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  tr { break-inside: avoid; page-break-inside: avoid; }
  /* One line per row: at screen sizes "US Large Cap +4" and
     "LOOK-THROUGH" wrapped, doubling every row's height. nowrap plus a
     smaller face keeps rows single-line; the name column ellipsises
     rather than wrapping if a fund name is very long. */
  .data-table, .bench-tbl, .cmp-table, .corr-table { font-size: 8pt !important; }
  .data-table th, .data-table td, .bench-tbl th, .bench-tbl td,
  .cmp-table th, .cmp-table td, .corr-table th, .corr-table td {
    padding: 4pt 5pt !important;
    white-space: nowrap !important;
  }
  .data-table td:nth-child(2), .data-table th:nth-child(2) {
    max-width: 150pt; overflow: hidden; text-overflow: ellipsis;
  }
  .tag { font-size: 5.6pt !important; padding: 0 3pt !important; }

  /* section dividers start a new page, sections keep their heading */
  .report-divider { break-before: page; page-break-before: always; }
  h2, h3, h4, .section-label { break-after: avoid; page-break-after: avoid; }
  p, li { orphans: 3; widows: 3; }

  a { text-decoration: none !important; color: #022a53 !important; }
  a[href^="http"]::after { content: ""; }

  .warn-bar, .error-bar, .hint {
    font-size: 7pt !important; border: .5pt solid #ccd2da !important;
  }

  /* closing disclaimer */
  .print-foot {
    margin-top: 14pt; padding-top: 7pt; border-top: .5pt solid #ccd2da;
    font-size: 7pt; line-height: 1.5; color: #5c6470;
  }
}

/* ===================================================================
   FINE PRINT — the disclosures, footnotes and provenance notes.

   A tester's note, 8/19/26: "I'd make the footnotes and disclosures below
   each section smaller and a lighter color. Currently, they kind of blend
   into the data you want people looking at."

   Right diagnosis. The fix is mostly NOT colour -- see --fineprint for why
   there is a floor -- so the hierarchy is carried by size, line-height and
   space, with colour going as light as AA allows and no lighter.

   This is deliberately one treatment rather than per-tool CSS: the platform
   spent two days making these disclosures correct, and correct-but-shouting
   is its own failure. They should be findable, not loud.
   =================================================================== */
.taa-fineprint,
.footnote,
.caveat,
.disclosure-note {
  font-size: var(--fineprint-size, .72rem);
  line-height: var(--fineprint-lh, 1.55);
  color: var(--fineprint, #6b7480);
  font-weight: 400;
  letter-spacing: .005em;
}
/* A block of fine print sits apart from the data rather than under it. */
.taa-fineprint {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
/* 500, NOT 600, AND THE REASON IS DM MONO. .footnote is mono at .74rem in
   both the Backtester and the Stress Tester, so a <strong> in one asked for
   DM Mono 600 -- and DM Mono ships exactly two static masters, 400 and 500,
   with no variable build. The rasteriser answered with a synthetic smear at
   12px, which is where it is least visible and most damaging. The rule is
   platform-wide because these two selectors are inherited into mono and
   sans contexts both, and 500 is honest in either: DM Sans has a real 500,
   DM Mono has a real 500, and nothing has a real 600. */
.taa-fineprint strong, .footnote strong { font-weight: 500; color: inherit; }
/* Definition lists of the "what these metrics mean" kind: the TERM stays
   legible because it is what someone scans for; the definition recedes. */
.taa-fineprint dt { color: var(--muted2); font-weight: 500; }
.taa-fineprint dd { color: var(--fineprint, #6b7480); }

/* ===================================================================
   AFFORDANCE — what can be clicked must look different from what cannot.

   Same tester: "make the color-scheme so that people can quickly determine
   what they can click on vs straight-up static data."

   The rule adopted: an ACCENT colour means "this does something". Numbers,
   labels and table cells wear text tokens only. That is the same discipline
   the chart layer already follows -- text wears text tokens, never a series
   colour -- extended to the page.

   Focus-visible is included because it was missing everywhere except the
   footer, which means the whole platform was unusable by keyboard.
   =================================================================== */
button, [role="button"], summary, label[for], select, .taa-click,
.preset-btn, .cmp-card, .scenario-card, .tab, .tab-btn {
  cursor: pointer;
}
/* One ring, everywhere, in the accent — so keyboard users get the same cue
   the mouse gets, and it never depends on a component remembering to add it. */
a:focus-visible, button:focus-visible, summary:focus-visible,
select:focus-visible, input:focus-visible, [tabindex]:focus-visible,
.taa-click:focus-visible, .preset-btn:focus-visible,
.cmp-card:focus-visible, .scenario-card:focus-visible {
  outline: 2px solid var(--accent2);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
/* Text that acts. Underline on hover rather than always, so a paragraph of
   prose with one live phrase in it does not read as a list of links. */
.taa-click, a.taa-click {
  color: var(--accent);
  text-decoration: none;
  transition: color .12s, text-decoration-color .12s;
}
.taa-click:hover { text-decoration: underline; text-underline-offset: 2px; }
/* A card that responds. The cue is the border, not a colour wash, so the
   data inside it keeps its own colours. */
.cmp-card, .scenario-card, .preset-btn {
  transition: border-color .12s, background .12s;
}
.cmp-card:hover, .scenario-card:hover, .preset-btn:hover {
  border-color: var(--accent2);
}
/* And the negative rule, which is the half that usually gets skipped: a
   value is not a control. Anything inside a stat or a data cell keeps text
   tokens even if a utility class tried to tint it. */
.stat-val, .kpi-val, .cmp-val, td.num, .mono-num {
  cursor: default;
}

/* ── Figures ─────────────────────────────────────────────────────────────
   TAA Serif's default figures are lining AND tabular -- every digit the
   same width with no feature applied -- which is the right default for a
   platform that is mostly columns. These rules keep that explicit and, more
   usefully, opt prose back OUT: tabular figures inside a sentence read
   gappy. The opsz axis is what earns font-optical-sizing; auto is the CSS
   default and is stated here so a reset elsewhere cannot quietly kill it.
   ──────────────────────────────────────────────────────────────────────── */
html{ font-optical-sizing:auto; }

table, td, th, .trust-num, .metric, .stat, [data-num]{
  font-variant-numeric: tabular-nums lining-nums;
}
p, .desc, .lede, .note, .legal-body{
  font-variant-numeric: proportional-nums;
}

/* ===================================================================
   THE ALLOCATION EDITOR — one definition, three tools.

   The Backtester and the Stress Tester carry byte-identical copies of
   every rule below, and the Frontier carried none of them: its left
   column was a flat list of fourteen number boxes with no grouping, no
   sliders and no composition bar, so the same portfolio looked like a
   different object depending on which tool you were standing in.

   The copies had already drifted where it counts. All three declare
   their own CAT_GROUPS, and the Frontier's was missing us_bonds_agg
   entirely -- 4/4/2/4 against the other two's 4/4/3/4, under a comment
   that said 4/4/3. A sleeve that exists in common/taa/assets.py, has
   data back to 1976, and cannot be expressed in one of the three tools
   that is supposed to be looking at the same portfolio.

   These rules are the shared copy. The two tools that duplicate them
   inline still win on source order, which is harmless while the rules
   are identical and is the next thing to remove.
   =================================================================== */
.comp-track{height:16px;border-radius:5px;background:var(--bg);overflow:hidden;display:flex;}
.comp-track > div{height:100%;transition:width .12s;}
.comp-legend{display:flex;flex-wrap:wrap;gap:8px 20px;margin-top:12px;
  font-family:var(--mono);font-size:.85rem;color:var(--muted2);}
.comp-legend span{display:inline-flex;align-items:center;gap:7px;}
.comp-legend .cd{width:10px;height:10px;border-radius:2px;flex-shrink:0;}
.comp-legend b{color:var(--text);font-weight:500;}
.ib-alloc{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:12px;}
.cat-card{background:var(--surface2);border:1px solid var(--border);
  border-radius:9px;padding:11px 13px;}
.cat-hd{display:flex;align-items:center;gap:8px;margin-bottom:8px;}
.cat-hd .cd{width:9px;height:9px;border-radius:2px;flex-shrink:0;}
.cat-hd .cn{font-family:var(--mono);font-size:.8rem;letter-spacing:.1em;
  text-transform:uppercase;}
.cat-hd .cs{margin-left:auto;font-family:var(--mono);font-size:.85rem;
  color:var(--muted2);}
.arow{display:flex;align-items:center;gap:12px;padding:5px 0;}
.arow.unavail{opacity:.5;}
.arow.unavail .arow-sl{cursor:not-allowed;}
.arow .an{font-size:.85rem;color:var(--text);flex:0 0 132px;}
.arow .av{font-family:var(--mono);font-size:.62rem;color:var(--warn,#d08b28);
  letter-spacing:.04em;flex:0 0 auto;white-space:nowrap;}
.arow-sl{flex:1;min-width:0;-webkit-appearance:none;appearance:none;height:6px;
  border-radius:4px;background:var(--bg);outline:none;cursor:pointer;}
.arow-sl::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:15px;height:15px;border-radius:50%;background:var(--thumb,var(--accent));
  cursor:pointer;border:2px solid var(--surface2);}
.arow-sl::-moz-range-thumb{width:13px;height:13px;border-radius:50%;
  background:var(--thumb,var(--accent));cursor:pointer;border:2px solid var(--surface2);}
.arow input[type=number]{width:50px;background:var(--bg);border:1px solid var(--border);
  color:var(--text);padding:5px;border-radius:5px;font-family:var(--mono);
  font-size:.85rem;text-align:center;outline:none;transition:border-color .15s;}
.arow input[type=number]:focus{border-color:var(--accent);}

/* ── Phones ─────────────────────────────────────────────────────────────
   MEASURED, NOT ESTIMATED. At a 390px viewport the tool pages had a
   scrollWidth of 730 and the landing page 591 -- both scrolled sideways,
   which is what "the mobile is messed up when i swipe over to the right"
   was. Three things did it, and only one of them was obvious:

     span.tool-tagline   715px wide, white-space:nowrap. "1972 – 2026 ·
                         15 Asset Classes · Monthly Returns · Powered by
                         Finaeon" cannot break, so it set the page width
                         on its own. It wraps here rather than being
                         hidden: it carries the data credit, and the rule
                         is that every source is credited.
     .row-brand          the wordmark at 1.7rem plus the by-Cambria link
                         and the theme toggle came to 438 against 390.
     .ib-alloc           minmax(300px,1fr) plus the card's own padding is
                         341px of grid track inside a 362px viewport. One
                         column below this width.

   Checked at 390, 360 and 320. */
@media (max-width: 620px) {
  .cambria-header { --header-padding-x: 12px; }
  .cambria-header .brand { font-size: 1.25rem; gap: 9px; }
  .cambria-header .brand-mark { height: 26px; }
  .cambria-header .cambria-logo img { height: 26px; }
  .cambria-header .tool-title { font-size: 1.25rem; }
  .cambria-header .tool-tagline {
    white-space: normal;
    font-size: .74rem;
    letter-spacing: .06em;
  }
  .cambria-header .row-tool { gap: 4px 12px; }
  /* Let the brand row wrap rather than picking a second breakpoint. The
     wordmark plus the by-Cambria link and the theme toggle come to 342 at
     360 and 367 at 320; above that they fit on one line and this changes
     nothing. Nothing is hidden -- the Cambria link is an attribution. */
  .cambria-header .row-brand { flex-wrap: wrap; row-gap: 6px; }
  /* minmax(0,1fr), not 1fr. A bare 1fr is minmax(auto,1fr) and `auto`
     floors the track at the content's min-content width -- measured, a
     341px track inside a 290px container, which is the whole overflow.
     The zero minimum lets the track be as narrow as the column is. */
  .ib-alloc { grid-template-columns: minmax(0, 1fr); }
  /* A canvas with no chart on it is 300x150 -- the HTML default -- and a
     static 300px box inside a 231px column scrolls the page sideways.
     Chart.js sizes these itself when it draws (responsive:true), so this
     never clamps a chart that worked; it catches the one whose fetch
     failed, which is the case where the page is already having a bad day. */
  canvas { max-width: 100%; }
}
