/*
 * One hand-written stylesheet. Design tokens as CSS custom properties.
 *
 * This file is the console framework's whole design language — tokens, the
 * rail, the shell, motion, depth, press feedback, the shared components — as it
 * landed in tag-bids-app, generalised. EVERY variant a component accepts has a
 * rule here (FE8). When you add a tone to the Tone union in components/ui.tsx,
 * add its rule in the same commit and look at it.
 *
 * THE PALETTE IS PERENNIAL'S AND IT DOES NOT CHANGE PER CLIENT. A console is
 * our tool, wearing our colours, run for a client — not a copy of their website.
 * Sampling the client's brand produced consoles indistinguishable from the
 * client's own product, which is the incident behind this file's current shape.
 * See frontend FE10 and DECISIONS.md, 2026-09-15.
 *
 * Because the accent is now FIXED, every number below is a measurement rather
 * than a shape to copy. They were computed against these exact tokens and they
 * are true as written. The ones that matter most, because they are the ones a
 * guess gets wrong:
 *
 *     --accent #2d7560   relative luminance 0.141 — a DARK ground
 *     off-white on it     5.49   <- the label on an accent fill
 *     --ink on it         3.36   <- FAILS. Ink is never the label on green.
 *     --accent on --bg    4.90   so the accent may be text as it stands
 *     --accent on forest  2.89   <- FAILS. The dark rail's accent text is
 *                                   --color-signal, not the accent. FE14.
 */

:root {
  /* --- THE PERENNIAL PALETTE. Fixed. Not a per-client slot. ---

     These four are Perennial's brand tokens, taken verbatim from
     perennial-design-system/tokens/colors.css, which takes them from the
     marketing site's globals.css. Do not re-derive them from a logo export: the
     same cyan existed there in four values inside 2/255 of each other, and every
     ratio written down here is measured against THESE. FE10. */
  --accent: #2d7560;   /* --color-green. Primary fills, active tabs, the rail marker */
  --ink: #101512;      /* --color-black. Body text. Near-black with a green cast that
                          is invisible at 7% lightness, and every ratio below is
                          measured against it. 16.48 on --bg. */
  --bg: #f4f2ec;       /* --color-off-white. Page background — not white, so soft
                          depth (FE19) has a highlight to show against */
  --line: #dedcd4;     /* --color-stone. Borders and table rules */

  /* The second brand colour, for a link that has to separate itself from the
     accent on a dark ground. --color-signal: 5.55 on forest, where the accent
     itself is 2.89 and unusable. */
  --signal: #2fa7b8;

  /* --- derived from the accent, so they follow it ---

     A saturated brand accent is usually too light to be TEXT on white, and often
     too light to take a white LABEL either. --accent-text is the accent darkened
     until it clears 4.5:1 on --surface, and it carries every accent-coloured
     word in the file; --accent-press is the primary button's hover fill — DARKER
     than the accent when the label is white, so the state you reach for is
     better than the one you started in.

     These were a color-mix when the accent was unknown at authoring time. The
     accent is now fixed, so they are LITERALS, measured once and true as
     written. Each number is the ratio that decides whether the token is allowed
     to do its job. FE14. */
  --accent-text:    #2d7560;   /* the accent itself: 5.49 on --surface, 4.90 on --bg.
                                  It clears 4.5 unaided, so accent-coloured WORDS
                                  need no darkened variant. The token stays because
                                  text and fill are different jobs and collapsing
                                  them is how the two drift apart later. */
  --accent-press:   #266251;   /* the primary button's hover fill: DARKER than the
                                  accent, because the label on it is off-white and
                                  the state you reach for must be better than the
                                  one you started in. Off-white on it: 6.36. */
  --accent-soft:    #e6eeec;   /* hover and selected-row tint. --ink on it: 15.65 */
  --accent-soft-bd: #d1e1dc;   /* the tint's own border; a pressed tile. --ink: 13.63 */

  /* --- everything below is semantic, not decorative. Leave it alone. ---

     THE NEUTRAL RAMP MOVED TOGETHER. Warming only the page background leaves a
     cool ground against warm lines, which reads dirtier than either palette
     whole — see traps.md. Every value here is the old cool token re-derived at
     the SAME relative luminance along Perennial's black-to-off-white axis, so
     every ratio that was measured before still holds to within 0.1. */
  --surface: #ffffff;          /* cards lift off the page: 1.12 on --bg */
  --surface-sunken: #faf9f5;
  --surface-chip: #edebe6;

  --line-soft: #f1f0ea;
  --line-medium: #eae9e3;
  --line-input: #c1c1bb;

  --ink-muted: #626460;        /* 5.34 on --bg (was 5.43) */
  --ink-faint: #8a918c;        /* --color-gray. 2.88 on --bg (was 2.97) — never text */
  --ink-ghost: #bab9b4;        /* 1.76 on --bg (was 1.77) */

  /* --- semantic. Hue-bearing, so they do NOT change per client: a red that
         does not read as "bad" is a bug, not a style choice. Three tokens per
         tone — the foreground carries the meaning, -bg and -bd pair with it.

     --good and --warn are one step darker than the template ships them. The
     template's #1a7f4b is 4.33 on its own --good-bg and #a86a00 is 3.88 on its
     own — a banner whose text fails against its own fill, which neither tsc nor
     the build can see. Hue untouched; only lightness moved. Measured on their
     own backgrounds / on white:
         good #187846   4.75 / 5.51
         warn #8f5a00   5.05 / 5.78
         bad  #b3261e   5.65 / 6.54   (unchanged, it already passed)
         mute #5b6472   5.05 / 5.98 */
  --good: #187846; --good-bg: #e8f0ec; --good-bd: #c3d8ce;
  --warn: #8f5a00; --warn-bg: #f7efdd; --warn-bd: #e3d3ab;
  --bad:  #b3261e; --bad-bg:  #f8ece8; --bad-bd:  #e2c8c1;
  --mute:  #5b6472; --mute-bg: #e9ecf0; --mute-bd: #d9dde2;

  /* Bid status is domain vocabulary, not a tone: "open" is neither good nor
     bad. It gets the accent family rather than one of the three above. */
  --open-fg: #00738f; --open-bg: #e0f5fd; --open-bd: #b9e6f6;   /* 4.85 on its bg */

  /* Floor for an aging band, matching a KPI tile so the two strips line up. A
     floor rather than a fixed height: grid items never shrink below their
     content, so a hard height is silently ignored by whichever band has the
     most in it. */
  --band-h: 86px;

  /* --- shape. A RADIUS IS A LADDER, one token per box size, largest outside —
         FE16. An inner corner must read tighter than the box holding it and a
         single shared token cannot do that. Set here, before the chrome, because
         a radius that arrives late is the dangerous case: nothing already in the
         file has ever been drawn against a curve. --- */
  --radius: 6px;        /* buttons, inputs, banners, pills, tables */
  --radius-card: 10px;  /* cards */
  --radius-shell: 12px; /* the main window, where it nests into the rail */
  --radius-pill: 999px;

  /* --- depth. SOFT-UI: a surface is lit from the upper left, so it casts a pale
         highlight that way and a soft ink shadow the other, and a control that is
         pressed carries the same pair INSET. The highlight is white on a #f2f4f6
         page, which is the whole reason the page is not white. --shadow-sm and
         --shadow are the raised pairs at two sizes; --shadow-lg is untouched and
         stays for things that FLOAT — a modal, a menu, a tooltip — which are lit
         from above like anything held over a page. --- */
  --shadow-sm: -2px -2px 5px rgba(255, 255, 255, 0.9), 2px 2px 5px rgba(20, 24, 31, 0.08);
  --shadow: -4px -4px 10px rgba(255, 255, 255, 0.9), 4px 4px 10px rgba(20, 24, 31, 0.08);
  --shadow-inset: inset 2px 2px 5px rgba(20, 24, 31, 0.07), inset -2px -2px 5px rgba(255, 255, 255, 0.9);
  --shadow-lg: 0 10px 28px rgba(20, 24, 31, 0.1), 0 2px 6px rgba(20, 24, 31, 0.05);
  --ring: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);   /* the focus halo follows the accent — a literal here was the first console's cyan on every later one */

  /* --- motion. TWO CURVES AND SIX DURATIONS, and nothing animates outside them.
         The curves are the two from Emil Kowalski's design-engineering standard,
         copied rather than approximated: the built-in `ease-out` is too weak to
         read as a decision. Which one, by what the thing is doing —
           entering, exiting, or answering a press  → --ease-out
           moving or turning while on screen        → --ease-in-out
           a hover or a colour change               → plain `ease`, as ever
         Nothing here uses ease-in: it delays the first frame, the one the eye
         is on. Durations are the standard's budgets, one token per kind of
         thing, all under 300ms — a press is felt, not watched. --- */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-fast: 120ms;   /* hover, colour, a caret turning — seen tens of times a day */
  --dur-tip: 125ms;    /* tooltips */
  --dur-press: 160ms;  /* the press and its release */
  --dur-pop: 200ms;    /* dropdowns */
  --dur-shell: 240ms;  /* the rail */
  --dur-modal: 250ms;  /* modals and the toast */

  --pad: 16px;
  --gutter: 24px;
  --gutter-card: 14px;
  --gap-card: 12px;
  --content-max: 1560px;
  --masthead-h: 64px;
  /* How far the rail's ground shows above the main window. The topbar pays it
     back out of its own height, so the rail's brand rule and the topbar's bottom
     rule still land on exactly one line. */
  --shell-lift: 12px;
  /* There is no band below the window: it runs to the bottom edge of the
     viewport and squares off there, and the rail's ground above it is what gives
     the frame its one curve. `--shell-foot` was kept here at zero for a day on
     the reasoning that rules still read it — nothing does, in either tree, so it
     is gone rather than dead (tag-aa, 2026-09-04). */
  --rail-w: 256px;       /* the grid column: the rail's width on the canvas, or 0 — set inline by App.tsx */
  --rail-open-w: 256px;  /* the rail's own width, kept while it is closed */
  /* Wide enough for the mark and the account chip and nothing else. */
  --cardhead-h: 38px;
  --row-h: 32px;

  --fs-figure: 26px;
  --fs-title: 15px;
  --fs-body: 13.5px;
  --fs-control: 13px;
  --fs-field: 14px;      /* the value in a field — above the body, above its 12px label */
  --fs-note: 12.5px;
  --fs-label: 12px;
  --fs-micro: 10.5px;
  /* The rail's own two sizes. Bigger than the form controls they used to borrow:
     nav is read at a glance and from further away than a field label. */
  --fs-rail: 14.5px;
  --fs-railsub: 13.5px;

