
  /* --- VANTIAS -----------------------------------------------------------
     One palette, named by ROLE rather than by hue, so a component never
     reaches for a raw colour. Everything downstream -- the dashboard sheet,
     the portfolio sheet, the reference blocks -- is written against these
     tokens, which is why the whole surface can be restyled from this block.

     THE GROUND IS DEEP GREY AND THE INK IS NEAR-WHITE. Turned over on
     16 Sep 2026: the light sheet read as paper, and this is an instrument
     somebody watches for hours. The canvas is a cool near-black grey, the
     panels sit a step LIGHTER than it, and the wells a step darker -- three
     levels, so a card reads as something resting on the surface and an input
     as a hole cut into it.

     NOT BLACK, AND NOT BLUE. Pure black makes every edge a hard line and
     every white letter glare; a navy tint is the default "dark mode" of every
     dashboard ever screenshotted. This grey is neutral with a trace of cool.

     THE SEMANTIC COLOURS ARE LIT, NOT NEON. MEASURED against the canvas
     #17191C, WCAG ratios: text 14.61, accent 11.22, muted 6.76, positive
     7.82, negative 5.78, warning 7.86, high 6.46, alternate 6.19. Every one
     clears 4.5:1 with room, because a number a holder reads at night is the
     whole product. */
  :root {
    color-scheme: dark;
    --bg: #17191C;            /* canvas            */
    --bg2: #131519;           /* inset, well       */
    --card: #1E2125;          /* card, form, table */
    --card-2: #23262B;        /* a card raised above a card */
    --border: #2A2E33;        /* hairline divider  */
    --line: #2A2E33;
    --rule-strong: #3A3F45;   /* a divider meant to be seen */
    --text: #E8EAED;          /* data and headings */
    --muted: #9BA1A8;         /* labels, prose     */
    --accent: #C9CFD6;        /* links, tabs, tags: platinum, not a hue */
    --accent2: #C9CFD6;
    --accent-soft: rgba(201,207,214,.10);
    --accent-line: rgba(201,207,214,.32);
    --pos: #56C08D;           /* profit, growth    */
    /* The step between "good" and the first sign of trouble. The SAME hue as
       --pos and lighter, rather than a yellow-green: measured with
       scripts/validate_palette.py, a yellow-green separates from --pos by 2.1
       under deuteranopia and this separates by 11.5 under protanopia, because
       lightness survives colour blindness and hue does not. 10.8:1 on a card. */
    --pos-weak: #A3E0C0;
    --neg: #E2736A;           /* loss              */
    --warn: #D7A54A;          /* risk, impact      */
    --high: #D98A4F;          /* the band between warn and loss */
    /* THE TINT AND THE EDGE THAT GO WITH EACH SEMANTIC INK. These exist
       because the components that needed them were writing raw rgba() of the
       OLD, LIGHT-SHEET greens and reds -- #1A7046 at 7% over a near-black
       card is nothing at all, so every "good" and "bad" pill quietly lost its
       fill when the sheet turned over. A tint on a dark ground has to be
       lighter than its ground, which means the tint is the INK at low alpha,
       never a second colour. */
    --pos-soft: rgba(86,192,141,.12);  --pos-line: rgba(86,192,141,.34);
    --neg-soft: rgba(226,115,106,.12); --neg-line: rgba(226,115,106,.34);
    --warn-soft: rgba(215,165,74,.12); --warn-line: rgba(215,165,74,.34);
    --high-soft: rgba(217,138,79,.12); --high-line: rgba(217,138,79,.34);
    /* The one hue that is not platinum: what is NOT a dollar market, and the
       YT leg. A category, not a severity, so it must not be read as either
       good or bad. 6.19 against the canvas. */
    --alt: #9A8CF0;
    --alt-soft: rgba(154,140,240,.12);
    --alt-line: rgba(154,140,240,.34);
    --on-accent: #16181B;     /* text ON the accent */
    --mark-tile: #DDE1E6;     /* the wordmark's ground */
    --mark-ink: #101215;      /* the V itself */

    /* SHADOW, NOT BORDER, IS WHAT SEPARATES. Two steps: one for what rests
       on the page, one for what floats above it -- an open disclosure, the
       language switch, the sign-in card. Wide, faint and neutral; a tight
       dark shadow is what made the old screen look cheap. */
    /* ON A DARK GROUND A SHADOW IS NOT ENOUGH ON ITS OWN: a panel is told
       apart by being lighter than the canvas, and the shadow only seats it.
       Deeper and softer than the light sheet's, with a hairline highlight on
       top of what floats -- the trick that makes glass look like glass. */
    --shadow-1: 0 1px 2px rgba(0,0,0,.34), 0 8px 24px rgba(0,0,0,.28);
    --shadow-2: 0 2px 8px rgba(0,0,0,.40), 0 24px 56px rgba(0,0,0,.44);
    --lift: inset 0 1px 0 rgba(255,255,255,.04);
    --ease: 160ms cubic-bezier(.22,.61,.36,1);

    /* A scale with real distance between a label and the number it labels:
       the two used to sit .22rem apart, which is why the screen read as one
       undifferentiated block. */
    --fs-2xs: .69rem; --fs-xs: .75rem; --fs-sm: .83rem; --fs-sm2: .9rem;
    --fs-base: .97rem; --fs-lg: 1.15rem; --fs-xl: 1.45rem; --fs-2xl: 1.75rem;
    --fs-data: 1.22rem;       /* the number a card exists to show */

    --pad-card: 1.25rem 1.35rem;
    --pad-cell: .75rem .85rem;
    /* Softer corners throughout: the old 10/6 read as a terminal window. */
    --radius: 14px;
    --radius-sm: 10px;
    --radius-xs: 7px;
  }
  @media (prefers-reduced-motion: reduce) {
    :root { --ease: 1ms linear; }
  }
  * { box-sizing: border-box; }
  body {
    margin: 0; background: var(--bg); color: var(--text);
    font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: var(--fs-base); line-height: 1.5;
    -webkit-font-smoothing: antialiased;
  }
  /* A LINK IS NOT A COLOUR HERE. The accent is graphite, so an underline --
     offset and thin -- is what says "this is a link"; colour alone would not,
     and on this ground nothing should have to shout to be found. */
  a { color: var(--accent); text-underline-offset: .2em;
      text-decoration-thickness: 1px; text-decoration-color: var(--accent-line);
      transition: text-decoration-color var(--ease), color var(--ease); }
  a:hover { text-decoration-color: var(--accent); }
  code, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

  /* SCROLLBARS BELONG TO THE PALETTE TOO. Left alone, a browser draws its own
     -- which on this dark ground is a bright white bar across the bottom of
     every table that scrolls sideways, and the portfolio has one under a nine
     column table where it is impossible to miss.

     Both syntaxes, because they are not interchangeable: `scrollbar-color` is
     the standard and Firefox honours it; the `::-webkit-` pseudo-elements are
     what Chrome and Safari read. Declared on `*` rather than on `body` so a
     bar drawn inside a scrolling box is styled as well as the page's own. */
  /* THE `hidden` ATTRIBUTE MUST WIN. A browser hides `[hidden]` through its
     own stylesheet, and ANY author rule that sets `display` on the same
     element beats it -- author styles outrank the user agent's.

     Found live: the portfolio's undo bar carried `hidden` from the moment the
     page loaded and was visible anyway, because `.pf-undo { display:flex }`
     sat one line below. It read as a permanent "Position removed. Undo" that
     referred to nothing. Nobody had written a bug; two correct lines simply
     disagreed, and the cascade settled it the wrong way.

     Declared once, here, so no component has to remember. `!important` is the
     right tool exactly once: `hidden` is not a style, it is a statement that
     the element is not currently relevant, and nothing should be able to
     argue with it. */
  [hidden] { display: none !important; }

  * { scrollbar-width: thin; scrollbar-color: var(--rule-strong) transparent; }
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--rule-strong); border-radius: 999px;
    border: 2px solid transparent; background-clip: content-box;
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }
  ::-webkit-scrollbar-corner { background: transparent; }

  /* Every figure in this service lines up in a column somewhere. */
  b, strong, td.num, .num { font-variant-numeric: tabular-nums; }

  .muted { color: var(--muted); }
  .pos { color: var(--pos); } .neg { color: var(--neg); } .mid { color: var(--warn); }
  .ok { color: var(--pos); } .bad { color: var(--neg); }
  .ico { width: 1em; height: 1em; vertical-align: -.12em; }
  .ico-warn { color: var(--warn); }

  /* --- THE LABEL TREATMENT ------------------------------------------------
     A terminal column header, applied wherever a word names a number. Small,
     recessive, uppercase and tracked so it reads as a header rather than as
     prose competing with the figure beside it. */
  .lbl, .pt-grid span, .pt-metrics span, .tb-grid > div > span:first-child {
    font-size: var(--fs-2xs); color: var(--muted); text-transform: uppercase;
    letter-spacing: .07em; font-weight: 600; line-height: 1.35;
  }
  /* How often the rate cleared its hurdle, per window. A row of small facts
     under the metrics, not a chart: two numbers and their spread. */
  .pt-hh { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 18px;
           margin: 6px 0 2px; }
  .pt-hh-lbl { font-size: var(--fs-2xs); color: var(--muted);
               text-transform: uppercase; letter-spacing: .07em;
               font-weight: 600; }
  .pt-hh-w { display: inline-flex; align-items: baseline; gap: 6px; }
  .pt-hh-w small { font-size: var(--fs-2xs); color: var(--muted); }

  /* The reason a break-even is out of reach, under the number itself. Not
     uppercase and not tracked: it is a sentence, not a column header, and the
     rule above would otherwise catch it and shout it. */
  .pt-metrics .pt-be {
    display: block; font-size: var(--fs-2xs); color: var(--muted);
    text-transform: none; letter-spacing: 0; font-weight: 400;
    margin-top: 2px; line-height: 1.35;
  }

  /* --- PILLS --------------------------------------------------------------
     Metadata reads as a badge, not as a run-on sentence. One primitive, three
     tones, used by the tags, the risk badges and the reference blocks. */
  .pill, em.ok, em.mid, em.bad, .pt-tag, .pt-count, .pf-type, .pf-src,
  .tb-new, .vcode {
    display: inline-flex; align-items: center; gap: .3rem;
    font-style: normal; font-size: var(--fs-2xs); font-weight: 600;
    letter-spacing: .04em; border-radius: 999px;
    padding: .18rem .55rem; background: var(--bg2);
    border: 1px solid var(--border); color: var(--muted);
  }
  em.ok { color: var(--pos); border-color: var(--pos-line); background: var(--pos-soft); }
  em.mid { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); }
  em.bad { color: var(--neg); border-color: var(--neg-line); background: var(--neg-soft); }

  /* --- TIME TO MATURITY ---------------------------------------------------
     A bar under the figure, so a glance separates a market maturing this
     month from one maturing next year without reading either number.
     THE DENOMINATOR IS A FIXED ONE-YEAR HORIZON, not the market's own launch
     date: the launch is not on the scan path, and a bar drawn against a start
     this service does not have would be a picture of an assumption. Full bar
     means a year or more left; an empty one means days. */
  .mt-bar {
    display: block; height: 3px; margin-top: .35rem; border-radius: 2px;
    background: var(--bg2); overflow: hidden;
  }
  .mt-bar > i {
    display: block; height: 100%; border-radius: 2px;
    background: var(--accent); opacity: .75;
  }
  .mt-bar.is-short > i { background: var(--warn); opacity: 1; }

  /* --- WHERE IT LIVES -----------------------------------------------------
     The chain glyph, and what it is NOT. It was a filled grey square holding
     a place for protocol icons that never arrived, and a filled rectangle of
     nothing is the universal picture of a broken image. This is a hollow
     hexagon at the weight of the words around it: contextual, and quiet
     enough that the eye passes straight over it to the name. */
  .ico-chain {
    width: .82em; height: .82em; margin-right: .3rem; vertical-align: -.09em;
    stroke-width: 1.6; opacity: .72; flex: none;
  }

  /* --- FORMS --------------------------------------------------------------
     Inset against the panel, not raised: an input is a hole in the card. */
  input, select, textarea {
    background: var(--card); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: .6rem .7rem; font: inherit;
    font-size: var(--fs-sm2);
    transition: border-color var(--ease), box-shadow var(--ease);
  }
  input::placeholder { color: var(--muted); opacity: .55; }
  input:hover, select:hover { border-color: var(--rule-strong); }
  input:focus, select:focus, textarea:focus {
    outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
  }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
                   border-radius: var(--radius-xs); }

  header.top {
    padding: 1.6rem 1.35rem .8rem; border-bottom: 1px solid var(--border);
    background: var(--card);
  }
  header.top h1 {
    margin: 0 0 .3rem; font-size: var(--fs-2xl); font-weight: 700;
    letter-spacing: -.015em; display: flex; gap: .5rem; align-items: center;
  }
  .h1-icon { color: var(--accent); display: inline-flex; }
  header.top .sub {
    margin: 0 0 .8rem; font-size: var(--fs-sm); color: var(--muted);
    font-weight: 400; max-width: 78ch;
  }
  footer {
    margin: 2.5rem 0 0; padding: 1.25rem 1.35rem 2.5rem;
    border-top: 1px solid var(--border);
    font-size: var(--fs-sm); color: var(--muted); font-weight: 400;
  }
  .vline { margin-top: .6rem; font-size: var(--fs-xs); }
  .vcode { background: var(--bg2); }
  main { max-width: 1180px; margin: 0 auto; padding-bottom: 1rem; }

  /* --- THE WORDMARK -------------------------------------------------------
     The drawn V and the name as one unit. Quiet: the page's subject is the
     market, not the product, so the mark is small and the name is set in
     tracked caps rather than at a larger size. */
  .vt-word { display: inline-flex; align-items: center; gap: .45rem;
             margin-right: auto; }
  .vt-mark { border-radius: 5px; display: block; flex: none; }
  .vt-name { font-weight: 700; letter-spacing: .17em; text-transform: uppercase;
             font-size: var(--fs-2xs); color: var(--text); }
  .h1-icon .vt-mark { border-radius: 8px; }

  /* --- THE EXPLAINED TERM -------------------------------------------------
     A dotted-underlined word with a small box behind it, and the reason it
     replaced `title=`: a native tooltip does not exist on a touch screen, does
     not appear for eight hundred milliseconds on a desktop, cannot hold two
     sentences, and cannot be read by anyone navigating with a keyboard. The
     words being explained here -- "extreme", "denomination", "control" -- are
     the ones a holder most needs explained, so they were in the worst possible
     place for it.

     IT IS A BUTTON, not a hover target. Click, Enter and Space all open it;
     Escape and the next click anywhere close it. The box is positioned by the
     browser against the term itself, which is why the term carries
     `position:relative` and nothing measures anything in script. */
  .vt-term { position: relative; display: inline-flex; align-items: center; }
  button.vt-key {
    font: inherit; color: inherit; background: none; border: 0; padding: 0;
    cursor: help; text-decoration: underline dotted var(--accent-line);
    text-underline-offset: .22em; text-decoration-thickness: 1px;
    display: inline-flex; align-items: center; gap: .25rem;
  }
  button.vt-key:hover { text-decoration-color: var(--accent); }
  button.vt-key::after {
    content: "?"; font-size: .62em; font-weight: 700; line-height: 1;
    width: 1.15em; height: 1.15em; border-radius: 999px; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--muted); border: 1px solid var(--border);
    background: var(--bg2); transition: color var(--ease),
                border-color var(--ease);
  }
  button.vt-key[aria-expanded="true"]::after,
  button.vt-key:hover::after { color: var(--text); border-color: var(--rule-strong); }
  /* A badge that explains itself keeps its own shape: no dotted rule, and the
     question mark would crowd it, so the badge IS the affordance. */
  button.vt-key.is-badge { text-decoration: none; cursor: help; }
  button.vt-key.is-badge::after { content: none; }
  /* FIXED, not absolute, and that is not a detail. Two of the places a term
     appears -- the risk cell of the positions table, and the market table --
     live inside a box with `overflow-x:auto`, which clips anything drawn
     outside it. An absolutely-positioned box would have had its explanation
     cut off exactly where the explanation was most needed. Fixed takes it out
     of every scroll container; the script places it against the button and
     closes it if the page scrolls underneath. */
  .vt-pop {
    position: fixed; z-index: 40; pointer-events: auto;
    width: max-content; max-width: min(30ch, 78vw);
    background: var(--card-2); color: var(--text);
    border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2), var(--lift);
    padding: .6rem .7rem; font-size: var(--fs-xs); font-weight: 400;
    line-height: 1.45; text-align: left; white-space: normal;
    animation: vt-pop var(--ease) both;
  }

  .vt-pop b { display: block; margin-bottom: .2rem; font-size: var(--fs-2xs);
    letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
  @keyframes vt-pop {
    from { opacity: 0; transform: translateY(-3px); }
    to { opacity: 1; transform: none; }
  }
  @media (prefers-reduced-motion: reduce) { .vt-pop { animation: none; } }

  /* While a swap is in flight. A whole-page spinner would be a bigger
     interruption than the reload this replaced; this is a hairline at the top
     of the window and a slight dimming of the column being replaced -- enough
     to say "asked", not enough to make the reader wait for it. */
  .vt-loading main { opacity: .55; transition: opacity var(--ease); }
  .vt-loading::before {
    content: ""; position: fixed; inset: 0 0 auto; height: 2px; z-index: 90;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: vt-sweep 900ms linear infinite;
  }
  @keyframes vt-sweep {
    from { transform: translateX(-100%); } to { transform: translateX(100%); }
  }
  @media (prefers-reduced-motion: reduce) {
    .vt-loading::before { animation: none; opacity: .5; }
    .vt-loading main { transition: none; }
  }

  /* =========================================================================
     THE CHART COMPONENT, shared by both pages.
     It was built for the portfolio and lived there, which left every chart on
     the dashboard a picture again -- the same complaint, one page over. A
     component used by two pages belongs where both can reach it.
     The class names keep their `pf-` prefix: they are names, renaming them
     across the stylesheets, the scripts and the tests would change nothing
     that a reader can see.
     ========================================================================= */
  /* --- A CHART YOU CAN ASK A QUESTION OF ----------------------------------
     THE GRID, THE AXES AND THE CARD. These were sparklines: a shape with no
     scale on it, so a rising line said the book had grown and nothing more.
     Three things changed -- rules at named values, dates along the bottom, and
     a card that names every series under the pointer with its own colour.

     THE LABELS ARE HTML AND THE PLOT IS SVG. The plot is stretched to the
     column's width, which is right for a line and fatal for a letter; type
     inside it would stretch with the container. Outside, positioned by
     percentage, it stays the size it was set in. */
  /* The sparkline wears the same component but stays inline: it lives inside
     a line of text on a card, not in a column of its own. */
  .spark-plot { display:inline-block; position:relative; }
  .spark-plot .pf-canvas { display:inline-block; }
  .spark-plot .pf-readout { min-width:6.5rem; }
  .pf-plot { position:relative; display:grid;
    grid-template-columns:auto 1fr; grid-template-rows:auto auto auto;
    column-gap:.45rem; margin:.15rem 0 .2rem; }
  .pf-canvas { position:relative; grid-column:2; grid-row:1; }
  .pf-plot svg { cursor:crosshair; touch-action:pan-y; display:block;
    width:100%; }
  /* The y labels sit against the plot's own height, each pinned to the rule it
     names -- translated up by half a line so the text centres on the rule. */
  .pf-yaxis { position:relative; grid-column:1; grid-row:1; width:3.1rem; }
  .pf-yaxis > span { position:absolute; right:0; transform:translateY(-50%);
    font-size:var(--fs-2xs); color:var(--muted); white-space:nowrap;
    font-variant-numeric:tabular-nums; }
  .pf-xaxis { position:relative; grid-column:2; grid-row:2; height:1.05rem;
    margin-top:.2rem; }
  .pf-xaxis > span { position:absolute; transform:translateX(-50%);
    font-size:var(--fs-2xs); color:var(--muted); white-space:nowrap; }
  /* The first and last labels are pulled inside the box rather than hanging
     off its edges, which is where a centred label at 0% and 100% would sit. */
  .pf-xaxis > span:first-child { transform:none; }
  .pf-xaxis > span:last-child { transform:translateX(-100%); }
  .pf-plot-note { grid-column:2; grid-row:3; margin:.35rem 0 0;
    font-size:var(--fs-2xs); color:var(--muted); line-height:1.45; }
  /* RECESSIVE ON PURPOSE. A grid is there to be measured against, not to be
     seen; at this weight it reads as texture until the eye looks for it. */
  .pf-grid { stroke:var(--border); stroke-width:1; opacity:.55;
    vector-effect:non-scaling-stroke; pointer-events:none; }
  /* THE LINE IS SOLID, AND THAT IS THE POINT OF IT. It was a dotted hairline
     at half opacity, which is a decoration; what a reader needs is a straight
     edge they can run their eye down to the date axis and up to the value
     axis. A dashed line breaks that edge into pieces and the eye has to
     rebuild it. Solid, at an opacity that still sits under the series. */
  .pf-cross { stroke:var(--accent); stroke-width:1; opacity:.6;
    vector-effect:non-scaling-stroke; pointer-events:none; }
  /* AN HTML DOT, NOT AN SVG CIRCLE -- see `_plot_frame`: these plots are
     stretched unevenly, and a circle drawn inside one comes out an ellipse.
     Positioned by percentage over the canvas, so it is round at any width.
     `translate(-50%,-50%)` puts its CENTRE on the reading rather than its
     top-left corner. */
  .pf-mark { position:absolute; z-index:15; width:.5rem; height:.5rem;
    margin:0; border-radius:50%; background:var(--card);
    border:2px solid var(--accent); transform:translate(-50%, -50%);
    pointer-events:none; }
  .pf-band { fill:var(--accent); opacity:.09; pointer-events:none; }
  /* PINNED LOOKS PINNED. A frozen reading and a live one must not be
     indistinguishable: the crosshair brightens and thickens and the card takes
     the accent edge, so nobody reads yesterday's point as today's pointer.
     It used to be the dashes that went away on pinning; now that the line is
     solid while hovering, weight and opacity carry the difference. */
  .pf-plot.is-pinned .pf-cross { stroke-width:1.5; opacity:.95; }
  .pf-plot.is-pinned .pf-readout { border-color:var(--accent-line); }
  .pf-plot.is-pinned svg { cursor:default; }

  /* --- THE READOUT CARD ---------------------------------------------------
     A heading with the day, then one row per series: its colour, its name,
     its number. It replaced a single run-on line, which worked for one value
     and fell apart the moment a day had both a result and a money movement. */
  .pf-readout { position:absolute; top:.1rem; z-index:20; pointer-events:none;
    background:var(--card-2); border:1px solid var(--rule-strong);
    border-radius:var(--radius-sm); box-shadow:var(--shadow-2), var(--lift);
    padding:.4rem .55rem; font-size:var(--fs-2xs); white-space:nowrap;
    font-variant-numeric:tabular-nums; min-width:8.5rem; }
  .pf-ro-head { color:var(--muted); font-weight:600; letter-spacing:.04em;
    padding-bottom:.28rem; margin-bottom:.28rem;
    border-bottom:1px solid var(--border); }
  .pf-ro-row { display:flex; align-items:center; gap:.4rem; line-height:1.55; }
  .pf-ro-row > i { width:.5rem; height:.5rem; border-radius:2px; flex:none; }
  .pf-ro-row > span { color:var(--muted); }
  .pf-ro-row > b { margin-left:auto; color:var(--text); font-weight:700; }
  .pf-ro-row > b.pos { color:var(--pos); }
  .pf-ro-row > b.neg { color:var(--neg); }


  /* --- WHAT RESTS AND WHAT FLOATS -----------------------------------------
     Two elevations, applied here once so no component invents a third. A
     panel at rest carries `--shadow-1`; anything the reader has opened, or
     that sits above the page, carries `--shadow-2` and a slightly wider
     radius. The lift on open is what the old flat-bordered screen lacked. */
  .pt-card, .pf-form, .pf-tablewrap, .pt-summary div, .lg-card,
  .pf-undo, .pf-sum, .rk-sys {
    box-shadow: var(--shadow-1);
    transition: box-shadow var(--ease), border-color var(--ease);
  }

  /* --- THE SHARED TABLE, AND WHY IT LIVES HERE ----------------------------
     `.pf-table` inside `.pf-tablewrap` is used by two pages: the book and the
     engine. The rules lived in the BOOK's stylesheet, which the engine page
     does not load -- so the engine's three tables were served with no width,
     no padding, no rules between cells, and, worst of it, no scrolling box.
     A five-column table with prose in the first cell then pushed the whole
     document sideways on a phone, taking the pinned bar out of reach with it.

     A component used by two pages is not one page's furniture. The same
     lesson the language switch taught, in a stylesheet instead of a rule.

     NO `min-width` HERE. The book's nine columns need a floor to scroll
     against; the engine's five do not, and forcing 52rem on them invents a
     sideways scroll on a table that would have fitted. The floor belongs to
     the table that needs it, in the sheet that knows. */
  .pf-tablewrap { overflow-x: auto; border-radius: var(--radius);
    background: var(--card); border: 1px solid var(--border); }
  .pf-table { width: 100%; border-collapse: collapse;
    font-size: var(--fs-sm2); }
  .pf-table th, .pf-table td { padding: .8rem .95rem; text-align: left;
    border-bottom: 1px solid var(--border); white-space: nowrap;
    vertical-align: top; }
  .pf-table th { font-size: var(--fs-2xs); color: var(--muted);
    font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
    white-space: nowrap; background: var(--bg2); }
  /* The figures are the reason for the table, so they are set like figures. */
  .pf-table td.num { text-align: right; font-size: var(--fs-base);
    font-weight: 600; }
  .pf-table th.num { text-align: right; }
  .pf-table tbody tr:hover td { background: var(--accent-soft); }
  .pf-table tbody tr:last-child td { border-bottom: 0; }
  .pf-table small { font-size: var(--fs-2xs); margin-left: .4rem;
    color: var(--muted); font-weight: 400; }
  .pf-empty { text-align: center; color: var(--muted); padding: 2rem .7rem; }
  .pt-card:hover { box-shadow: var(--shadow-2); }
  details[open] > summary ~ * { animation: vt-unfold var(--ease) both; }
  @keyframes vt-unfold {
    from { opacity: 0; transform: translateY(-2px); }
    to { opacity: 1; transform: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    details[open] > summary ~ * { animation: none; }
    .pt-card:hover { box-shadow: var(--shadow-1); }
  }
  summary { transition: color var(--ease); }

/* A SEGMENTED SWITCH, not two links. It read as body text with a border round
   it: underlined on hover, no affordance, and small enough to miss. A switch
   says "a control with two positions, one of them chosen", which is what it
   is. */
.pt-langs{position:static;display:inline-flex;
  padding:3px;gap:2px;border-radius:999px;background:var(--bg2);
  border:1px solid var(--border);z-index:5;
  box-shadow:var(--shadow-1)}
.pt-lang{display:inline-flex;align-items:center;justify-content:center;
  min-width:2.6rem;min-height:1.9rem;padding:0 .6rem;border-radius:999px;
  font-size:.78rem;font-weight:600;letter-spacing:.05em;color:var(--muted);
  text-decoration:none;border:0;transition:background var(--ease),color var(--ease)}
.pt-lang:hover{color:var(--text);background:var(--accent-soft);
  text-decoration:none}
.pt-lang.is-on{background:var(--accent);color:var(--on-accent);
  box-shadow:var(--shadow-1)}
.pt-lang.is-on:hover{background:var(--accent);color:var(--on-accent)}
.pt-lang:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* IN FLOW, BESIDE THE LINKS -- not floating over them.
   Absolute positioning put the switch at the top-right corner, which is where
   the navigation already was: on the dashboard it covered the last 1.2rem of
   "Sign out", on the portfolio page it covered the link outright. Tapping sign
   out changed the language instead, which is why sign-out looked broken. A
   pixel offset would not have fixed it either -- the links are translated, so
   their width changes with the language the switch selects. A flex row cannot
   overlap at any width, in any language. */
.pt-bar{display:flex;justify-content:flex-end;align-items:center;
  gap:.7rem;margin-bottom:.2rem}
/* THE DEFAULT IS IN FLOW, AND THAT IS THE FIX FOR A BUG THAT CAME BACK FOUR
   TIMES. The switch used to be absolute by default and reset to static inside
   an allow-list of containers, with a comment saying every new home had to add
   itself. Three homes were added and the fourth -- the about page's own
   `<nav>` -- was missed, so on a phone the switch sat on top of "sign in".
   An allow-list that must be edited by whoever adds a header is not a rule,
   it is a trap with a note attached.

   So the polarity is inverted. In flow, always; floating only where a page
   has no bar to put it in, which is exactly one page: the sign-in form, whose
   layout is a centred card on an empty sheet. A new header anywhere else gets
   the correct behaviour without knowing this file exists. */
.lg-body>.pt-langs{position:absolute;top:.9rem;right:.9rem}
.pt-bar .pt-signout{position:static;top:auto;right:auto}

/* The switch is two targets side by side and they were 38x34 -- under the
   forty-four a finger needs, on the one control every page carries. The
   height comes from the shared rule in RESPONSIVE_CSS; the width is here. */
@media (max-width:640px){.lg-body>.pt-langs{top:.6rem;right:.6rem}
  .pt-lang{min-width:2.9rem}}
@media (prefers-reduced-motion:reduce){.pt-lang{transition:none}}

/* --- THE PINNED WAY BACK, on every page you go INTO ------------------------
   See `subnav()`. It is deliberately the size of a control: the link it
   replaced was small grey text under a heading, and a heading three screens
   up is not a way out of anything. It carries the language switch with it so
   a sub-page has one bar rather than two things pinned at different heights.

   OPAQUE, like the dashboard's own pinned bar, because content scrolls
   underneath it and on a dark sheet translucent text over moving text is
   unreadable for the second it takes to pass. */
.vt-subnav{position:sticky;top:0;z-index:31;display:flex;align-items:center;
  gap:.9rem;flex-wrap:wrap;background:var(--bg);
  border-bottom:1px solid var(--border);
  padding:.55rem 1.35rem;margin:0 0 1.1rem}
.vt-back{display:inline-flex;align-items:center;gap:.45rem;
  font-size:var(--fs-sm);font-weight:600;letter-spacing:.01em;
  text-decoration:none;color:var(--text);
  border:1px solid var(--rule-strong);background:var(--card-2);
  border-radius:var(--radius-sm);padding:.42rem .8rem;
  transition:border-color var(--ease),color var(--ease)}
.vt-back:hover{border-color:var(--accent);color:var(--accent)}
.vt-back:hover .ico-left{transform:translateX(-2px)}
.vt-subnav .pt-langs{margin-left:auto}
@media (max-width:640px){.vt-subnav{padding:.5rem .8rem;gap:.6rem}}

  @media (max-width: 860px) {
    body { font-size: 1rem; }
    main, header.top, footer { padding-left: 1rem; padding-right: 1rem; }
    header.top h1 { font-size: 1.45rem; }
    .pt-grid, .pt-summary, .pt-metrics {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .tb-grid { grid-template-columns: 1fr; }
    .pt-card { padding: 1rem .9rem; }
    .pf-tablewrap { -webkit-overflow-scrolling: touch; }
    .pt-controls, .pf-form { gap: .7rem; }
    .pt-controls label, .pf-field { flex: 1 1 100%; }
    .pt-controls input, .pf-field input, .pf-field select { width: 100%; }

    /* --- SIXTEEN PIXELS, AND IT HAS TO BE PIXELS ------------------------
       Safari on iOS ZOOMS THE WHOLE PAGE IN when a form control smaller
       than 16px takes focus, and it does not zoom back out when the field
       is left. The controls were set at `--fs-sm2`, which is .9rem -- 14.4
       pixels -- so tapping the sign-in field, the board's search, a filter
       select or the wallet box left the reader on a page half again too
       wide, scrolled somewhere they did not choose, with no way back but a
       pinch. Every form on the service was affected.

       NOT a rem: the threshold is absolute, and a rem is only 16px while
       nobody has changed their browser's text size. Somebody who set theirs
       larger is fine either way; somebody who set it smaller would get the
       zoom back with a rem and never know why. */
    input, select, textarea { font-size: 16px; }

    /* Forty-four pixels is the smallest target a finger hits reliably. */
    .pt-tab, .pt-btn, .pf-submit, .pf-refresh, .pt-sort, .pf-del,
    .pt-showall, .vt-back, .pf-open, .lg-submit, .pt-lang { min-height: 44px; }
    .pt-tab { padding: .65rem .9rem; }
    .tb > summary, .pt-risk summary, .pt-trust summary, .pt-loop summary {
      padding: .5rem 0; min-height: 44px;
    }
    .tb-watch, .pt-note, .pt-note-row { font-size: .88rem; }

    /* --- THE PINNED BAR ON A NARROW SCREEN ------------------------------
       Two rows instead of one, and the selects go full width: three of them
       squeezed onto a phone's width are three targets too small to hit. The
       count moves under them rather than fighting for the same line. */
    .pt-stick { padding: .45rem 1rem .5rem; }
    .pt-filters { gap: .45rem; }
    .pt-f { flex: 1 1 8.5rem; }
    .pt-f select { flex: 1 1 auto; width: 100%; min-height: 38px; }
    .pt-fcount { margin-left: 0; flex-basis: 100%; }
    /* THE HERO CELL STOPS SPANNING. At two columns a cell spanning both is
       the whole row, which is right -- but at one column the span is a no-op
       that leaves the grid confused about its own width. */
    .pt-grid > div.pt-hero { grid-column: span 2; }
    .pt-hero b { font-size: 1.45rem; }
    /* The actions drop to their own line rather than crushing the name. */
    .pt-actions { margin-left: 0; flex-basis: 100%; }
    .pt-cta { min-height: 38px; }
    /* A popover that is 30 characters wide on a phone runs off the screen. */
    .vt-pop { max-width: calc(100vw - 24px); }
  }
  @media (max-width: 520px) {
    .pt-grid, .pt-summary, .pt-metrics { grid-template-columns: 1fr; }
    .pt-grid > div.pt-hero { grid-column: auto; }
    .pt-head { gap: .4rem; }
    .pt-name { font-size: 1.05rem; }
    header.top h1 { font-size: 1.3rem; flex-wrap: wrap; }
    .pf-head h1 { font-size: 1.3rem; }
    /* One tab strip that scrolls sideways beats three tabs wrapped onto two
       lines and pushing the filters off the top of the screen. */
    .pt-stick-row .pt-tabs { overflow-x: auto; flex-wrap: nowrap;
                             -webkit-overflow-scrolling: touch; }
    .pt-tabs::-webkit-scrollbar { display: none; }
    .pt-tab { white-space: nowrap; }
  }

  /* --- A TALL, NARROW WINDOW ON A DESKTOP -------------------------------
     The pinned bar earns its place by being small. On a short window it can
     take a quarter of the screen, at which point the thing it was meant to
     help -- reading the board -- is what it is costing. */
  @media (max-height: 560px) {
    .pt-stick { position: static; }
  }