/* ALL CAPS IS GONE FROM THE INTERFACE.
   Uppercase plus wide tracking was the treatment for every label in the file —
   fact keys, column heads, card eyebrows, pills, the rail's group. It is a lot of
   shouting for text whose job is to be read past, it costs legibility at 10.5px,
   and it forced a second tracking scale to stay readable at all. Sentence case
   throughout; the label still separates from its value by size, weight and
   colour, which is what was doing the work anyway.
   One tracking value survives, and it is small — enough to keep a short label
   from looking cramped, not enough to read as a style. */
  --track-eyebrow: 0.02em;
  /* The three wide-tracking scales went with the uppercase they existed to make
     readable. Kept as aliases of the one that survives rather than deleted, so a
     rule added from the reference console lands on something sane instead of on
     an undefined property that silently voids its whole declaration. */
  --track-colhead: var(--track-eyebrow);
  --track-pill: var(--track-eyebrow);
  --track-railgrp: var(--track-eyebrow);

  /* Denim WD / Brandon Text are the real brand faces but are commercially
     licensed. Drop them in here and nothing else changes. */
  --font-brand: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
    sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

  /* A console driven by tab order cannot ship without a visible focus state. */
  --focus-ring: 2px solid var(--accent);
  --focus-offset: 2px;

  color-scheme: light;
}


/* --------------------------------------------------------------------------
 * CONSOLE-LOCAL TOKENS
 *
 * The block above is the framework's, copied verbatim (playbook 10 step 2), so
 * nothing per-console belongs in it. These three are this console's own and are
 * defined here instead. Each is an alias onto a framework token rather than a
 * new value, so the palette still has one source.
 * ------------------------------------------------------------------------ */
:root {
  --fs-tile: var(--fs-figure);      /* this console's KPI tiles predate --fs-figure */
  --brand-navy: var(--accent-text); /* .btn-link; the navy went with the client's brand (FE10) */
  --auth-bg: var(--rail-bg);        /* the sign-in card sits on the rail's ground */
}
/* ---------------------------------------------------------------------------
 * THE RAIL — frontend FE13.
 *
 * A vertical nav rail is chrome inside a light application, not a dark theme:
 * nothing else on the page inverts with it. So it is its own token set switched
 * by `:root[data-rail='...']`, never a prefers-color-scheme block and never a
 * class on body that other rules read. lib/railTheme.ts owns the switch and
 * VALIDATES the stored name against the list before setting the attribute.
 *
 * A palette defines ALL THIRTEEN. It was eleven until the current item stopped
 * being a 3px edge and became a filled bar: a fill is a new GROUND, so it needs
 * its own label token and both of them need measuring per palette.
 *
 * Three palettes ship. Every number below is measured against Perennial's own
 * tokens and, unlike the per-client era, it stays true — the accent cannot move
 * underneath it.
 *
 *              fg     dim    label  mark   logo   logo-mark  selected row
 *   dark     14.18   9.12   5.31   5.55  14.18    14.18      #20322b, fg 12.08
 *   light    15.49   5.09   4.60   4.61  13.33     4.61      #ffffff, fg 18.45
 *   green     5.49   4.90   4.90   5.49   5.49     5.49      #265d4c, fg  7.63
 *
 * THE MARK FOLLOWS PERENNIAL'S OWN RULE, not a contrast calculation: the green
 * mark on a light ground, the off-white mark on a dark one. That is what
 * perennial-design-system ships two files for, and Logo.tsx reproduces it with
 * one masked asset so a single token still colours it.
 *
 * THE DARK RAIL'S ACCENT TEXT IS NOT THE ACCENT. Green on forest is 2.89 and
 * fails; --signal is 5.55 and is the token that exists for exactly this.
 * Pointing --rail-mark at --accent here is the mistake this table is for.
 *
 * THE HIGHLIGHT — --rail-hi is a FILL and --rail-hi-fg is its label, so the pair
 * is measured twice: the label on the fill, and the fill against the rail ground
 * it has to be picked out from.
 *
 *              fill      label on it   fill vs ground
 *   dark      #2d7560       4.90            2.89
 *   light     #2d7560       4.90            4.61
 *   green     #12261f      14.18            2.89
 *
 * The two 2.89s are knowingly shipped. They are FILLED BARS against their
 * ground, not text on it, and the separation people actually see is the hue,
 * which a luminance ratio does not measure. What has to be READ is the label,
 * and that is 4.90 and 14.18. The bar that carries "you are here" is the accent
 * on every palette, and a rail that changes which colour means that between
 * palettes is a worse answer than these two numbers.
 *
 * ON THE LIGHT RAIL THE CURRENT ITEM'S LABEL IS OFF-WHITE, NOT INK. Ink on the
 * accent is 3.36 and fails. This is the reverse of the console this design came
 * from, whose accent was a LIGHT ground and took ink: the direction is a
 * property of the accent's luminance, and ours is 0.141 — dark. FE14.
 *
 * The selected row is a step DOWN in luminance on the dark and green rails and
 * UP on the light one. On a dark ground there is nowhere below to go, so the
 * emphasised row is a shade lighter and reads as recessed because it stays dark;
 * the green rail is dark enough that the same holds. Measured, not assumed.
 *
 * A SECOND GROUND, because the module's views are nested in the rail: .navsub
 * sits on --rail-sel, not on --rail-bg, so none of the numbers above apply to it
 * and all of these were measured again against the fill the items actually sit on.
 *
 *              ground     current view   resting view   marker
 *   dark      #20322b        12.08           7.77         4.73
 *   light     #ffffff        18.45           6.07         5.49
 *   green     #265d4c         7.63           6.82         7.63
 *
 * Everything in that table clears 4.5:1 on its own ground.
 *
 * THE GREEN RAIL'S HIERARCHY IS COMPRESSED BY CONSTRUCTION. The accent's
 * luminance is 0.141, so the ceiling for any label on it is 5.49 and fg/dim/label
 * land at 5.49/4.90/4.90. The last two are the same value on purpose: rest
 * versus current is carried by weight and size, not by tone. There is no further
 * tone available; do not go looking for one. FE14 predicts this for any accent
 * ground and it is why the rail has its own tokens at all.
 * ------------------------------------------------------------------------- */

:root {
  --rail-bg:    #12261f;            /* --color-forest */
  --rail-rule:  #1e312a;
  --rail-fg:    #f4f2ec;            /* 14.18 */
  --rail-dim:   #c3c5bf;            /*  9.12 */
  --rail-label: #909791;            /*  5.31 */
  --rail-chip:  #293a34;
  --rail-sel:   #20322b;            /* drops: off-white is 12.08 on it */
  --rail-edge:  #12261f;            /* equals the ground, so no hairline shows */
  --rail-logo:  #f4f2ec;            /* the wordmark reverses on a dark ground */
  --rail-logo-mark: #f4f2ec;        /* the OFF-WHITE mark here — the design
                                       system ships a second file for this, and
                                       the green one is 2.89 on forest. */
  /* --rail-mark is the ACCENT LINE AND ACCENT TEXT — the role link under the
     user's name, and the caret. It is NOT the current-item fill; that is
     --rail-hi, and the two are separate because on this ground one of them has
     to be readable as text and the other does not. */
  --rail-mark:  var(--signal);      /* 5.55. The accent itself is 2.89 and FAILS. */
  --rail-hi:    var(--accent);      /* the current item's fill */
  --rail-hi-fg: #f4f2ec;            /* 4.90 on it. Ink would be 3.36. */
}

/* The pale rail. Every token restated, including the ones a dark rail lets you
   point straight at a brand token. */
:root[data-rail='light'] {
  --rail-bg:    #edebe6;
  --rail-rule:  #dedcd4;            /* --color-stone */
  --rail-fg:    var(--ink);         /* 15.49 */
  --rail-dim:   #616360;            /*  5.09 */
  --rail-label: #676a66;            /*  4.60 — 0.49 below --rail-dim. The group
                                       eyebrow is separated from the nav items by
                                       size and tracking, not tone; the obvious
                                       lighter grey fails. */
  --rail-chip:  #e4e2dc;
  --rail-sel:   #ffffff;            /* on a pale ground the selected row LIFTS */
  --rail-edge:  #dedcd4;            /* here it draws the hairline to the panel */
  --rail-logo:  #12261f;            /* --color-forest, 13.33 — the wordmark as printed */
  --rail-logo-mark: var(--accent);  /* the GREEN mark here, 4.61 — the design
                                       system's rule for a light ground */
  --rail-mark:  var(--accent);      /* 4.61 as TEXT — the role link. The accent
                                       needs no darkened variant on this ground. */
  --rail-hi:    var(--accent);      /* the fill, 4.61 against this ground */
  --rail-hi-fg: #f4f2ec;            /* 4.90. NOT ink: ink on the accent is 3.36
                                       and fails. The console this came from took
                                       ink here because ITS accent was light. */
}

/* PERENNIAL GREEN AS THE RAIL. The third palette puts the accent itself on the
   rail. It exists as much to keep the token set honest as to be chosen: two
   palettes hide the tokens you forgot, and a third forces --rail-mark and
   --rail-logo-mark out into the open.

   Nothing here is computed at runtime. The previous version of this file
   resolved the accent's luminance in the browser at boot and stamped
   `data-accent-light` on the html element, because the accent was a client's and
   unknown until deploy. It is ours now, it is 0.141, and it is dark — so the
   answer is written down instead. lib/railTheme.ts lost that function. */
:root[data-rail='green'] {
  --rail-bg:    var(--accent);
  --rail-rule:  #266251;
  --rail-fg:    #ffffff;            /* 5.49 — the ceiling on this ground */
  --rail-dim:   #f4f2ec;            /* 4.90 */
  --rail-label: #f4f2ec;            /* 4.90 — the same value as dim, on purpose.
                                       See the note above about compression. */
  --rail-chip:  #266251;
  --rail-sel:   #265d4c;            /* drops: white is 7.63 on it */
  --rail-edge:  #266251;
  --rail-logo:  #ffffff;
  --rail-logo-mark: #ffffff;        /* dark ground, so the off-white mark again */
  --rail-mark:  #ffffff;            /* 5.49 */
  --rail-hi:    #12261f;            /* --color-forest. 2.89 against the ground as
                                       a FILL; its label is 14.18. */
  --rail-hi-fg: #f4f2ec;            /* 14.18 */
}

/* Adding a fourth palette: copy a block, rename it, define all eleven, add the name to
   RAIL_THEMES in lib/railTheme.ts and to OPTIONS on the account page, and
   re-measure — none of the numbers above transfer to a new ground. */




* { box-sizing: border-box; }
html, body { margin: 0; }
body {
  font-family: var(--font-brand);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
button, input, select { font-family: inherit; font-size: inherit; color: var(--ink); }
button { cursor: pointer; }
a { color: var(--accent-text); }
:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius); }

/* ===================================================================== auth */
.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--auth-bg);
}
.auth-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: inset 0 3px 0 var(--accent), var(--shadow-lg);
  width: 100%;
  max-width: 400px;
}
.auth-head { padding: 30px 28px 4px; display: flex; justify-content: center; }
.auth-body { padding: 26px 28px 28px; }
.auth-body h2 { margin: 0 0 20px; font-size: 17px; font-weight: 650; color: var(--ink); text-align: center; }
.auth-body label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 5px; color: var(--ink-muted); }
.auth-body .control { width: 100%; margin-bottom: 14px; }
.auth-body .btn { width: 100%; height: 40px; }

/* == APP SHELL ==========================================================
 * Ported from console-template/web/src/index.css. Same rules, no React.
 *
 * The rail's ground is painted by the GRID, not by .rail, so it also fills the
 * band above the main window and the rail reads as chrome wrapping the content
 * rather than a column stopping at its edge. .shell must therefore paint its
 * own background; without one the rail's ground shows straight through it.
 *
 * THE RAIL IS OFF THE CANVAS WHEN CLOSED, not narrowed to a strip of icons. The
 * spec has no icon-rail mode on purpose — six glyphs and a tooltip each is a
 * vocabulary nobody learns, and a strip cannot show a section label. The rail is
 * either open at its width or gone, and the trigger in the topbar (or `[`)
 * brings it back.
 *
 * THE COLUMN SNAPS; THE WINDOW SLIDES. The grid takes its new width in one pass
 * and the rail slides out on a transform, which runs on the compositor and costs
 * no layout. Transitioning the column itself would reflow the whole content
 * area on every frame.
 * ====================================================================== */
.app {
  position: relative;
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  grid-template-rows: minmax(0, 1fr);
  height: 100vh;
  overflow: hidden;
  background: var(--rail-bg);
}
.app.rail-closed { --rail-w: 0px; }

.rail {
  position: absolute; top: 0; bottom: 0; left: 0;
  width: var(--rail-open-w);
  z-index: 3;
  background: var(--rail-bg); color: var(--rail-fg);
  display: flex; flex-direction: column;
  overflow: hidden;
  /* NEVER sideways. overflow-y: auto computes overflow-x to auto as well, so one
     un-truncated label puts a horizontal scrollbar along the bottom of the rail. */
  overflow-x: hidden;
  transition: transform var(--dur-shell) var(--ease-out);
}
.app.rail-closed .rail { transform: translateX(-100%); }

.rail-brand {
  height: var(--masthead-h); flex: 0 0 auto;
  display: flex; align-items: center; gap: 9px;
  padding: 0 var(--pad);
  border-bottom: 1px solid var(--rail-rule);
}
.rail-brand .mark {
  width: 26px; height: 26px; flex: 0 0 auto; border-radius: 7px;
  background: var(--rail-logo-mark); color: var(--rail-bg);
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: -.02em;
}
.rail-brand .nm {
  min-width: 0; flex: 1; color: var(--rail-logo);
  font-size: var(--fs-rail); font-weight: 600; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The chevron that shrinks the rail, beside the lockup rather than on it: a logo
   that is sometimes a control and sometimes decoration is a target people have
   to learn twice. */
.rail-collapse {
  margin-left: auto; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border: 0;
  border-radius: var(--radius); background: transparent;
  color: var(--rail-dim);
  transition: transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .rail-collapse:hover { background: var(--rail-sel); color: var(--rail-fg); }
}
.rail-collapse:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--rail-mark); }

.rail-nav {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  padding: 12px 10px;
}
.railgrp {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: var(--track-railgrp); color: var(--rail-label);
  padding: 0 10px; margin: 2px 0 8px;
}
.rail-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  height: 36px; padding: 0 10px; text-align: left;
  border: 0; background: none; border-radius: var(--radius);
  font-size: var(--fs-rail); font-weight: 500; color: var(--rail-dim);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.rail-item + .rail-item { margin-top: 2px; }
.rail-item svg { width: 17px; height: 17px; flex: 0 0 auto; }
.rail-item .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {
  .rail-item:hover { background: var(--rail-sel); color: var(--rail-fg); }
}
.rail-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--rail-mark); }
/* The current item is a FILLED BAR, so it is a new ground and its label is
   --rail-hi-fg, measured on the fill rather than on the rail. */
.rail-item[aria-current='page'] {
  background: var(--rail-hi); color: var(--rail-hi-fg); font-weight: 600;
}

/* The account block. Sign out lives in its menu — two clicks, not one beside
   the navigation (FE15, as amended 2026-09-01). */
.rail-foot { position: relative; flex: 0 0 auto; border-top: 1px solid var(--rail-rule); padding: 10px; }
.rail-chip {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 8px; border: 0; background: none; border-radius: var(--radius);
  color: var(--rail-fg); text-align: left;
  transition: background var(--dur-fast) ease;
}
@media (hover: hover) and (pointer: fine) { .rail-chip:hover { background: var(--rail-sel); } }
.rail-chip:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--rail-mark); }
.rail-chip .avatar {
  width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%;
  background: var(--rail-chip); color: var(--rail-fg);
  display: flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 650;
}
.rail-chip .who { min-width: 0; flex: 1; }
.rail-chip .who .nm {
  font-size: var(--fs-railsub); color: var(--rail-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail-chip .who .rl { font-size: var(--fs-label); color: var(--rail-mark); }
.rail-chip .cvt { flex: 0 0 auto; color: var(--rail-dim); transition: transform var(--dur-fast) ease; }
.rail-chip[aria-expanded='true'] .cvt { transform: rotate(180deg); }

.rail-menu {
  position: absolute; left: 10px; right: 10px; bottom: calc(100% - 4px);
  z-index: 8; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-lg);
}
.rail-menu[hidden] { display: none; }
.rail-menu button {
  display: block; width: 100%; padding: 8px 10px; text-align: left;
  border: 0; background: none; border-radius: var(--radius);
  font-size: var(--fs-control); color: var(--ink);
}
@media (hover: hover) and (pointer: fine) { .rail-menu button:hover { background: var(--surface-sunken); } }
.rail-menu button:focus-visible { outline: none; box-shadow: var(--ring); }
.rail-menu form { margin: 0; }

/* -------------------------------------------------------------- the window */
.shell {
  /* The second column, SAID SO: the rail is positioned rather than in flow, so
     it takes no track and the window would otherwise auto-place into the
     rail's column and sit under it. */
  grid-column: 2;
  display: flex; flex-direction: column;
  min-width: 0; overflow: hidden;
  margin-top: var(--shell-lift);
  background: var(--bg);
  border-top: 1px solid var(--rail-edge);
  border-left: 1px solid var(--rail-edge);
  border-top-left-radius: var(--radius-shell);
  /* Square at the foot: the window reaches the bottom edge of the viewport, so
     there is no ground under it to curve into. */
}

.rail-trigger {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; margin-left: -4px; padding: 0;
  border: 0; border-radius: var(--radius); background: transparent;
  color: var(--ink-muted);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease,
              transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .rail-trigger:hover { background: var(--surface-sunken); color: var(--ink); }
}
.rail-trigger:focus-visible { outline: none; box-shadow: var(--ring); }

.topbar {
  /* Short by exactly the lift. The rail's brand block is --masthead-h tall and
     its bottom rule is the strongest horizontal line on the page; at full
     height the two rules would miss each other by the lift. */
  height: calc(var(--masthead-h) - var(--shell-lift));
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: 0 var(--gutter);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.topbar h1 {
  font-size: var(--fs-title); font-weight: 600; margin: 0;
  white-space: nowrap; letter-spacing: 0.01em;
}
.topbar-sep { color: var(--ink-faint); font-size: var(--fs-title); }
.topbar-view {
  font-size: var(--fs-title); font-weight: 600; color: var(--ink-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar .spacer { flex: 1 1 auto; }
.topbar .user { font-size: var(--fs-note); color: var(--ink-faint); }
.topbar-search { width: 250px; }
.topbar-search .control { width: 100%; height: 30px; }
@media (max-width: 1100px) { .topbar-search { display: none; } }

/* The window scrolls, not the page: .app is 100vh and clips. */
main.content {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--gutter); width: 100%; max-width: var(--content-max);
}

/* Below the narrowest width the rail is a drawer over the page, and the shell's
   lift and radius go with it. */
@media (max-width: 860px) {
  .app { grid-template-columns: 0 1fr; }
  .rail { box-shadow: var(--shadow-lg); }
  .shell { margin-top: 0; border-top: none; border-left: none; border-top-left-radius: 0; }
  .topbar { height: var(--masthead-h); padding: 0 14px; gap: 10px; }
  main.content { padding: 16px 14px; }
}
.error {
  background: var(--bad-bg); color: var(--bad); padding: 9px 12px;
  border-radius: var(--radius); font-size: 12.5px; margin-bottom: 14px;
}



/* ================================================================== controls */
.control {
  height: 36px; padding: 0 10px; font-size: 14px;
  border: 1px solid var(--line-input); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); outline: none;
  box-shadow: var(--shadow-inset);
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.control:focus { border-color: var(--accent); box-shadow: var(--ring); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 36px; padding: 0 14px; border-radius: var(--radius);
  border: 1px solid var(--line-input); background: var(--surface); color: var(--ink);
  font-weight: 550; white-space: nowrap; text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: background var(--dur-fast) ease, border-color var(--dur-fast) ease,
    color var(--dur-fast) ease, transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: var(--surface-sunken); border-color: var(--brand-navy); }
}
.btn:active { background: var(--line); box-shadow: var(--shadow-inset); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: 2px 2px 5px rgba(20,24,31,.12); }
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover { background: var(--accent-press); border-color: var(--accent-press); }
}
.btn-primary:active { background: var(--accent-press); box-shadow: inset 2px 2px 5px rgba(20,24,31,.18); }
.btn:disabled, .btn-primary:disabled { background: var(--surface-sunken); border-color: var(--line); color: var(--ink-muted); cursor: not-allowed; }
.btn-sm { height: 30px; padding: 0 10px; font-size: 12.5px; }
.btn-link { border: none; background: none; color: var(--brand-navy); padding: 2px 4px; font-weight: 550; border-radius: 4px; }
@media (hover: hover) and (pointer: fine) {
  .btn-link:hover { color: var(--accent-text); background: var(--accent-soft); }
}

/* ====================================================================== pills */
.pill { display: inline-block; padding: 2px 9px; border-radius: var(--radius-pill); font-size: 11.5px; font-weight: 650; line-height: 17px; letter-spacing: .01em; white-space: nowrap; }
.pill-good { background: var(--good-bg); color: var(--good); }
.pill-warn { background: var(--warn-bg); color: var(--warn); }
.pill-bad  { background: var(--bad-bg);  color: var(--bad); }
.pill-mute { background: var(--mute-bg); color: var(--mute); }
.pill-open { background: var(--open-bg); color: var(--open-fg); }

/* ====================================================================== banner */
.banner { display: flex; align-items: flex-start; gap: 10px; background: var(--open-bg); border: 1px solid var(--open-bd); color: var(--open-fg); padding: 9px 12px; border-radius: var(--radius); font-size: 13px; line-height: 1.5; }
.banner-error { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.banner-warn  { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.banner-good  { background: var(--good-bg); border-color: var(--good-bd); color: var(--good); }

/* ====================================================================== cards */
.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm); padding: 0 var(--gutter-card) 14px; margin-bottom: 20px;
}
.card-h { font-size: var(--fs-body); font-weight: 700; letter-spacing: var(--track-eyebrow); color: var(--accent-text); margin: 0; }
.card > .card-h, .card > .card-head {
  height: var(--cardhead-h); display: flex; align-items: center; gap: 12px;
  border-bottom: 1px solid var(--line-soft);
  margin: 0 calc(-1 * var(--gutter-card)); padding: 0 var(--gutter-card);
}
.card-head { justify-content: space-between; }
.card-head .actions { display: flex; gap: 6px; }
.card > * { margin-top: 12px; }
.card > .card-h:first-child, .card > .card-head:first-child { margin-top: 0; }
.card > *:last-child { margin-bottom: 0; }

/* ======================================================================= kpis */
/* Two columns so three tiles wrap to two rows rather than a lone strip. */
.kpis { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); box-shadow: var(--shadow); overflow: hidden; margin-bottom: 20px; }
@media (max-width: 480px) { .kpis { grid-template-columns: 1fr; } }
.kpi { padding: 12px 16px 14px; border-right: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); margin-right: -1px; margin-bottom: -1px; }
.kpi .label { font-size: var(--fs-micro); font-weight: 700; letter-spacing: var(--track-eyebrow); color: var(--ink-faint); margin-bottom: 4px; text-transform: uppercase; }
.kpi .value { font-size: 26px; font-weight: 680; letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1.15; }
.kpi .sub { font-size: 11.5px; color: var(--ink-faint); margin-top: 2px; }
.kpi.accent .value { color: var(--accent-text); }

/* stat tiles (per-document headline facts) */
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.tile { background: var(--surface-sunken); border: 1px solid var(--line-soft); border-radius: var(--radius); padding: 10px 12px; }
.tile .label { font-size: var(--fs-micro); font-weight: 700; letter-spacing: var(--track-eyebrow); color: var(--ink-faint); text-transform: uppercase; margin-bottom: 4px; }
.tile .value { font-size: var(--fs-tile); font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; }
.tile .meta { font-size: 11px; color: var(--ink-faint); margin-top: 4px; }
.alt-toggle {
  display: block; margin-top: 3px; padding: 0; border: 0; background: none;
  font-size: 11px; font-weight: 600; color: var(--warn); cursor: pointer;
}
.alt-toggle:hover { text-decoration: underline; }
.alt-toggle .caret { display: inline-block; transition: transform var(--dur-fast) ease; }
.alt-toggle.open .caret { transform: rotate(180deg); }
.alt-list { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line-soft); }
.alt-row { font-size: 12px; color: var(--ink); padding: 2px 0; font-variant-numeric: tabular-nums; }
.alt-row .muted { font-size: 11px; margin-left: 5px; }

/* =============================================================== digest */
.digest-file { border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface); box-shadow: var(--shadow-sm); padding: 14px 16px; }
.digest-file-h { font-size: var(--fs-body); font-weight: 700; color: var(--accent-text); letter-spacing: var(--track-eyebrow); margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line-soft); }
.digest-md { font-size: 13px; line-height: 1.55; max-height: 480px; overflow: auto; }
.digest-md h1 { font-size: 17px; margin: 0 0 10px; }
.digest-md h2 { font-size: 14.5px; margin: 18px 0 8px; color: var(--accent-text); }
.digest-md h2:first-child { margin-top: 0; }
.digest-md h3, .digest-md h4 { font-size: 13.5px; margin: 14px 0 6px; }
.digest-md p { margin: 0 0 10px; }
.digest-md blockquote { margin: 0 0 10px; padding: 8px 12px; background: var(--surface-sunken); border-left: 3px solid var(--accent-soft-bd); color: var(--ink-muted); font-size: 12.5px; }
.digest-md hr { border: none; border-top: 1px solid var(--line); margin: 14px 0; }
.digest-md table.grid td, .digest-md table.grid th { white-space: normal; }
.digest-md .tablewrap { margin-bottom: 12px; max-height: none; }

/* ============================================================ run progress */
.runprog-track {
  height: 10px; background: var(--surface-sunken); border: 1px solid var(--line-soft);
  border-radius: var(--radius-pill); overflow: hidden;
}
.runprog-fill {
  height: 100%; background: var(--accent); border-radius: var(--radius-pill);
  transition: width var(--dur-pop) var(--ease-out);
}
/* No counter from the backend yet: a bar that sweeps rather than one that
   claims a position it does not know. */
.runprog-fill.indet {
  width: 34%;
  background: linear-gradient(90deg, var(--line) 0%, var(--accent) 50%, var(--line) 100%);
  animation: sweep 1.4s var(--ease-out) infinite;
}
@keyframes sweep { from { transform: translateX(-110%); } to { transform: translateX(300%); } }
.runprog-line { font-size: var(--fs-body); color: var(--ink); font-variant-numeric: tabular-nums; }
.runprog-line .runprog-last { color: var(--ink-muted); }
.runprog-meta { font-size: var(--fs-note); color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* =============================================================== telemetry */
/* "not priced" is a sentence, not a figure — it does not want the tile's
   21px numeral treatment. */
.tile .value.na { font-size: 14px; font-weight: 600; color: var(--ink-faint); }
.telem-slow { margin-top: 12px; }

/* ============================================================ severity chart */
.sevbar-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line-soft); }
.sevbar-row:last-child { border-bottom: none; }
.sevbar-name { flex: 0 0 260px; font-size: 12.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
.sevbar-name:hover { color: var(--accent-text); text-decoration: underline; }
.sevbar-track { flex: 1; height: 14px; background: var(--surface-sunken); border-radius: 4px; overflow: hidden; }
.sevbar-fill { height: 100%; border-radius: 4px; transition: width var(--dur-pop) var(--ease-out); }
.sevbar-fill.normal { background: var(--good); }
.sevbar-fill.elevated { background: var(--warn); }
.sevbar-fill.outlier { background: var(--bad); }
.sevbar-count { flex: 0 0 34px; text-align: right; font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
.sevbar-secs { flex: 0 0 50px; text-align: right; font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--ink-faint); }
.sevbar-badge { flex: 0 0 auto; }
.sevlegend { display: flex; gap: 16px; font-size: 11.5px; color: var(--ink-faint); margin-bottom: 6px; }
.sevlegend .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.sevlegend .dot.normal { background: var(--good); }
.sevlegend .dot.elevated { background: var(--warn); }
.sevlegend .dot.outlier { background: var(--bad); }

/* ===================================================================== table */
.tablewrap { overflow: auto; border-radius: var(--radius); }
table.grid { border-collapse: separate; border-spacing: 0; width: 100%; font-variant-numeric: tabular-nums; }
table.grid th { position: sticky; top: 0; background: var(--surface-sunken); text-align: left; font-size: 11px; color: var(--ink-faint); font-weight: 500; padding: 10px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
table.grid th.num { text-align: right; }
table.grid td { padding: 9px 12px; border-bottom: 1px solid var(--line-soft); color: var(--ink); vertical-align: middle; }
table.grid td.num { text-align: right; }
table.grid tbody tr { transition: background var(--dur-fast) ease; }
@media (hover: hover) and (pointer: fine) { table.grid tbody tr:hover { background: var(--surface-sunken); } }
table.grid tbody tr:last-child td { border-bottom: none; }
.sub-line { display: block; font-size: 11.5px; color: var(--ink-faint); margin-top: 1px; }

/* ==================================================================== search */
.search-result { cursor: pointer; }
.search-result:hover { border-color: var(--brand-navy); }
.search-hit { font-size: 12.5px; padding: 5px 0; color: var(--ink-muted); border-bottom: 1px solid var(--line-soft); }
.search-hit:last-child { border-bottom: none; }
mark { background: var(--accent-soft); color: var(--ink); border-radius: 2px; padding: 0 1px; }

/* =================================================================== dropzone */
.dropzone {
  border: 2px dashed var(--line-input); border-radius: var(--radius-card);
  background: var(--surface-sunken); padding: 44px 24px; text-align: center;
  transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease;
  margin-bottom: 20px;
}
.dropzone.dragover { border-color: var(--accent); background: var(--accent-soft); }
.dropzone .dz-title { font-size: 15px; font-weight: 650; color: var(--ink); margin-bottom: 6px; }
.dropzone .dz-sub { font-size: 12.5px; color: var(--ink-faint); margin-bottom: 16px; }
.dropzone input[type=file] { display: none; }

/* A smaller dropzone for the Setup page's Script and Instruction set panels
   — same drag/hover idiom as .dropzone on Home, sized to sit inside a card
   rather than take the whole page. */
.dropzone-mini {
  border: 2px dashed var(--line-input); border-radius: var(--radius);
  background: var(--surface-sunken); padding: 14px 16px; text-align: center;
  transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.dropzone-mini.dragover { border-color: var(--accent); background: var(--accent-soft); }
.dropzone-mini .dz-title { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.dropzone-mini .dz-sub { font-size: 11.5px; color: var(--ink-faint); margin-top: 2px; }
.dropzone-mini input[type=file] { display: none; }

.progress-line { display: flex; align-items: center; gap: 10px; font-size: 12.5px; color: var(--ink-muted); }
.spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line-input); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
  display: inline-block; vertical-align: middle;
}
.spinner-sm { width: 10px; height: 10px; border-width: 1.5px; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ==================================================================== misc */
.muted { color: var(--ink-faint); }
.mono { font-family: var(--mono); font-size: 12.5px; }
/* Setup page: a scrollable, lazily-filled source viewer for a config file
   that can run to a thousand lines — bounded height rather than letting one
   expanded file push the rest of the page away. */
.src-view { font-family: var(--mono); font-size: 11.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; max-height: 480px; overflow: auto; background: var(--surface-sunken); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; margin-top: 10px; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.row { display: flex; align-items: center; gap: 10px; }
.crumbs { font-size: 12.5px; color: var(--ink-faint); margin-bottom: 14px; }
.crumbs a { color: var(--brand-navy); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.checks-list { margin: 0; padding-left: 0; list-style: none; }
.checks-list li { padding: 8px 0; border-bottom: 1px solid var(--line-soft); font-size: 13px; }
.checks-list li:last-child { border-bottom: none; }
.checks-list code { background: var(--surface-sunken); padding: 1px 4px; border-radius: 3px; font-size: 12px; }

/* The pre-upload / mid-validation / post-result states of the Script
   panel's five-check list — pending checks are muted until they run, the
   one running gets a spinner and tint, and once a result lands, a failing
   check is made visually dominant while the passing ones collapse and mute. */
.checks-list li.check-row { padding: 8px 10px; border-radius: var(--radius); }
.check-row .pill { min-width: 34px; text-align: center; }
.check-pending .pill { background: var(--mute-bg); color: var(--ink-faint); }
.checks-list li.check-active { background: var(--accent-soft); border-bottom-color: transparent; }
.checks-list li.check-fail { background: var(--bad-bg); border-bottom-color: transparent; }
.check-collapsed { opacity: .55; }
.dl-row { display: flex; gap: 8px; flex-wrap: wrap; }
.runlist-item { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); cursor: pointer; }
.runlist-item:last-child { border-bottom: none; }
.runlist-item:hover { background: var(--surface-sunken); }
.runlist-item .rmeta { flex: 1; }
.runlist-item .rtime { font-size: 12px; color: var(--ink-faint); }

/* =================================================================== vault */
.vault-head { margin-bottom: 18px; }
.vault-title { font-size: 19px; font-weight: 680; letter-spacing: -.01em; }
.vault-sub { font-size: var(--fs-note); color: var(--ink-faint); margin-top: 3px; }

/* Four headline numbers read as a row, not as a 2×2 block. */
.kpis-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .kpis-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .kpis-4 { grid-template-columns: 1fr; } }

.vault-charts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-bottom: 20px; }
.vault-charts-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 900px) { .vault-charts { grid-template-columns: 1fr; } }
.vault-charts .card { margin-bottom: 0; }

/* The market list reads as the map's index, so it sits beside it. */
.vault-split { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 14px; margin-bottom: 20px; }
@media (max-width: 900px) { .vault-split { grid-template-columns: 1fr; } }
.vault-split .card { margin-bottom: 0; display: flex; flex-direction: column; }
/* Sized against the map so the two cards end level; the list scrolls rather
   than the page growing by however many markets the vault happens to hold. */
.bars-scroll { max-height: 440px; overflow-y: auto; margin-right: -4px; padding-right: 4px; }

/* Bars carry one series, so they take one hue and the category name does the
   identifying — no legend, and the value is labelled directly. */
.bars { display: flex; flex-direction: column; gap: 7px; }
.bar-row { display: grid; grid-template-columns: 112px 1fr 30px; align-items: center; gap: 10px; }
.bar-label { font-size: var(--fs-note); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-track { height: 8px; background: var(--surface-sunken); border-radius: 4px; overflow: hidden; }
.bar-fill { height: 100%; background: var(--accent); border-radius: 4px; min-width: 4px; }
.bar-value { font-size: var(--fs-note); color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.bar-row:hover .bar-fill { background: var(--accent-press); }

/* A market row is a button when there is somewhere on the map to fly to. */
.bar-row-btn { width: 100%; font: inherit; text-align: left; background: none; border: none;
  padding: 3px 6px; margin: 0 -6px; border-radius: var(--radius); cursor: pointer; }
.bar-row-btn:hover:not(:disabled) { background: var(--surface-sunken); }
.bar-row-btn:disabled { cursor: default; opacity: .55; }
.bar-row-btn:disabled .bar-fill { background: var(--ink-faint); }
.bar-row-btn.selected { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-soft-bd); }
.bar-row-btn.selected .bar-label { color: var(--accent-text); font-weight: 600; }

/* Neighbourhoods under a market: indented, lighter, and visibly subordinate. */
.bar-children { display: flex; flex-direction: column; gap: 7px; margin: 7px 0 7px 12px;
  padding-left: 10px; border-left: 2px solid var(--line); }
/* Needed because the rule above out-specifies the browser's [hidden] rule:
   without it the neighbourhood list is permanently open and cannot collapse. */
.bar-children[hidden] { display: none; }
.bar-row-child .bar-label { color: var(--ink-faint); font-size: var(--fs-micro); }
.bar-row-child .bar-value { color: var(--ink-muted); font-size: var(--fs-micro); }
.bar-row-child .bar-track { height: 6px; }
.caret { display: inline-block; width: 11px; margin-left: -11px; color: var(--ink-faint);
  transition: transform var(--dur-fast) var(--ease-out); }
.bar-row-btn.expanded .caret { transform: rotate(90deg); }
/* The caret needs room, so a market that has one starts a little further in. */
.bars .bar-row-btn .bar-label { padding-left: 11px; }
.bars .bar-row-child .bar-label { padding-left: 0; }

.donut-wrap { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.donut { flex: none; }
.legend { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.legend-row { display: flex; align-items: center; gap: 8px; font-size: var(--fs-note); }
.swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.legend-label { color: var(--ink-muted); }
.legend-value { color: var(--ink); font-variant-numeric: tabular-nums; margin-left: auto; padding-left: 10px; }

.vault-map { height: 440px; border-radius: var(--radius); border: 1px solid var(--line); overflow: hidden; }
/* Says what a filled pin means versus a hollow one — the distinction carries
   real information (a building vs a neighbourhood) and is not guessable. */
.map-legend { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: var(--fs-micro); color: var(--ink-muted); }
.map-legend .lg { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.pin-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.pin-exact { background: var(--accent); box-shadow: 0 0 0 1.5px #fff, 0 0 0 2.5px color-mix(in srgb, var(--accent) 45%, white); }
.pin-approx-dot { background: #fff; border: 2px solid var(--accent); }
/* Leaflet ships its own type scale; pull the popup back to the app's. */
.pin-pop { font-family: var(--font-brand); font-size: var(--fs-note); line-height: 1.5; }
.pin-pop strong { display: block; margin-bottom: 3px; font-size: var(--fs-body); }
.pin-approx { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line); color: var(--ink-faint); font-size: var(--fs-micro); }
.leaflet-container { font-family: var(--font-brand); background: var(--surface-sunken); }
/* No tile filter: the basemap is already a grey canvas, so the pins are the
   only colour on the map without post-processing it. */

/* Credit metrics + per-record facts */
.metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1000px) { .metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 560px) { .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.metric { min-width: 0; }
.metric-k { font-size: var(--fs-micro); font-weight: 700; letter-spacing: var(--track-eyebrow); color: var(--ink-faint); text-transform: uppercase; }
.metric-v { font-size: 20px; font-weight: 650; letter-spacing: -.02em; font-variant-numeric: tabular-nums; margin-top: 3px; }
.metric-n { font-size: 11px; color: var(--ink-faint); margin-top: 2px; }

.facts { display: flex; flex-direction: column; }
.fact { padding: 8px 0; border-bottom: 1px solid var(--line-soft); }
.fact:last-child { border-bottom: none; }
.fact-k { font-size: var(--fs-micro); font-weight: 700; letter-spacing: var(--track-eyebrow); color: var(--ink-faint); text-transform: uppercase; }
.fact-v { font-size: var(--fs-note); margin-top: 2px; overflow-wrap: anywhere; }
.fact-n { font-size: 11px; color: var(--ink-faint); margin-top: 2px; }

table.grid-clickable tbody tr { cursor: pointer; }
table.grid-clickable tbody tr:hover { background: var(--surface-sunken); }
table.grid-clickable tbody tr:focus-visible { outline: none; box-shadow: inset var(--ring); }
.pin-open { margin-top: 6px; padding-left: 0; }

/* Credit metrics and the loan-type donut are both short, so they pair without
   leaving a tall empty card the way the donut did beside the asset-type bars. */
.vault-row-metrics { display: grid; grid-template-columns: 2fr 1fr; gap: 14px; margin-bottom: 20px; align-items: start; }
@media (max-width: 900px) { .vault-row-metrics { grid-template-columns: 1fr; } }
.vault-row-metrics .card { margin-bottom: 0; }
.metrics-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 16px; }
@media (max-width: 560px) { .metrics-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The donut card sizes to its chart rather than stretching to its neighbour. */
.vault-charts, .vault-row-metrics { align-items: start; }

/* Library — the files surface (bucket -> record -> files). Everything here
   leans on the existing card/grid/pill vocabulary; these are the few shapes
   that genuinely had no equivalent. */
.crumbs { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; font-size: 13px; flex-wrap: wrap; }
.crumb { background: none; border: 0; padding: 2px 4px; margin: 0; color: var(--accent-text); cursor: pointer; border-radius: var(--radius); font: inherit; }
.crumb:hover { background: var(--accent-soft); }
.crumb-sep { color: var(--mute); }
.crumb-here { color: var(--mute); padding: 2px 4px; }

.bucket-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; margin-top: 12px; }
.bucket-tile { text-align: left; background: var(--surface-sunken); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; cursor: pointer; font: inherit; color: inherit; transition: border-color var(--dur-fast) var(--ease-out); }
.bucket-tile:hover { border-color: var(--accent); }
.bucket-name { font-weight: 600; margin-bottom: 4px; }
.bucket-count { font-size: 13px; }
.bucket-split { font-size: 12px; margin-top: 6px; }

/* A record name comes out of the digest and can be a whole sentence, so the
   name column takes the slack and the metadata columns stay put — otherwise
   one long title pushes the pills off the right edge of the card. */
.lib-tbl { table-layout: fixed; width: 100%; }
.lib-tbl th:first-child, .lib-tbl td:first-child { width: auto; }
/* Real widths, not 1%: under table-layout:fixed a percentage is honoured
   literally, so 1% collapsed these to nothing and the header text and the
   pills drew on top of each other. */
.lib-tbl th.col-where,  .lib-tbl td.col-where  { width: 132px; }
.lib-tbl th.col-digest, .lib-tbl td.col-digest { width: 116px; }
.col-narrow { white-space: nowrap; }
.lib-files td.col-narrow { width: 104px; }
.lib-cell { min-width: 0; }
.lib-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-folder { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-files { table-layout: fixed; width: 100%; }

.linklike { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: var(--accent-text); cursor: pointer; text-align: left; }
.linklike:hover { text-decoration: underline; }
.folder-ico { width: 18px; height: 18px; color: var(--mute); margin-bottom: 6px; display: block; }
.file-group { margin-top: 14px; }
.file-group-h { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--mute); margin-bottom: 4px; }
.rec-actions { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.sub-card { margin-top: 14px; }
details.fold > summary { cursor: pointer; list-style: none; }
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::before { content: '▸ '; color: var(--mute); }
details.fold[open] > summary::before { content: '▾ '; }
