/* ============================================================
   Backbone Atlas — premium UI
   Brand tokens match Backbone Solutions brand.html verbatim
   (Navy 900/800/700 · Gold · Fraunces/Poppins/Open Sans)
   ============================================================ */
@font-face { font-family: "Fraunces"; font-weight: 400 600; font-style: normal; font-display: swap;
  src: url("/static/fonts/fraunces.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 400; font-style: normal; font-display: swap;
  src: url("/static/fonts/poppins400.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 500; font-style: normal; font-display: swap;
  src: url("/static/fonts/poppins500.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 600; font-style: normal; font-display: swap;
  src: url("/static/fonts/poppins600.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 700; font-style: normal; font-display: swap;
  src: url("/static/fonts/poppins700.woff2") format("woff2"); }
@font-face { font-family: "Open Sans"; font-weight: 300 600; font-style: normal; font-display: swap;
  src: url("/static/fonts/opensans.woff2") format("woff2"); }

:root {
  /* brand — Navy 900 #0B1220 · Navy 800 #0F172A · Navy 700 #1B2A47 · Gold #C9A24A */
  --navy: #0F172A;
  --navy-2: #0B1220;
  --navy-soft: #1B2A47;
  /* THE LOGO'S OWN TWO COLOURS, AND NOTHING ELSE'S.
     The mark used to be tiled with --navy-soft/--navy-2, which reads as "the brand navy"
     but is not: the green theme deliberately re-points that triple so a dark component
     does not strand a navy slab in a green page. Every such component SHOULD follow the
     theme. The logo is not one of them — a logo that is navy on one screen and green on
     the next is not a theme, it is a second logo, and the mark is the one thing on the
     page that must be the same everywhere.
     Fixed literals rather than var(--navy-*): a token that any theme may re-point cannot
     express "never changes", which is the whole requirement here. No theme block below
     overrides these two, and test_logo_constant.py fails the build if one starts to. */
  --brand-mark-a: #1B2A47;
  --brand-mark-b: #0B1220;
  --gold: #C9A24A;
  --gold-lt: #DDBB63;
  --gold-dk: #A5822F;
  --font-display: "Fraunces", Georgia, serif;
  --font-head: "Poppins", -apple-system, "Segoe UI", system-ui, sans-serif;
  --font-body: "Open Sans", -apple-system, "Segoe UI", system-ui, sans-serif;
  /* surfaces */
  --bg: #f5f6f9;
  --bg-2: #eef1f5;
  --panel: #ffffff;
  --ink: #1a2434;
  --ink-soft: #43526b;
  --muted: #5f6d80;
  --line: #e6eaf0;
  --line-strong: #d6dce6;
  /* semantic */
  --brand: #0F172A;
  --brand-2: #2f6fb0;
  --accent: #eef3f8;
  --ok: #1a7a48; --ok-bg: #e6f5ec;
  --warn: #a8651a; --warn-bg: #fbf0e2;
  --bad: #b42318; --bad-bg: #fbeae8;
  --shadow: 0 1px 2px rgba(16,26,46,.04), 0 4px 16px rgba(16,26,46,.06);
  --shadow-lg: 0 8px 30px rgba(16,26,46,.12);
  /* Plain badges are gold. On a light surface it has to be darker than the palette's
     --gold-dk (#A5822F): measured against the tinted pill that gives 3.6:1, and a
     650-weight 11.5px label is not "large text", so AA wants 4.5:1. #7A5E1F measures
     5.3:1 and still reads as the same gold family. */
  --badge-fg: #7A5E1F;
  --badge-bg: rgba(201,162,75,.14);
  /* ---- SPACING / RADIUS SCALE ----------------------------------------------
     Added 2026-08-19. Padding and radii were hand-placed across this file (20px,
     16px 18px, 13px 20px; radii 7/8/9/10/12/14) with no rule, which is why dense
     pages never looked quite settled — card paddings and corners almost agreed
     without ever agreeing. A 4px rhythm and three radii. Existing literals are
     migrated to these as each component is touched, not in one sweep: a global
     find-and-replace over a file this size is how a layout regression ships. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 28px;
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  /* ONE number scale, two steps. The KPI strip and the smaller 360/lens cards used
     26px and 22px with nothing tying them together; they are the same idea at two
     densities, so they are now the same scale with two named steps rather than two
     unrelated figures. Kept at the existing sizes deliberately — enlarging the
     small step overflows a 150px card on a seven-figure number. */
  --num-lg: 26px;
  --num-md: 22px;
  /* ---- SIDEBAR SURFACE ------------------------------------------------------
     The sidebar was the one surface in the app painted in literals: a navy gradient with
     eleven hardcoded light-ink values (#cdd6e6, #fff, #93a4c0, #6f809c, #c3cddd, #a8b6cc
     and five rgba(255,255,255,...) rules). Because they were literals, the sidebar stayed
     navy on BOTH palettes — and on the dark palette it sat at #0F172A against a page of
     #0e1420, which is the same colour twice. The sidebar stopped reading as a surface and
     merged into the page.

     So the sidebar INVERTS with the theme rather than following it: navy behind light ink
     on the light palette, light behind navy ink on the dark one. The point of the sidebar
     is to be a distinct surface, and holding its contrast against the content is what
     keeps that true in both themes; keeping its literal colour is what broke it.

     The logo tile is deliberately NOT tokenised and stays navy on both: the mark is a gold
     gradient and gold-on-navy is the master lockup. A gold mark on a light tile is the
     off-brand, low-contrast version of it. */
  --side-a: var(--navy);            /* gradient top */
  --side-b: var(--navy-2);          /* gradient bottom */
  --side-ink: #cdd6e6;              /* sidebar body text */
  --side-ink-strong: #ffffff;       /* wordmark, hover, active link */
  --side-ink-soft: #93a4c0;         /* the organisation name */
  --side-ink-faint: #6f809c;        /* group headings */
  --side-link: #c3cddd;             /* nav links at rest */
  --side-link-hover: #a8b6cc;       /* group toggle hover */
  --side-rule: rgba(255,255,255,.08);
  --side-hover: rgba(255,255,255,.07);
  --side-scroll: rgba(255,255,255,.12);
  --side-active: rgba(201,162,75,.14);
  --side-gold: var(--gold-lt);      /* gold that must read on the sidebar's own ground */
}
/* Guarded with :not([data-theme="light"]) so an explicit light choice beats a dark OS.
   Before the guard existed this block applied even to someone who had chosen light, and
   :root[data-theme="light"] below had to undo it token by token — which it did only
   PARTLY: --ok/--warn/--bad and both shadows were never re-listed, so choosing light on a
   dark-OS machine produced a white page carrying dark-tuned status colours and shadows
   tuned to be seen over near-black, i.e. not seen at all. Unreachable until now only
   because nothing in the product ever set data-theme; shipping the toggle is what would
   have made it reachable. With the guard, "light" is simply the unmodified :root, so the
   light palette exists in exactly one place and cannot fall out of step with itself. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="green"]) {
    --bg: #0e1420; --bg-2: #0b111b; --panel: #16202f; --ink: #e7edf5; --ink-soft: #b3c0d3;
    --muted: #7f8ea3; --line: #223046; --line-strong: #2c3c56; --accent: #1a2739;
    --brand-2: #6aa8d8; --ok-bg: #10281b; --warn-bg: #2a1e0e; --bad-bg: #2a1210;
    /* --brand-2 was overridden here from the start and --brand was not, so on the dark
       palette --brand stayed #0F172A — a near-black navy meant for a white page. Against
       the panel it measures 1.09:1, which is not low contrast, it is invisible: the step
       headings in the guidance drawer were rendering and could not be seen at all. The
       light theme measures 17.85:1 on white, which is why nothing ever looked wrong to
       anyone reading it there. Matching --brand-2 gives 6.40:1 and keeps the heading
       reading as an accent rather than as body text. */
    --brand: #6aa8d8;
    /* --ok/--warn/--bad are tuned for a white surface; on the dark palette they measure
       3.0-3.5:1 against their own tinted backgrounds, under AA for text this size.
       Lightened here only — the light theme keeps the originals. */
    --ok: #58c98a; --warn: #e0a558; --bad: #f08b82;
    --shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 16px rgba(0,0,0,.35);
    --shadow-lg: 0 8px 30px rgba(0,0,0,.5);
    /* Light gold on dark: the deep gold above disappears against #16202f. */
    --badge-fg: #DDBB63;
    --badge-bg: rgba(221,187,99,.22);
    /* The sidebar inverts: light ground, navy ink. Values chosen against #f4f7fb — the
       group headings are 10.5px uppercase, which is small text, so #6b7a93 (4.0:1) was
       too pale and #5d6b83 (4.9:1) is used instead; and the sidebar's gold is --badge-fg's
       #7A5E1F (5.3:1) rather than --gold-dk (3.6:1), for the same reason. */
    --side-a: #f4f7fb; --side-b: #e7edf6;
    --side-ink: #29364c; --side-ink-strong: #0B1220;
    --side-ink-soft: #55647d; --side-ink-faint: #5d6b83;
    --side-link: #26344a; --side-link-hover: #0F172A;
    --side-rule: rgba(15,23,42,.10);
    --side-hover: rgba(15,23,42,.06);
    --side-scroll: rgba(15,23,42,.18);
    --side-active: rgba(165,130,47,.16);
    --side-gold: #7A5E1F;
  }
}
/* The manual toggle's palette. It must stay identical to the media-query block above:
   a variable added to one and not the other is invisible to whichever half of the users
   are on that route, and nothing reports it. --brand was missing from BOTH, which is
   how the guidance headings ended up unreadable on dark. */
:root[data-theme="dark"] {
  --bg: #0e1420; --bg-2: #0b111b; --panel: #16202f; --ink: #e7edf5; --ink-soft: #b3c0d3;
  --muted: #7f8ea3; --line: #223046; --line-strong: #2c3c56; --accent: #1a2739;
  --brand-2: #6aa8d8; --brand: #6aa8d8;
  --ok-bg: #10281b; --warn-bg: #2a1e0e; --bad-bg: #2a1210;
  --badge-fg: #DDBB63; --badge-bg: rgba(221,187,99,.22);
  --ok: #58c98a; --warn: #e0a558; --bad: #f08b82;
  /* Also one-sided until now: the toggle inherited the light shadows, which are a faint
     grey tuned for a white page and read as nothing at all over a dark one. */
  --shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 16px rgba(0,0,0,.35);
  --shadow-lg: 0 8px 30px rgba(0,0,0,.5);
  --side-a: #f4f7fb; --side-b: #e7edf6;
  --side-ink: #29364c; --side-ink-strong: #0B1220;
  --side-ink-soft: #55647d; --side-ink-faint: #5d6b83;
  --side-link: #26344a; --side-link-hover: #0F172A;
  --side-rule: rgba(15,23,42,.10);
  --side-hover: rgba(15,23,42,.06);
  --side-scroll: rgba(15,23,42,.18);
  --side-active: rgba(165,130,47,.16);
  --side-gold: #7A5E1F;
}

/* ---- GREEN — the low-strain palette ------------------------------------------------
   Asked for by people who sit in front of this application all day: a workspace that is
   not a white page. Two shades of green, the menu the darker of them, exactly as the navy
   theme is built -- so nothing about the layout, the gold or the type changes, only the
   ground they sit on.

   WHY THESE GREENS. Desaturated and slightly yellow-shifted (hue ~110-135, saturation
   kept under 20%) because a saturated green is more tiring than the white it replaced,
   not less. The workspace sits at ~92% lightness rather than 100%: enough to take the
   glare off a white page without the text losing contrast. Cards stay LIGHTER than the
   ground, the same relationship the navy theme uses, so a panel still reads as raised.

   GOLD IS UNCHANGED. It is the brand's signature and it is warm, which is exactly what a
   cool green ground wants -- gold on sage is a more comfortable pairing than gold on
   white. Links move off blue: a mid blue on a green ground is the one combination here
   that reads as slightly muddy, so they take a deep pine that carries the same "this is
   a link" weight while belonging to the palette.
   Everything is a token override. No component CSS is touched, and the navy and dark
   palettes are byte-for-byte what they were. */
:root[data-theme="green"] {
  /* THE BRAND TRIPLE, RE-POINTED. Components that need a dark ground of their own -- the
     process strip across the dashboard, panel headers, the deep bands -- reach for
     --navy/--navy-2/--navy-soft directly rather than for a surface token. Overriding the
     three here turns every one of them green in one move; leaving them would strand a
     navy slab in the middle of a green page, which is what the first preview showed.
     Gold is deliberately NOT overridden: it is the brand's signature and it reads better
     on green than it does on navy. */
  --navy: #2A4133; --navy-2: #1E3126; --navy-soft: #3A5645;

  /* surfaces — the lighter of the two greens.
     THE FIRST VERSION OF THIS WAS COSMETIC. Measured across the workspace it came out
     209.7 mean luminance against white's 211.9 — 1.1% dimmer. It was green and it was not
     doing the job it exists to do. These values are ~20% dimmer, which is
     what Imran asked for after seeing 6.5% and finding it still too bright.
     WHY DIM AT ALL, AND WHY NOT MORE. What is well supported for screen comfort is
     reducing GLARE — the mismatch between a bright screen and a dimmer room — and avoiding
     the pure-white/pure-black extreme. What is NOT well supported is that any particular
     hue is therapeutic; green earns its place because the eye's peak sensitivity is around
     555nm and short-wavelength light focuses slightly in front of the retina, so a
     low-saturation green ground asks marginally less of the focusing muscles than a blue-
     white one. That is a second-order effect. The luminance is the first-order one.
     POSITIVE POLARITY IS KEPT — dark text on a light ground, which is better evidenced for
     sustained reading in a normally lit room than dark mode is. This dims the page; it
     does not invert it.
     EVERY TEXT TOKEN MOVED WITH THE GROUND. Darkening a background silently costs
     contrast on the palest text, which is the trap in every "just make it dimmer"
     change: muted was 4.29:1 and links 3.45:1 against these surfaces before being
     re-darkened. Nothing here is below 4.5:1. */
  --bg: #A8BEA5; --bg-2: #9EB69B; --panel: #BCCEB9;
  --ink: #16241B; --ink-soft: #31453A; --muted: #39493D;
  --line: #9BB298; --line-strong: #8BA588; --accent: #B1C6AE;

  /* links and emphasis: deep pine rather than the navy theme's blue */
  /* Links darkened with the ground: #256B4B measured 3.45:1 on the new ground and
     failed AA. Every text token here moved when the surfaces did -- that is the
     whole discipline of dimming a palette, and skipping it is how a 'softer' theme
     quietly becomes a less readable one. */
  --brand: #12281A; --brand-2: #174733;

  /* STATUS COLOURS: the soft BACKGROUNDS were re-tinted for green; the INKS were not moved
     far enough with them, and the two are used in two different places.

     On a badge the ink sits on its own tint and that pairing was nearly right (--ok was the
     exception, at 4.46:1 -- itself under AA). But --ok, --warn and --bad are also painted
     straight onto the PAGE in a dozen rules that have no tint behind them: the aging-bucket
     figures on the payables board, the .cards KPI numbers, the tracker's stopped label, the
     date echo, and -- since v2026.08.26c -- the RED HOVER ON THE LOG OUT ICON, which is one
     of the three compensations that control was given when it lost its text label.

     MEASURED against this theme's own ground (#A8BEA5) before the change:
         --ok   2.70:1     --warn  2.90:1     --bad  3.52:1
     The first two are below even the 3:1 large-text floor. 'Overdue' and 'EXPIRED' are the
     words this product most needs a person to actually read.

     Darkened along the same hue and saturation -- hue moves by at most 0.6 degrees and
     saturation by 0.004, so these are the same three colours, moved WITH the ground as the
     rest of this palette was. Every pairing improves, the badges included:
         --ok    2.70 -> 4.84 on --bg,  4.46 -> 8.00 on its own tint
         --warn  2.90 -> 4.82 on --bg,  4.87 -> 8.08 on its own tint
         --bad   3.52 -> 4.80 on --bg,  5.61 -> 7.65 on its own tint
     Scoped to [data-theme=green]; the light and dark palettes are untouched. */
  --ok: #114F2F; --ok-bg: #DCEFE1;
  --warn: #623D10; --warn-bg: #F4EBD9;
  --bad: #842018; --bad-bg: #F5E2DF;

  /* gold, untouched, on a ground that suits it better than white did */
  --badge-fg: #6C531B; --badge-bg: rgba(201,162,75,.20);

  --shadow: 0 1px 2px rgba(26,44,32,.05), 0 4px 16px rgba(26,44,32,.07);
  --shadow-lg: 0 8px 30px rgba(26,44,32,.14);

  /* the menu — the darker of the two greens, deep enough to carry white type */
  --side-a: #33503C; --side-b: #27402F;
  --side-ink: #D2E2D5; --side-ink-strong: #FFFFFF;
  /* --side-ink-faint lifted from #86A08D, which measured 3.55:1 on the menu and
     failed AA for normal text. #A3BCA9 gives 4.94:1. The navy theme's equivalent
     sits at 4.46:1, so this is the one place the new palette is deliberately
     better than the one it copies rather than matching it. */
  --side-ink-soft: #A2BAA8; --side-ink-faint: #A3BCA9;
  --side-link: #C5D8C9; --side-link-hover: #E9F3EB;
  --side-rule: rgba(255,255,255,.09);
  --side-hover: rgba(255,255,255,.07);
  --side-scroll: rgba(255,255,255,.13);
  --side-active: rgba(201,162,75,.18);
  /* The gold that has to read on the rail's OWN ground, not on the page. The rail is dark
     here as it is in the navy theme, so it takes the light gold; the dark theme, whose
     rail is pale, uses #7A5E1F instead. Missing until the palette parity check was taught
     to look at this theme -- the active nav icon and the workspace eyebrow were falling
     back to nothing. */
  --side-gold: var(--gold-lt);
  /* THE SAME FIX AS --side-gold ABOVE, FOR THE PAGE RATHER THAN THE RAIL, and it was
     missed for exactly the reason that one nearly was: the rail is obviously dark, so its
     gold obviously needed rethinking, while the workspace ground merely got dimmer and the
     gold on it looked like it had already been considered.

     MEASURED. --gold-dk is #A5822F, a MID-tone; this theme's ground is #A8BEA5 and its
     panel #BCCEB9, also mid-tones. Two mid-tones give 1.81:1 and 2.17:1 -- not 'a little
     low' but effectively invisible, against 4.5:1 for body text. It is used as a TEXT
     colour in thirteen rules, and the ones that matter are ordinary daily surfaces: the
     eyebrow above every page heading, WHICH NAV ITEM YOU ARE ON, the sort-direction arrow
     on every sortable table, and the hover state of the three icon-only chrome controls --
     which, having given up their labels, have nothing left but that hover to answer with.

     #524017 is the SAME gold moved down with the ground: hue 42.2 and saturation 0.557 are
     unchanged from #A5822F and only lightness drops. 5.02:1 on --bg, 6.02:1 on --panel.
     The margin over 4.5 is deliberate -- an earlier green value passed by +0.02, close
     enough that any later tweak would have broken it with nothing reporting a failure.
     test_e2e_themes.py now measures every text token on every ground in all three themes.

     THE LIGHT AND DARK PALETTES ARE NOT TOUCHED. Scoped to [data-theme=green], the same
     way the whole green theme is: additive, never a redefinition of a value another theme
     uses. That is the standing rule, kept. */
  --gold-dk: #524017;
}
/* :root[data-theme="light"] used to restate the light palette here. It is deliberately
   GONE: the dark media query is now guarded against it, so [data-theme="light"] inherits
   the light palette from :root untouched. A third copy of the same values could only ever
   drift from the other two — and it already had. */

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font: 15px/1.55 var(--font-body);
  background: var(--bg); color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* ---------- app shell: sidebar + content ---------- */
.app { display: grid; grid-template-columns: 244px 1fr; min-height: 100vh; }
.sidebar {
  background: linear-gradient(178deg, var(--side-a) 0%, var(--side-b) 100%);
  color: var(--side-ink); padding: 18px 14px 28px; position: sticky; top: 0; height: 100vh;
  overflow-y: auto; display: flex; flex-direction: column; gap: 4px;
}
/* The sidebar is a column flex box pinned to 100vh, and it holds far more than that —
   1636px of navigation in a 720px box on a laptop. Flex children default to
   `flex-shrink: 1`, so rather than scrolling, the browser was COMPRESSING them to fit.
   Most children have padding that resists, but `.org` also has `overflow: hidden` for its
   ellipsis, so it was squeezed from a natural 29px to 12px and the workspace name was cut
   through the middle — the "squeezing" Imran photographed. Refusing to shrink is what
   makes `overflow-y: auto` above actually scroll, which is what was intended all along.
   Long-standing, but only reachable once the navigation grew past the viewport; grouping
   Administration open for admins is what pushed it over on a 720px-tall screen. */
.sidebar > * { flex-shrink: 0; }
.sidebar::-webkit-scrollbar { width: 6px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--side-scroll); border-radius: 3px; }
.sidebar .logo {
  display: flex; align-items: center; gap: 10px; padding: 6px 10px 16px; margin-bottom: 6px;
  border-bottom: 1px solid var(--side-rule);
}
.sidebar .logo .mark {
  width: 34px; height: 34px; border-radius: 8px; flex: none;
  /* Brand-constant: see --brand-mark-a/b. The avatar below keeps the theme-following
     --navy-soft/--navy-2 on purpose — it is chrome, not the mark. */
  background: linear-gradient(150deg, var(--brand-mark-a), var(--brand-mark-b));
  display: grid; place-items: center;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.06), 0 2px 8px rgba(11,18,32,.3);
}
.mark svg { width: 100%; height: 100%; }
.sidebar .logo .txt { font: 700 16px var(--font-head); color: var(--side-ink-strong); letter-spacing: .01em; line-height: 1.1; }
.sidebar .logo .txt small { display: block; font-weight: 500; font-size: 10.5px; letter-spacing: .2em;
  color: var(--side-gold); text-transform: uppercase; margin-top: 2px; font-family: var(--font-head); }
/* Whose workspace this is. Sits directly under the product lockup, quieter than the
   product name so the hierarchy reads house > product > customer. Truncates rather than
   wrapping: organisation names run long ("Riyadh Infrastructure Authority", "Wisła
   Components Sp. z o.o.") and a two-line name would push the whole nav down. */
.sidebar .org {
  font: 600 11.5px var(--font-head); color: var(--side-ink-soft); letter-spacing: .01em;
  padding: 0 12px 12px; margin-top: -2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-bottom: 1px solid var(--side-rule); margin-bottom: 8px;
}
.nav-group { font: 600 10.5px var(--font-head); letter-spacing: .14em; text-transform: uppercase; color: var(--side-ink-faint);
  padding: 14px 12px 5px; }
/* Collapsible nav groups. A flat list of thirty destinations makes every one of them
   equally hard to find; groups collapse, remember their state per browser, and the
   group holding the current page is forced open so you can never lose your place. */
button.nav-toggle { display: flex; align-items: center; justify-content: space-between; gap: 6px;
  width: 100%; background: none; border: 0; cursor: pointer; text-align: left;
  border-radius: 7px; transition: color .13s; }
button.nav-toggle:hover { color: var(--side-link-hover); }
button.nav-toggle:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
button.nav-toggle .chev { width: 13px; height: 13px; flex: none; opacity: .6;
  transition: transform .16s ease; }
button.nav-toggle[aria-expanded="false"] .chev { transform: rotate(-90deg); }
.nav-items { display: flex; flex-direction: column; gap: 2px; }
.nav-items[hidden] { display: none; }
/* Keyboard hint on the global search box */
.topbar-app .search kbd { position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font: 600 10.5px var(--font-head); color: var(--muted); background: var(--bg-2);
  border: 1px solid var(--line-strong); border-radius: 5px; padding: 2px 6px; pointer-events: none; }
.topbar-app .search input:focus ~ kbd { display: none; }
.sidebar a.nav-link {
  display: flex; align-items: center; gap: 11px; color: var(--side-link); text-decoration: none;
  padding: 8px 12px; border-radius: 9px; font: 500 14px var(--font-head); transition: all .13s;
}
.sidebar a.nav-link svg { width: 17px; height: 17px; flex: none; opacity: .8; }
.sidebar a.nav-link:hover { background: var(--side-hover); color: var(--side-ink-strong); }
.sidebar a.nav-link.active { background: var(--side-active); color: var(--side-ink-strong); }
.sidebar a.nav-link.active svg { opacity: 1; color: var(--side-gold); }
.sidebar a.nav-link.active { box-shadow: inset 3px 0 0 var(--gold); }
/* A count beside a nav entry. Only rendered when non-zero — see the nav macro. */
.sidebar a.nav-link .nav-count {
  margin-left: auto; min-width: 20px; text-align: center; padding: 1px 6px;
  border-radius: 999px; background: var(--gold); color: #1a1a1a;
  font: 700 11.5px var(--font-head); font-variant-numeric: tabular-nums;
}

/* ---------- topbar ---------- */
.topbar-app {
  display: flex; align-items: center; gap: 14px; height: 62px; padding: 0 26px;
  background: var(--panel); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 20;
  box-shadow: 0 1px 0 rgba(16,26,46,.02);
}
.topbar-app .search { flex: 1; max-width: 420px; position: relative; }
.topbar-app .search svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--muted); }
.topbar-app .search input {
  width: 100%; padding: 9px 12px 9px 36px; border: 1px solid var(--line-strong);
  border-radius: 999px; background: var(--bg); font-size: 13.5px;
}
.topbar-app .search input:focus { outline: none; border-color: var(--brand-2); background: var(--panel);
  box-shadow: 0 0 0 3px rgba(47,111,176,.12); }
/* Guidance quick-links, sitting between the search box and the user block. Quiet by
   default so they don't compete with the primary sidebar nav; the active page is marked
   in gold. Collapse below the search's own breakpoint, where the sidebar already carries
   these destinations and horizontal room runs out. */
.topbar-app .topnav-links { display: flex; align-items: center; gap: 2px; margin-left: 8px; }
.topbar-app .topnav-links a { font: 500 13px var(--font-head); color: var(--ink-soft);
  text-decoration: none; padding: 7px 12px; border-radius: 8px; white-space: nowrap;
  transition: background .13s, color .13s; }
.topbar-app .topnav-links a:hover { background: var(--bg-2); color: var(--ink); }
.topbar-app .topnav-links a.active { color: var(--gold-dk); background: color-mix(in srgb, var(--gold) 12%, transparent); }
.topbar-app .topnav-links a:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
/* Reference, as a dropdown on the bar. A <details>, so it needs no script: nothing here
   can be disabled by a CSP nonce, and it is keyboard-operable before any JS has loaded. */
.topnav-menu { position: relative; }
.topnav-menu > summary { list-style: none; cursor: pointer; white-space: nowrap;
  font: 500 13px var(--font-head); color: var(--ink-soft); padding: 7px 12px;
  border-radius: 8px; transition: background .13s, color .13s; user-select: none; }
.topnav-menu > summary::-webkit-details-marker { display: none; }
.topnav-menu > summary::after { content: ""; display: inline-block; width: 5px; height: 5px;
  margin-left: 7px; vertical-align: 2px; border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-1px,-1px); }
.topnav-menu[open] > summary::after { transform: rotate(-135deg) translate(-2px,-2px); }
.topnav-menu > summary:hover { background: var(--bg-2); color: var(--ink); }
.topnav-menu > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.topnav-menu-body { position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  min-width: 210px; padding: 6px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px; }
.topnav-menu-body a { display: block; }
/* THE THREE REFERENCE LINKS COME OFF THE BAR AT 1300px, not 900px.

   Measured 2026-08-27, on the way to adding the feedback icon: the application scrolled
   HORIZONTALLY at every viewport below about 1300px — 22px over at 1240, 82px at 1180,
   162px at 1100, 302px at 960 — because these three links are 337px wide and were held on
   the bar down to 900px. It is a pre-existing fault, not one the new icons introduced (the
   shape they replaced was 20px WIDER at every width), but a bar that already does not fit
   is not one to quietly add a fourth control to.

   Dropping these is the right 337px to reclaim because they are the only things on the bar
   that exist somewhere else: they are a shortcut INTO the sidebar's Reference section,
   which carries all three at every width. Nothing becomes unreachable — see the note beside
   them in base.html, which already said the sidebar carries them on narrow screens. That
   was true; 900px was simply the wrong reading of "narrow". */
@media (max-width: 1300px) { .topbar-app .topnav-links { display: none; } }

.topbar-app .spacer { flex: 1; }
.topbar-app .who { text-align: right; line-height: 1.25; }
.topbar-app .who b { font: 600 13.5px var(--font-head); color: var(--ink); display: block; }
.topbar-app .who span { font: 11.5px var(--font-head); color: var(--muted); }
.topbar-app .avatar { width: 38px; height: 38px; border-radius: 50%; flex: none;
  background: linear-gradient(150deg, var(--navy-soft), var(--navy-2)); color: var(--gold-lt);
  display: grid; place-items: center; font-weight: 700; font-size: 14px; }
/* Transitions are suspended for the single frame in which the palette is swapped.
   Not cosmetic — without this the switch leaves the menu unreadable. Anything carrying a
   transition on a property whose value comes from a custom property (the sidebar's links
   are `transition: all .13s` with `color: var(--side-link)`) does not repaint when that
   property changes on :root: the browser neither starts a transition nor takes the new
   value, so the old colour stays. The sidebar's own background has no transition and does
   flip, so the result was light ink on the light sidebar — an unreadable navigation until
   the next page load, which is also why it looked correct on every reload. Verified live:
   an element created AFTER the swap took the new colour while the identical elements
   already on the page kept the old one. */
:root.theme-switching *,
:root.theme-switching *::before,
:root.theme-switching *::after { transition: none !important; }

/* The theme button. Quiet at rest — it is a preference, not an action, and it sits next to
   Log out. Sized to the same 38px as the avatar so the right-hand cluster keeps one rhythm. */
.theme-btn {
  width: 34px; height: 34px; flex: none; display: grid; place-items: center; cursor: pointer;
  background: none; border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); padding: 0; transition: color .13s, border-color .13s, background .13s;
}
.theme-btn:hover { color: var(--gold-dk); border-color: var(--gold); background: var(--bg-2); }
.theme-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
/* The leaf shows whether the low-strain palette is ON. The light/dark button next to it
   swaps its icon to say which theme is in force; this one is a switch, so it says so by
   filling in rather than by changing shape. */
:root[data-theme="green"] .theme-btn-green {
  color: var(--gold-dk); border-color: var(--gold); background: var(--badge-bg);
}
/* Show the icon for the theme you would switch TO. Written out for all three states the
   root can be in — an explicit choice either way, and the un-stamped default where only
   the operating system has an opinion. Getting this wrong in one state shows both icons
   at once, which is why the sun is hidden at :root rather than only inside the media
   query: an omission there fails visibly rather than silently. */
.theme-btn .ic-sun { display: none; }
.theme-btn .ic-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="green"]) .theme-btn .ic-sun { display: block; }
  :root:not([data-theme="light"]):not([data-theme="green"]) .theme-btn .ic-moon { display: none; }
}
:root[data-theme="dark"] .theme-btn .ic-sun { display: block; }
:root[data-theme="dark"] .theme-btn .ic-moon { display: none; }
:root[data-theme="light"] .theme-btn .ic-sun { display: none; }
:root[data-theme="light"] .theme-btn .ic-moon { display: block; }

.topbar-app .logout { color: var(--muted); text-decoration: none; font: 500 13px var(--font-head); padding: 7px 10px;
  border-radius: 8px; border: 1px solid var(--line); }
.topbar-app .logout:hover { color: var(--bad); border-color: var(--bad); }
/* The chrome's icon-only controls: the support ticket, the feedback note, and Log out.

   SAME GEOMETRY AS .theme-btn, deliberately — 34px, same border, same radius. The right-hand
   end of the bar is now six small square things in a row, and six squares that agree on their
   size read as one cluster while six that nearly agree read as a mistake. It is not merged
   with .theme-btn because these are LINKS to somewhere and those are switches for a
   preference; sharing a selector would be sharing a name for two different behaviours.

   Colours are the existing tokens only, per the standing rule: no new hex, nothing redefined,
   and both themes come along for free because that is what the tokens are for. */
.topbar-app .icon-btn {
  width: 34px; height: 34px; flex: none; display: grid; place-items: center;
  background: none; border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); padding: 0; text-decoration: none; position: relative;
  transition: color .13s, border-color .13s, background .13s;
}
.topbar-app .icon-btn:hover { color: var(--gold-dk); border-color: var(--gold); background: var(--bg-2); }
.topbar-app .icon-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

/* Log out: the one control on the bar that must never be clicked by accident, and it no
   longer has a word on it to slow the hand down. So it gets the two things a label was
   doing — SEPARATION (the rule; nothing else in this cluster has one) and a colour that is
   not the colour of everything beside it. Red on hover, gold everywhere else, which is the
   same distinction .topbar-app .logout already drew when it had its text. */
.topbar-app .icon-btn-out { margin-left: 18px; }
.topbar-app .icon-btn-out::before {
  content: ""; position: absolute; left: -10px; top: 50%; transform: translateY(-50%);
  width: 1px; height: 22px; background: var(--line);
}
.topbar-app .icon-btn-out:hover { color: var(--bad); border-color: var(--bad); background: none; }


main { max-width: 1180px; margin: 0 auto; padding: 26px 26px 60px; }

/* ---------- typography ----------
   Fraunces (the display face) was previously used only on KPI numbers. Page titles
   now carry it too: at 30px it gives the product an editorial weight that separates
   it from default SaaS, while Poppins keeps every working label crisp. Restraint is
   the point — display face for the title only, never for UI chrome. */
h1 { font: 600 clamp(25px, 3vw, 31px)/1.12 var(--font-display); margin: 0 0 18px;
  letter-spacing: -.02em; color: var(--ink); text-wrap: balance; }
h2 { font: 650 15.5px/1.3 var(--font-head); margin: 24px 0 11px; color: var(--ink); letter-spacing: -.005em; }
/* brand.html puts every heading level on the head face, not just h1-h3 */
h3, h4, h5 { font-family: var(--font-head); }
/* THE MISSING MIDDLE. h1 is 31px Fraunces and everything below it clustered at
   11-15.5px Poppins, so a long page read as one dense band under the title with no
   way to see its shape. One confident step between them: used for the major divisions
   of a page, where h2 marks a section and h3 a group inside it. */
h3 { font-size: 19px; font-weight: 600; line-height: 1.25; letter-spacing: -.01em;
  color: var(--ink); margin: 22px 0 9px; }
h4 { font-size: 15px; font-weight: 600; line-height: 1.3; color: var(--ink); margin: 18px 0 8px; }
a { color: var(--brand-2); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- panels / cards ---------- */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px; margin-bottom: 18px; box-shadow: var(--shadow); }

/* ---------- KPIs ---------- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 14px; margin-bottom: 20px; }
.kpi { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; box-shadow: var(--shadow); position: relative; overflow: hidden; }
.kpi::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(var(--gold-lt), var(--gold-dk)); opacity: .0; transition: opacity .15s; }
.kpi:hover::before { opacity: 1; }
.kpi .n { font-family: var(--font-display); font-size: var(--num-lg); font-weight: 600; color: var(--navy); letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; }
.kpi .l { font: 600 11px var(--font-head); color: var(--muted); text-transform: uppercase; letter-spacing: .05em;
  margin-top: 3px; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]):not([data-theme="green"]) .kpi .n { color: var(--gold-lt); } }
:root[data-theme="dark"] .kpi .n { color: var(--gold-lt); }

/* ---------- Process dashboards: full-width + premium density ----------
   Opt-in via a .board wrapper, so only these data-dense pages widen and breathe — forms and
   reading pages keep their comfortable ~1180px measure. The left nav already offsets main, so
   a larger cap simply fills the content area instead of sitting in a narrow centred column.
   The gold accent is the signature; everything else stays quiet and disciplined. */
main:has(.board) { max-width: 1680px; }
.board .kpis { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; margin-bottom: 22px; }
.board .kpi { padding: 16px 18px 15px; }
.board .kpi::before { opacity: .3; }                 /* a quiet gold edge, not shouting */
.board .kpi .n { font-size: 30px; line-height: 1.05; }
/* Clickable KPI tiles drill into their stage; lift + gold edge on hover to read as tappable. */
a.kpi { text-decoration: none; color: inherit; cursor: pointer;
  transition: border-color .14s ease, transform .14s ease, box-shadow .14s ease; }
a.kpi:hover { border-color: var(--gold); transform: translateY(-2px); box-shadow: var(--shadow-lg); }
a.kpi:hover::before { opacity: 1; }
a.kpi:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Stage tabs on their own row (keeping the gold-underline rail), filters on the row beneath —
   predictable and never cramped, however many tabs or filters a board carries. */
.board-toolbar { margin: 0 0 18px; }
.board-toolbar .stagetabs { margin: 0; }
.board-toolbar .board-facets { padding: 12px 2px 0; }
/* One consistent, premium style for every dashboard's filter controls — so Year / Category /
   Department / Supplier / etc. look identical across all boards without per-template styling. */
.board-facets { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.board-facets input[type="search"], .board-facets select {
  font-size: 13px; padding: 7px 11px; border: 1px solid var(--line-strong, var(--line));
  border-radius: 9px; background: var(--panel); color: var(--ink); font-family: var(--font-body); }
.board-facets input[type="search"] { min-width: 170px; max-width: 260px; }
.board-facets select { max-width: 168px; }
.board-facets input[type="search"]:focus, .board-facets select:focus {
  outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold); }
/* "More filters" — the less-used dropdowns stay hidden until asked for, so the bar is calm. */
.board-facets .more-filter { display: none; }
.board-facets.show-more .more-filter { display: inline-block; }
.board .board-facets { margin-bottom: 16px; }
/* Wide, many-column boards scroll sideways on small screens instead of crushing columns to an
   unreadable width (.tablescroll already provides the scroll box). */
.board .tablescroll > table { min-width: 720px; }
/* Phones / tablets: tighten the rhythm so a dense board still feels considered, not cramped,
   and the filter controls flow to full-width rows instead of overflowing. */
@media (max-width: 760px) {
  .board .kpis { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 16px; }
  .board .kpi { padding: 13px 14px 12px; }
  .board .kpi .n { font-size: 25px; }
  .board thead th, .board tbody td { padding: 10px 12px; }
  .board-facets input[type="search"] { flex: 1 1 150px; min-width: 0; max-width: none; }
  .board-facets select { flex: 1 1 44%; max-width: none; }
}
.board table { font-size: 14.5px; }
.board thead th { padding: 13px 16px; }
.board tbody td { padding: 14px 16px; }
@media (max-width: 900px) { main:has(.board) { max-width: 100%; } }

/* ---------- The shared filter bar (_facets.html) ----------
   Replaces the loose row of unlabelled dropdowns every board used to hand-roll. Three
   things make it read as considered rather than as a form:

   1. EVERY CONTROL IS LABELLED, above the control, in the same small caps the KPI tiles
      use. A bare row of selects reading "All years / All categories / All business units"
      makes the reader parse each one to find out what it filters; the label answers it
      before they look. It costs 13px of height and buys the whole bar's legibility.
   2. THE BAR IS A SURFACE, not floating controls — one panel with the tabs above it, so
      "filter" reads as one object rather than six.
   3. The gold signature appears ONCE per state: on focus, and on the count of active
      filters. Gold everywhere would stop meaning anything. */
.facets { display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: flex-end;
  padding: 14px 16px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); margin: 0 0 20px; }
.facet { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.facet > span { font: 600 10.5px var(--font-head); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; padding-left: 1px; }
.facets select, .facets input[type="search"] {
  font: 400 13.5px var(--font-body); color: var(--ink); background: var(--bg-2);
  border: 1px solid var(--line-strong, var(--line)); border-radius: 9px;
  padding: 8px 11px; min-height: 36px; transition: border-color .14s ease, box-shadow .14s ease; }
.facets select { max-width: 190px; min-width: 130px; cursor: pointer;
  /* The native arrow is inconsistent across platforms and looks unfinished beside a
     custom control set; one chevron, drawn once, in the muted ink. */
  appearance: none; -webkit-appearance: none; padding-right: 30px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238795ad' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center; background-size: 15px; }
.facets select:hover, .facets input[type="search"]:hover { border-color: var(--muted); }
.facets select:focus, .facets input[type="search"]:focus { outline: none;
  border-color: var(--gold); box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 22%, transparent); }

/* Search leads, and looks like it: wider, with the magnifier inside the field. */
.facet-search { position: relative; display: flex; align-items: center; flex: 1 1 240px;
  min-width: 190px; max-width: 340px; }
.facet-search svg { position: absolute; left: 11px; width: 15px; height: 15px;
  color: var(--muted); pointer-events: none; }
.facet-search input[type="search"] { width: 100%; padding-left: 33px; }
.facet-search input::-webkit-search-cancel-button { cursor: pointer; }

.facet-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
/* A quiet third button weight: Apply is the action, these are controls on the controls. */
.facets .btn.ghost { background: transparent; border: 1px solid var(--line-strong, var(--line));
  color: var(--muted); }
.facets .btn.ghost:hover { border-color: var(--gold); color: var(--ink); background: var(--bg-2); }
/* How many filters are on — the number that tells someone why the board looks empty. */
.facet-count { display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; margin-left: 6px; padding: 0 5px; border-radius: 999px;
  font: 600 10.5px var(--font-head); background: var(--gold); color: var(--navy); }

.facets .more-filter { display: none; }
.facets.show-more .more-filter { display: flex; }

@media (max-width: 760px) {
  .facets { gap: 9px 10px; padding: 12px; }
  .facet-search { flex: 1 1 100%; max-width: none; }
  .facet { flex: 1 1 44%; }
  .facets select { max-width: none; width: 100%; }
  .facet-actions { margin-left: 0; flex: 1 1 100%; }
}

/* ---------- The board, tightened ----------
   The toolbar is one object: stage tabs sitting directly on the filter surface, sharing
   its top edge, so the eye reads "choose a stage, then narrow it" as one movement rather
   than as two unrelated rows floating above a table. */
.board-toolbar .stagetabs { margin: 0 0 12px; }
.board .page-head { margin-bottom: 22px; }
.board .page-head p { max-width: 78ch; }
/* Tables get the same panel treatment as the filter bar so the page reads as a stack of
   deliberate surfaces rather than a form followed by a grid. */
.board table { box-shadow: var(--shadow); }
.board thead th { background: var(--bg-2); font: 600 10.5px var(--font-head);
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.board tbody tr { transition: background .12s ease; }
.board tbody tr:hover { background: var(--bg-2); }
/* Money and dates line up when they are tabular; a column of figures that does not is the
   single most common thing that makes a dense screen look amateur. */
.board td { font-variant-numeric: tabular-nums; }

/* ---------- The generated flow diagram (_flowdiagram.html) ----------
   A numbered chain with a connector between steps, and a marker where the work changes
   hands. Horizontal on a wide screen so the sequence reads left-to-right the way people
   expect a process to; stacked below 900px, where a horizontal chain of seven would
   either clip or shrink its text to nothing.

   An ordered list underneath, so a screen reader gets "1 of 7" and the real screen name
   rather than a picture it cannot describe. */
.flowdia .flowchain { display: flex; flex-wrap: wrap; gap: 0; list-style: none;
  margin: 0; padding: 0; counter-reset: none; }
.flowstep { position: relative; display: flex; align-items: flex-start; gap: 9px;
  flex: 1 1 150px; min-width: 140px; padding: 12px 14px 12px 0; }
/* The connector. Drawn between steps rather than after every one, so the chain does not
   end in a line pointing at nothing. */
.flowstep + .flowstep::before { content: ""; position: absolute; left: -7px; top: 25px;
  width: 14px; height: 1px; background: var(--line-strong, var(--line)); }
.fs-n { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 25px; height: 25px; border-radius: 999px; background: var(--bg-2);
  border: 1px solid var(--line-strong, var(--line));
  font: 600 12px var(--font-head); color: var(--muted); }
.fs-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fs-screen { font: 600 13px/1.35 var(--font-head); color: var(--ink); }
.fs-actor { font: 400 11.5px var(--font-body); color: var(--muted); }
/* THE HANDOVER is the claim this diagram exists to make, so it is the one thing that gets
   the gold. Everything else stays quiet. */
.flowstep.handover .fs-n { border-color: var(--gold); color: var(--gold-dk);
  background: color-mix(in srgb, var(--gold) 14%, var(--panel)); }
.flowstep.handover + .flowstep::before,
.flowstep.handover::before { background: var(--gold); }
.fs-hand { display: block; margin-top: 3px; font: 600 9.5px var(--font-head);
  color: var(--gold-dk); text-transform: uppercase; letter-spacing: .045em; }
@media (max-width: 900px) {
  .flowdia .flowchain { flex-direction: column; }
  .flowstep { flex: 1 1 auto; padding: 9px 0 9px 2px; }
  .flowstep + .flowstep::before { left: 12px; top: -5px; width: 1px; height: 10px; }
}

/* ---------- The version footer, and the release notes it links to ----------
   Quiet by construction: it appears on every page, so it must be findable and never
   compete with the content. Muted until hovered, and gold only on hover — the same
   single-signature rule the rest of the product follows. */
.appver { margin: 34px 0 4px; padding-top: 12px; border-top: 1px solid var(--line);
  font: 400 10px var(--font-body); color: var(--muted); letter-spacing: .01em; }
/* PINNED TO THE BOTTOM OF THE COLUMN, not to the end of the content. It used to sit
   wherever the page happened to stop, so on a short screen it floated in the middle of the
   window and moved from page to page (Imran 2026-08-29). The content column becomes a flex
   column and the footer takes up the slack, so it lands on the bottom edge of a short page
   and directly after the content on a long one. `main > * { flex: none }` holds every other
   child at its natural height, so no existing page changes how it lays out. */
.app > div { display: flex; flex-direction: column; min-width: 0; }
.app > div > main { flex: 1 1 auto; display: flex; flex-direction: column; }
.app > div > main > * { flex: none; }
.appver { margin-top: auto; }
.appver a { color: var(--muted); text-decoration: none; border-bottom: 1px solid transparent;
  transition: color .14s ease, border-color .14s ease; }
.appver a:hover { color: var(--gold-dk); border-bottom-color: var(--gold); }
.appver a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Entity attribution on the left, release stamp on the right, one line on a wide screen
   and two on a narrow one. No new colour: both halves inherit .appver's muted treatment,
   which is the standing rule (atlas-do-not-change-app-colours). */
.appver { display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: baseline; }
.appver .appver-rel { margin-left: auto; }
@media (max-width: 560px) { .appver .appver-rel { margin-left: 0; } }

/* The operator line on the pages that are NOT the application shell: the sign-in page,
   the two-factor step, the forgotten-password page, the workspace finder and the
   supplier portal. Those five never extended base.html, so for four releases they said
   nothing at all about who runs the service -- on exactly the screens where somebody is
   deciding whether to trust it. See app/templates/_operator.html.

   LAYOUT ONLY. It sets no colour of its own: the footer inside it is the same .appver
   component the application shell uses, so it inherits --muted, --line and --gold in all
   three themes and nothing here needs a dark or green variant. That is the standing rule
   (atlas-do-not-change-app-colours) -- reuse the token, never restate the value. */
.auth-operator { grid-column: 1 / -1; padding: 0 clamp(20px, 6vw, 64px) 22px; }
.auth-operator .appver { margin: 0; }
/* The operator line is a SIBLING of .auth, not a child of it, because these seven templates
   have seven different nesting depths inside .auth and one shared shape outside it. That
   made the first version of this WORSE THAN USELESS: .auth is min-height:100vh, so the line
   rendered at exactly y = 100vh -- present in the DOM, correct in every contrast
   measurement, and one full scroll below the fold on the one screen it most needed to be
   seen on. Measured at 1366x900: rect.y = 900. A footer nobody scrolls to is a footer
   nobody reads, and every numeric check in the suite passed while it was like that.
   Caught only by driving a real browser and asking where the element actually is.

   So the page becomes a column and .auth takes what is left. `:has()` is doing the work of
   "style the element BEFORE this one", which CSS otherwise cannot do; it is not a new
   dependency -- this stylesheet already relies on color-mix(), which shipped later than
   :has() did. Scoped to `> .auth-operator` so it can only ever affect these auth pages. */
body:has(> .auth-operator) { display: flex; flex-direction: column; min-height: 100vh; }
body:has(> .auth-operator) > .auth { flex: 1 1 auto; min-height: 0; }
body:has(> .auth-operator) > .auth-operator { flex: 0 0 auto; }
/* The two-column auth grid gains a row so the line sits under BOTH columns rather than
   being tucked inside one of them and reading as part of the marketing panel. */
.auth { grid-template-rows: 1fr auto; }
@media (max-width: 900px) { .auth-operator { padding: 0 20px 18px; } }

/* The changelog is markdown shown verbatim — see whats_new.html for why it is not run
   through a renderer. It therefore needs to wrap rather than scroll a page sideways. */
pre.changelog { white-space: pre-wrap; word-wrap: break-word; margin: 0;
  font: 400 13px/1.65 var(--font-body); color: var(--ink); background: transparent; }

/* ---------- The measured strip (_measured.html) ----------
   Same .kpi component as the count tiles, so the page reads as one system, plus a third
   line: what the number is OF. A money figure without its currency, or a rate without its
   denominator, is the kind of number people quietly stop trusting. */
.kpis.measured .kpi .sub { font: 400 11px var(--font-body); color: var(--muted);
  margin-top: 4px; letter-spacing: .01em; }
/* The measured tiles sit directly under the count tiles, so the gap between the two rows
   is tightened — they are two readings of one board, not two sections. */
.board .kpis.measured { margin-top: -8px; }
.kpis.measured .kpi .n { font-variant-numeric: tabular-nums; }

/* ---------- Payables aging strip ----------
   One horizontal row of buckets above the payables queue: how the open ledger ages, and a
   click filters to a bucket. Deliberately NOT another KPI grid — aging is a single ordered
   scale read left to right, and rendering it as separate cards loses that it is one shape.
   Quiet by default; a bucket with anything actually late takes a red numeral, and the
   selected bucket takes the gold rail that means "active" everywhere else in the product. */
.agestrip { display: flex; align-items: stretch; flex-wrap: wrap; gap: 1px; margin: 0 0 22px;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--line); }
.agestrip-label { display: flex; align-items: center; padding: 10px 16px; background: var(--panel);
  font: 600 11px var(--font-head); color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.agebucket { flex: 1 1 0; min-width: 96px; text-decoration: none; background: var(--panel);
  padding: 10px 16px 11px; border-bottom: 2px solid transparent; transition: background .14s ease, border-color .14s ease; }
.agebucket:hover { background: var(--bg-2); }
.agebucket .n { display: block; font: 600 21px/1.1 var(--font-display); color: var(--navy);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.agebucket .l { display: block; font: 600 11px var(--font-head); color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }
.agebucket.late .n { color: var(--bad); }
/* No terms recorded is a GAP, not a late payment — amber (something to fix), never red. */
.agebucket.unknown .n { color: var(--warn); }
.agebucket.on { border-bottom-color: var(--gold); background: var(--bg-2); }
.agebucket:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]):not([data-theme="green"]) .agebucket .n { color: var(--gold-lt); } }
:root[data-theme="dark"] .agebucket .n { color: var(--gold-lt); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]):not([data-theme="green"]) .agebucket.late .n { color: var(--bad); }
  :root:not([data-theme="light"]):not([data-theme="green"]) .agebucket.unknown .n { color: var(--warn); } }
:root[data-theme="dark"] .agebucket.late .n { color: var(--bad); }
:root[data-theme="dark"] .agebucket.unknown .n { color: var(--warn); }
@media (max-width: 760px) {
  .agestrip-label { flex: 1 1 100%; padding: 8px 14px; }
  .agebucket { min-width: 84px; padding: 9px 12px 10px; }
  .agebucket .n { font-size: 18px; }
}

/* ---------- tables ---------- */
table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
  margin-bottom: 18px; }
th { text-align: left; font: 650 11px var(--font-head); text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); background: var(--bg-2); padding: 11px 14px; border-bottom: 1px solid var(--line); }
td { padding: 11px 14px; border-top: 1px solid var(--line); vertical-align: top; font-size: 14px; }
tr:first-child td { border-top: 0; }
tbody tr:hover td, table tr:hover td { background: color-mix(in srgb, var(--accent) 55%, transparent); }
.winner td { background: var(--ok-bg) !important; }

/* ---------- buttons ---------- */
.btn { display: inline-flex; align-items: center; gap: 6px; background: var(--navy); color: #fff; border: 0;
  border-radius: 9px; padding: 9px 17px; font: 600 14px var(--font-head); cursor: pointer;
  text-decoration: none; transition: all .14s; box-shadow: 0 1px 2px rgba(16,26,46,.12); }
.btn:hover { background: var(--navy-soft); text-decoration: none; transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(16,26,46,.18); }
.btn:active { transform: translateY(0); }
.btn.secondary { background: var(--panel); color: var(--navy); border: 1px solid var(--line-strong); box-shadow: none; }
.btn.secondary:hover { background: var(--bg-2); border-color: var(--brand-2); color: var(--brand-2); }
.btn.danger { background: var(--bad); }
.btn.danger:hover { background: #922017; }
.btn.gold { background: linear-gradient(145deg, var(--gold-lt), var(--gold-dk)); color: var(--navy-2); }
.btn.gold:hover { filter: brightness(1.05); }
.btn.small { padding: 5px 11px; font-size: 12.5px; border-radius: 7px; }
:root[data-theme="dark"] .btn.secondary { color: var(--gold-lt); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]):not([data-theme="green"]) .btn.secondary { color: var(--gold-lt); } }

/* ---------- forms ---------- */
form.inline { display: inline; }
/* A ONE-LINE FILTER ROW: label beside field, the whole thing wrapping as a unit. Its own
   class rather than form.inline, which 25 templates use for a one-button form inside a table
   cell - making that a flex container would move layout all over the app to fix one panel.
   NOT `.filterbar`: that name is already taken further down by a different component. */
.filterrow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.filterrow label { display: inline-block; margin: 0; white-space: nowrap; }
.filterrow input, .filterrow select { width: auto; }
.filterrow .btn { margin: 0; }
label { display: block; font: 550 12.5px var(--font-head); color: var(--ink-soft); margin: 11px 0 4px; }
input, select, textarea { width: 100%; padding: 9px 11px; border: 1px solid var(--line-strong);
  border-radius: 9px; font: inherit; font-size: 14px; background: var(--panel); color: var(--ink);
  transition: border-color .13s, box-shadow .13s; }
/* A checkbox is not a text field, and the rule above does not know that. width:100%
   stretches the control to the full width of its label, and the browser then draws the
   tick mark centred in all that space — so the box floats out into the middle of the
   row, above the words it belongs to. On a single confirmation you can still guess what
   it means; on the scope list in Integrations, seven boxes sit in a column of their own
   with seven labels in another, and nothing says which belongs to which.

   Nine controls had already worked around this one at a time with style="width:auto".
   Fixing the rule instead of adding a tenth: those overrides now agree with the
   stylesheet rather than fighting it, and a checkbox added later is right by default.
   Padding goes too — 9px of it inflates the hit area off-centre from the drawn box. */
input[type="checkbox"], input[type="radio"] { width: auto; padding: 0; }

/* A file input is two controls in one, and the generic rule only reaches the outer
   half. The wrapper picked up the theme — dark panel, dark border — while the button
   inside stayed the browser's native light grey, so every upload on the product looked
   like an unfinished form: a bright grey slab in the middle of a dark screen. Ten of
   them, and two are in the supplier portal, which is the one surface a customer's
   suppliers judge them by.

   Styled through ::file-selector-button with the same variables as everything else, so
   it follows light and dark without a second rule. Padding on the wrapper is reduced
   because the button brings its own. */
input[type="file"] { padding: 6px 8px; }
input[type="file"]::file-selector-button {
  font: 600 12.5px var(--font-head); cursor: pointer;
  background: var(--bg-2); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: 7px;
  padding: 6px 13px; margin: 0 11px 0 0;
  transition: border-color .13s, background .13s;
}
input[type="file"]::file-selector-button:hover {
  border-color: var(--brand-2); background: var(--panel);
}

input:focus, select:focus, textarea:focus { outline: none; border-color: var(--brand-2);
  box-shadow: 0 0 0 3px rgba(47,111,176,.13); }
textarea { min-height: 74px; resize: vertical; }
::placeholder { color: var(--muted); }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0 20px; }
.grid4 { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 0 20px; }
/* A grid item defaults to min-width:auto, so it refuses to shrink below its content.
   That silently defeats .tablescroll: _tablesort.html wraps every table in a scroll box,
   but the box can never be narrower than the table, so the panel widens instead and the
   whole page scrolls sideways. Settings was 77px past the window edge before anything on
   this branch touched it. Zero here lets the scroll box do its job. */
.grid2 > *, .grid3 > *, .grid4 > * { min-width: 0; }

/* ---------- badges ---------- */
/* Plain badges label a thing — "Yours to set", "Set by <provider>" — so they carry the
   brand rather than a neutral. They previously used --accent on --brand, which in dark
   mode is #1a2739 on #0F172A: dark on dark, and effectively invisible. There were no
   dark-mode rules for .badge at all. Tokens rather than a second rule, so the media
   query, [data-theme="dark"] and [data-theme="light"] are all covered from one place. */
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 999px;
  font: 650 11.5px var(--font-head); background: var(--badge-bg); color: var(--badge-fg);
  letter-spacing: .01em; white-space: nowrap;
  /* A hairline in the badge's own colour. On the dark palette the tinted fill alone
     nearly vanished against --panel #16202f, so a neutral status ("Approved") read as
     absent while the coloured ones popped — the plainest status looked like the least
     real one. The border is currentColor at low alpha, so every variant keeps its
     own hue and none of them float. */
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent); }
.badge.ok { background: var(--ok-bg); color: var(--ok); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.badge.bad { background: var(--bad-bg); color: var(--bad); }
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .7; }
/* Inline advisory that must be noticed but is not an error — e.g. two settings that
   disagree in a way that may well be deliberate. Uses the warn tokens so it follows
   the light/dark theme instead of hardcoding a colour. */
.warn-note { margin: 0 0 12px; font-size: 13px; color: var(--warn); background: var(--warn-bg);
  border-left: 3px solid var(--warn); padding: 9px 13px; border-radius: 0 8px 8px 0; }
/* Explains what the public/private entity choice actually changes. Not a warning —
   this is confirmation of a deliberate setting, so it uses the neutral surface. */
.entity-effects { margin: 12px 0 4px; padding: 12px 15px; background: var(--bg-2);
  border: 1px solid var(--line); border-radius: 10px; font-size: 13px; }
.entity-effects ul { margin: 7px 0 8px; padding-left: 19px; }
.entity-effects li { margin: 3px 0; }

/* ---------- bar chart rows ---------- */
.bar-row { display: grid; grid-template-columns: 190px 1fr 96px; gap: 12px; align-items: center;
  margin: 9px 0; font-size: 13.5px; }
.bar { height: 16px; background: var(--bg-2); border-radius: 8px; overflow: hidden; }
/* DATA VIZ BELONGS TO THE PALETTE. These bars were a generic mid-blue (--brand-2
   #2f6fb0) running into navy — a hue that appears nowhere in the brand. On the spend
   pages that put bright blue bars beside a navy-and-gold sidebar, which read as two
   different products stitched together and was the loudest un-premium element in the
   app. Now a navy→gold ramp: the same two colours the rest of the product is made of,
   with gold at the long end so the biggest number is the one carrying the accent.
   --brand-2 is kept for INTERACTIVE affordance only (links, focus rings), where a blue
   is doing real work that gold would not do as legibly. */
.bar i { display: block; height: 100%; border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--navy-soft), var(--gold-dk) 85%, var(--gold));
  transition: width .5s ease; }
.bar-row > div:last-child { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-soft); }

.muted { color: var(--muted); font-size: 13px; }
.topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; }
.topbar h1 { margin: 0; }
.error { background: var(--bad-bg); color: var(--bad); border-radius: 9px; padding: 11px 14px;
  margin-bottom: 14px; font-size: 14px; border: 1px solid color-mix(in srgb, var(--bad) 22%, transparent); }
.error strong { display: block; margin-bottom: 2px; }

/* ---------- negotiation threads: one card per proponent, in evaluated rank order ---------- */
.nthread { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 12px; }
.nthread.open { border-left: 3px solid var(--gold); }
.nthread.agreed { border-left: 3px solid var(--ok); }
.nthread.closed { border-left: 3px solid var(--line-strong); }
.nthread > header { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: baseline; justify-content: space-between; }
.nthread h3 { margin: 0; font-size: 15px; }
.nrank { display: inline-block; min-width: 20px; text-align: center; border-radius: 10px; padding: 1px 8px;
  background: var(--accent); color: var(--muted); font-size: 12px; font-weight: 600; margin-right: 8px; }
.nblocked { color: var(--muted); font-size: 12.5px; font-style: italic; }

/* ---------- callout panels (alerts / my actions) ---------- */
.callout { border-radius: var(--radius); padding: 18px 20px; margin-bottom: 18px; box-shadow: var(--shadow); border: 1px solid var(--line); }
.callout h2 { margin-top: 0; }
.callout.gold { background: linear-gradient(140deg, color-mix(in srgb, var(--gold-lt) 16%, var(--panel)), var(--panel));
  border-color: color-mix(in srgb, var(--gold) 35%, var(--line)); }
/* Transient "just auto-filled" highlight on a form control — a brand-gold tint that tracks
   the theme, replacing a hardcoded pale yellow that did not. */
.field-flash { background: color-mix(in srgb, var(--gold-lt) 20%, var(--panel)) !important;
  transition: background .4s ease; }
.callout.blue { background: linear-gradient(140deg, color-mix(in srgb, var(--brand-2) 10%, var(--panel)), var(--panel));
  border-color: color-mix(in srgb, var(--brand-2) 28%, var(--line)); }
.callout .row { display: flex; align-items: center; gap: 9px; padding: 5px 0; font-size: 13.5px; }

/* ---------- lifecycle progress tracker ---------- */
.tracker { display: flex; align-items: flex-start; gap: 0; overflow-x: auto; padding: 6px 2px 2px;
  margin-bottom: 18px; }
.tracker .step { flex: 1 1 0; min-width: 92px; text-align: center; position: relative; }
.tracker .step::before { content: ""; position: absolute; top: 13px; left: -50%; width: 100%; height: 2px;
  background: var(--line-strong); z-index: 0; }
.tracker .step:first-child::before { display: none; }
.tracker .step .dot { position: relative; z-index: 1; width: 28px; height: 28px; margin: 0 auto 7px;
  border-radius: 50%; background: var(--panel); border: 2px solid var(--line-strong); color: var(--muted);
  display: grid; place-items: center; font-size: 12.5px; font-weight: 700; }
.tracker .step .lbl { font: 11.5px var(--font-head); color: var(--muted); line-height: 1.3; padding: 0 3px; }
.tracker .step.done .dot { background: var(--ok); border-color: var(--ok); color: #fff; }
.tracker .step.done::before { background: var(--ok); }
.tracker .step.current .dot { background: linear-gradient(150deg, var(--gold-lt), var(--gold-dk));
  border-color: var(--gold-dk); color: var(--navy-2); box-shadow: 0 0 0 4px rgba(201,162,75,.22); }
.tracker .step.current .lbl { color: var(--ink); font-weight: 650; }
.tracker .step.stopped .dot { background: var(--bad); border-color: var(--bad); color: #fff; }
.tracker .step.stopped .lbl { color: var(--bad); font-weight: 650; }

/* ---------- empty states ---------- */
.empty { text-align: center; padding: 46px 20px; color: var(--muted); }
.empty img { width: 132px; height: 132px; opacity: .9; margin-bottom: 14px; }
.empty h3 { margin: 0 0 6px; color: var(--ink); font-size: 16px; }

/* ---------- login / register ---------- */
.auth { display: grid; grid-template-columns: 1.05fr .95fr; min-height: 100vh; }
.auth .brandside {
  background: linear-gradient(160deg, var(--navy) 0%, var(--navy-2) 100%); color: #fff;
  padding: 60px 56px; display: flex; flex-direction: column; justify-content: space-between;
  /* gap stops the footer butting against the feature list once the copy grows;
     space-between alone lets them touch as soon as content fills the panel */
  gap: 40px; position: relative; overflow: hidden;
}
/* Optional hero art — drop an image at /static/img/login-bg.png and it appears
   behind the copy. The navy scrim on top is part of the same layer, so text stays
   readable over any photograph; with no image present the scrim alone renders as
   a clean gradient, so the page never looks broken while art is being sourced. */
.auth .brandside::before { content: ""; position: absolute; inset: 0; z-index: 0;
  background-image:
    linear-gradient(155deg, rgba(11,18,32,.93) 0%, rgba(11,18,32,.80) 42%, rgba(11,18,32,.58) 100%),
    url("/static/img/login-bg.png");
  background-size: cover; background-position: center; }
.auth .brandside > * { position: relative; z-index: 1; }
.auth .brandside::after { content: ""; position: absolute; right: -140px; bottom: -140px; width: 440px; height: 440px;
  border-radius: 50%; background: radial-gradient(circle, rgba(201,162,75,.26), transparent 70%); z-index: 0; }
.auth .brandside .mark { width: 66px; height: 66px; border-radius: 15px;
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16); display: grid; place-items: center;
  box-shadow: 0 4px 20px rgba(11,18,32,.45); }
.auth .brandside .mark svg { width: 100%; height: 100%; }

/* Two-part wordmark: house name in Poppins, product beneath it in gold small caps,
   separated by a hairline rule. Same lockup as the sidebar, sized for the hero. */
.auth .brandside .wordmark { margin: 20px 0 22px; font: 700 26px var(--font-head);
  color: #fff; letter-spacing: .01em; line-height: 1.05; }
.auth .brandside .wordmark span { display: block; margin-top: 7px; padding-top: 7px;
  font: 500 12px var(--font-head); letter-spacing: .34em; text-transform: uppercase;
  color: var(--gold-lt); border-top: 1px solid rgba(255,255,255,.16); max-width: 210px; }

/* gold eyebrow pill — the brand guide's own hero pattern */
.auth .brandside .eyebrow { display: inline-flex; align-items: center; gap: 9px;
  font: 600 12px var(--font-head); letter-spacing: .18em; text-transform: uppercase;
  color: var(--gold-lt); background: rgba(201,162,74,.12); border: 1px solid rgba(201,162,74,.30);
  padding: 8px 16px; border-radius: 999px; margin: 30px 0 0; }
.auth .brandside .eyebrow .dot { width: 7px; height: 7px; border-radius: 50%;
  background: var(--gold-lt); box-shadow: 0 0 0 4px rgba(221,187,99,.20); }

.auth .brandside h1 { font-family: var(--font-display); font-weight: 500; color: #fff;
  font-size: clamp(34px, 3.1vw, 50px); line-height: 1.1; margin: 22px 0 18px;
  max-width: 15ch; letter-spacing: -.02em; }
/* the brand's hero accent: gold italic on the phrase that matters */
.auth .brandside h1 .accent { color: var(--gold-lt); font-style: italic; }
.auth .brandside p { color: #c2cee2; font-size: clamp(15.5px, 1.1vw, 18px); max-width: 44ch; line-height: 1.65; font-weight: 300; }
.auth .brandside .feats { display: flex; flex-direction: column; gap: 16px; margin-top: 38px; font-family: var(--font-head); }
.auth .brandside .feats div { display: flex; align-items: center; gap: 14px; color: #e2e9f4;
  font-size: clamp(14.5px, 1vw, 16.5px); font-weight: 500; }
.auth .brandside .feats div::before { content: "✓"; width: 28px; height: 28px; flex: none; border-radius: 50%;
  background: rgba(201,162,75,.20); border: 1px solid rgba(201,162,74,.34);
  color: var(--gold-lt); display: grid; place-items: center; font-size: 14px; font-weight: 700; }

/* Capability strip — shows the breadth "Source-to-Pay" alone doesn't spell out. A quiet
   wrapped set of chips: enough to read "this does a lot" at a glance without competing
   with the headline. Each chip carries a small gold marker. */
.auth .brandside .caps-label { margin-top: 34px; font: 600 11px var(--font-head);
  letter-spacing: .16em; text-transform: uppercase; color: var(--gold-lt); }
.auth .brandside .caps { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.auth .brandside .caps span { display: inline-flex; align-items: center; gap: 7px;
  font: 500 12.5px var(--font-head); color: #dbe3ef; padding: 6px 12px 6px 10px;
  border-radius: 999px; background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.12); white-space: nowrap; }
.auth .brandside .caps span::before { content: ""; width: 5px; height: 5px; flex: none;
  border-radius: 50%; background: var(--gold-lt); }

.auth .brandside .foot { color: #8593ab; font: 500 13px var(--font-head); position: relative; z-index: 1; }

/* The panel clips (overflow:hidden keeps the gold glow inside), so on shorter
   screens — 768px laptops, or a browser with toolbars eating height — the tall
   type would push the footer out of sight. Tighten the scale instead of letting
   content disappear. Large displays keep the full-size treatment. */
@media (max-height: 900px) {
  .auth .brandside { padding: 42px 48px; gap: 22px; }
  .auth .brandside .mark { width: 54px; height: 54px; border-radius: 13px; }
  .auth .brandside .mark svg { width: 100%; height: 100%; }
  .auth .brandside .eyebrow { margin-top: 22px; padding: 7px 14px; font-size: 11px; }
  .auth .brandside h1 { font-size: clamp(27px, 2.5vw, 36px); margin: 16px 0 12px; }
  .auth .brandside p { font-size: 15px; line-height: 1.55; }
  .auth .brandside .caps-label { margin-top: 22px; }
  .auth .brandside .caps { gap: 7px; margin-top: 11px; }
  .auth .brandside .caps span { font-size: 11.5px; padding: 5px 10px 5px 9px; }
}
.auth .formside { display: grid; place-items: center; padding: 40px; background: var(--bg); }
.auth .formside .box { width: 100%; max-width: 380px; }
.auth .formside .box h2 { font-size: 22px; color: var(--ink); margin: 0 0 4px; }
.auth .formside .box .sub { color: var(--muted); font-size: 14px; margin-bottom: 22px; }
.auth .formside .box .btn { width: 100%; justify-content: center; padding: 11px; margin-top: 8px; }

/* legacy login-wrap fallback */
.login-wrap { max-width: 400px; margin: 9vh auto; }
.login-wrap .brand-big { text-align: center; font: 700 27px var(--font-head); color: var(--brand); margin-bottom: 18px; }

/* ---------- page section heading ---------- */
.page-head { margin-bottom: 20px; }
.page-head .eyebrow { font: 700 11px var(--font-head); letter-spacing: .14em; text-transform: uppercase; color: var(--gold-dk); }
.page-head h1 { margin: 4px 0 0; }
.page-head p { color: var(--muted); margin: 6px 0 0; font-size: 14px; }

/* ---------- mobile ---------- */
.menu-btn { display: none; }
@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }
  .sidebar { position: fixed; left: 0; top: 0; z-index: 60; width: 244px; transform: translateX(-100%);
    transition: transform .2s; box-shadow: var(--shadow-lg); }
  .app.nav-open .sidebar { transform: translateX(0); }
  .menu-btn { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 8px;
    border: 1px solid var(--line); background: var(--panel); cursor: pointer; }
  .grid2, .grid3, .grid4 { grid-template-columns: 1fr; }
  .auth { grid-template-columns: 1fr; }
  .auth .brandside { display: none; }
  .bar-row { grid-template-columns: 120px 1fr 80px; }
}
@media (max-width: 560px) {
  main { padding: 18px 14px 50px; }
  .topbar-app { padding: 0 14px; }
  .topbar-app .who { display: none; }
}

/* ---- Date fields: bigger calendar affordance, quick picks, plain-English echo ---- */
.datefield input[type="date"] { cursor: pointer; }
/* Enlarge and brand the native calendar button (WebKit/Blink).
   Keep it in normal flow - absolute positioning escapes the input box. */
.datefield input[type="date"]::-webkit-calendar-picker-indicator {
  width: 18px; height: 18px; padding: 2px; margin-left: 4px;
  border-radius: 5px; cursor: pointer;
  opacity: .55; transition: opacity .15s ease, background .15s ease;
}
.datefield input[type="date"]:hover::-webkit-calendar-picker-indicator,
.datefield input[type="date"]:focus::-webkit-calendar-picker-indicator {
  opacity: 1; background: rgba(201, 162, 75, .18);
}
.date-echo { font-size: 12px; color: var(--ink-soft); margin-top: 4px; min-height: 15px; }
.date-echo.warn { color: var(--warn); }   /* was #b45309: bypassed the theme, wrong in dark */
/* Quick picks stay out of the way until the field is in use - they are a
   shortcut, not the primary control (the calendar itself is).

   THE STRIP NEVER LEAVES THE FLOW, and only its VISIBILITY changes. It used to be
   display:none until :hover/:focus-within, so focusing a date field grew the page and
   blurring it shrank the page back - and the blur happens on the MOUSEDOWN of the next
   click. Anything below a date field therefore slid out from under the pointer between
   mousedown and mouseup, the two landed on different elements, and the browser dispatched
   no click event at all: a submit button below a date field silently did nothing whenever
   the date was the last thing touched. Wide forms hid it (the strip was one row and a 41px
   button still covered the point); a narrow column was fatal, where seven pills wrapped to
   four rows and moved the button 72px.

   nowrap is load-bearing, not tidiness: it is what makes the reserved height ONE CONSTANT
   ROW instead of a height that depends on how wide the column happens to be. The pills need
   512px and the narrowest cell they ship in is 153px, so a wrapping strip reserves four rows
   in one place and one row in another. They scroll sideways instead - the scrollbar itself
   is suppressed because it would put the height back on a content measurement. */
.date-picks {
  display: flex; visibility: hidden;
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  gap: 5px; margin-top: 6px;
  /* A QUERY CONTAINER, because what decides how many shortcuts fit is the width of this
     CELL and not of the window: one screen carries date fields 521px wide and date fields
     153px wide, and a media query cannot tell them apart.

     IT SITS ON THE STRIP AND NOT ON .datefield, which is where it was first written and
     where it did real damage. container-type also means `contain: inline-size`, and a
     wrapper whose width may not depend on its contents is a wrapper with NO width wherever
     its parent is shrink-to-fit - on /spend, /logs and /categories the date fields sat in
     exactly such a container and collapsed from 512px to 0. The same rule on the strip is
     harmless, and it earns a second keep: now that the strip is permanent rather than
     display:none, its 512px of pills would otherwise become the wrapper's max-content width
     and blow those same toolbars open. Containing the strip keeps it out of that
     calculation, so a date field is still sized by its INPUT, exactly as before. */
  container-type: inline-size;
}
.date-picks::-webkit-scrollbar { display: none; }
.datefield:hover .date-picks,
.datefield:focus-within .date-picks { visibility: visible; }
/* SHED SHORTCUTS THAT DO NOT FIT, from the least useful end, keeping the first one and
   Clear at every width. Making the strip one row is what stops it reflowing the page, but a
   one-row strip in a 153px cell would leave five of the seven pills - Clear among them -
   parked off the right edge of a scroller with no scrollbar to say so. A control nobody can
   see is not a smaller bug than a button that does nothing; it is the same bug, moved. So
   the strip offers only what it can actually show, and wide cells are untouched. */
@container (max-width: 500px) { .date-pick[data-opt="5"] { display: none; } }
@container (max-width: 430px) { .date-pick[data-opt="4"] { display: none; } }
@container (max-width: 355px) { .date-pick[data-opt="3"] { display: none; } }
@container (max-width: 285px) { .date-pick[data-opt="2"] { display: none; } }
@container (max-width: 215px) { .date-pick[data-opt="1"] { display: none; } }
.date-pick {
  flex: 0 0 auto;                 /* never squashed to illegibility in a narrow cell */
  font: 500 11.5px var(--font-head); line-height: 1; cursor: pointer;
  padding: 5px 9px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink-soft);
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.date-pick:hover { border-color: var(--gold); color: var(--ink); background: rgba(201, 162, 75, .10); }
.date-pick.clear { color: var(--muted); }
.date-pick.clear:hover { border-color: var(--line-strong); background: transparent; color: var(--ink-soft); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="green"]) .datefield input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(1) brightness(1.6); }
}
:root[data-theme="dark"] .datefield input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(1.6); }


/* ---- Sourcing project workspace nav: one page, every section a click away ---- */
.proj-nav {
  position: sticky; top: 0; z-index: 30;
  display: flex; flex-wrap: wrap; gap: 4px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 6px; margin: 0 0 16px;
  box-shadow: 0 4px 14px rgba(11, 18, 32, .05);
}
.proj-nav a {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 7px;
  font: 600 13px var(--font-head); color: var(--ink-soft); text-decoration: none;
  transition: background .15s ease, color .15s ease;
}
.proj-nav a:hover { background: rgba(201, 162, 75, .12); color: var(--ink); }
.proj-nav .chip {
  font: 600 11px var(--font-head); line-height: 1;
  padding: 3px 7px; border-radius: 999px;
  background: var(--line); color: var(--ink-soft);
}
.proj-nav .chip.warn { background: var(--warn-bg); color: var(--warn); }  /* was #fde8d7/#9a3412 */


/* ---- Source-to-pay journey: the whole process at first glance ---- */
.journey {
  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-2) 100%);
  border-radius: 14px; padding: 18px 20px 20px; margin: 0 0 20px;
  color: #e7edf5; overflow-x: auto;
}
.journey .eyebrow { color: var(--gold-lt); }
/* The strip is a <details> so each person can collapse it for good (ui.js data-remember).
   `display:flex` on the summary would drop the disclosure marker in Safari, so the marker
   is replaced with a chevron of our own that rotates on open — and `list-style:none` plus
   the ::-webkit-details-marker rule removes the native triangle in every engine. */
.journey > summary { list-style: none; }
.journey > summary::-webkit-details-marker { display: none; }
.journey > summary::after { content: ""; width: 9px; height: 9px; flex: none; margin-left: auto;
  border-right: 2px solid var(--gold-lt); border-bottom: 2px solid var(--gold-lt);
  transform: rotate(45deg); transition: transform .18s; opacity: .85; }
.journey[open] > summary::after { transform: rotate(-135deg); }
.journey > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.journey-head { display: flex; justify-content: space-between; align-items: flex-end;
  gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
/* Closed, the strip is just its own header — the 14px gap under it would leave a dead band. */
.journey:not([open]) > summary { margin-bottom: 0; }
.journey-head strong { font: 600 15px var(--font-head); }
.journey-head .muted { color: #8fa0b8; }
.journey-track { display: flex; align-items: stretch; min-width: 860px; }
.j-step {
  flex: 1; min-width: 86px; text-decoration: none; color: #b3c0d3;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 5px; padding: 8px 4px; border-radius: 10px;
  transition: background .15s ease, color .15s ease;
}
.j-step:hover { background: rgba(221, 187, 99, .10); color: #fff; }
.j-dot {
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--navy-soft); color: #b3c0d3;
  font-size: 12px; font-weight: 700;
  border: 1.5px solid rgba(179, 192, 211, .35);
}
.j-step.mine .j-dot { background: var(--gold); border-color: var(--gold-lt); color: var(--navy-2); }
.j-step.mine { color: #fff; }
.j-step.mine .j-name { color: var(--gold-lt); }
.j-name { font: 700 12.5px var(--font-head); display: flex; gap: 5px; align-items: center; }
.j-count {
  font: 700 10.5px var(--font-head); line-height: 1;
  background: rgba(221, 187, 99, .22); color: var(--gold-lt);
  padding: 3px 6px; border-radius: 999px;
}
.j-hint { font-size: 10.5px; color: #8fa0b8; line-height: 1.35; max-width: 110px; }
.j-step.mine .j-hint { color: #c9d4e4; }
.j-link {
  align-self: center; width: 18px; height: 2px; flex: 0 0 auto;
  background: linear-gradient(90deg, rgba(179,192,211,.35), rgba(221,187,99,.5));
  margin-top: -26px;
}
@media (max-width: 760px) { .journey-track { min-width: 760px; } }


/* ---- KPI card row (360 page, dashboard lenses) ---- */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin: 0 0 18px; }
.cards .card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; box-shadow: 0 4px 14px rgba(11, 18, 32, .04); }
.cards .num { font-family: var(--font-display); font-size: var(--num-md); font-weight: 600; color: var(--ink); letter-spacing: -.02em; }
.cards .num.warn { color: var(--warn); }  /* was #9a3412: did not track dark mode */
.cards .lbl { font-size: 11.5px; color: var(--muted); margin-top: 3px; line-height: 1.35; }
.cards .lbl a { color: inherit; }


/* ============================================================
   Premium layer — hierarchy, not decoration
   ------------------------------------------------------------
   Three rules hold this together:
   1. GOLD MEANS "THIS NEEDS YOU". It is the only accent used on the
      attention rail, and it is used nowhere else on a landing page.
      An accent that appears everywhere signals nothing.
   2. One idea per band. Sections are separated by rhythm and a quiet
      rule, not by boxing every paragraph in its own card.
   3. Dense pages disclose progressively. A 600-line workspace becomes
      one visible section at a time.
   ============================================================ */

/* ---------- page header: quieter eyebrow, more room to breathe ---------- */
.page-head { margin-bottom: 24px; }
.page-head .eyebrow { font: 700 10.5px var(--font-head); letter-spacing: .16em; text-transform: uppercase;
  color: var(--gold-dk); }
.page-head p { max-width: 74ch; }

/* ---------- section band: groups content without boxing it ---------- */
.band { margin: 30px 0 8px; display: flex; align-items: baseline; gap: 12px; }
.band h2 { margin: 0; font-size: 16.5px; }
.band .rule { flex: 1; height: 1px; background: var(--line); }
.band .band-link { font: 600 12.5px var(--font-head); white-space: nowrap; }

/* ---------- the attention rail: "what needs you", the dashboard's thesis ---------- */
.attention {
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--gold) 34%, var(--line));
  border-radius: 14px; overflow: hidden; margin-bottom: 22px;
  box-shadow: 0 1px 2px rgba(16,26,46,.05), 0 10px 30px -18px rgba(16,26,46,.35);
}
.attention > header {
  display: flex; align-items: center; gap: 10px; padding: 14px 20px;
  background: linear-gradient(100deg, color-mix(in srgb, var(--gold) 14%, var(--panel)), var(--panel) 70%);
  border-bottom: 1px solid color-mix(in srgb, var(--gold) 22%, var(--line));
}
.attention > header h2 { margin: 0; font-size: 15px; }
.attention > header .count {
  font: 700 11px var(--font-head); color: var(--navy-2);
  background: linear-gradient(145deg, var(--gold-lt), var(--gold-dk));
  border-radius: 999px; padding: 3px 10px; letter-spacing: .02em;
}
.attention .rail { display: flex; flex-direction: column; }
.attention .item {
  display: flex; align-items: center; gap: 14px; padding: 13px 20px;
  border-top: 1px solid var(--line-soft, var(--line)); text-decoration: none; color: inherit;
  transition: background .12s ease;
}
.attention .item:first-child { border-top: 0; }
.attention .item:hover { background: var(--bg-2); text-decoration: none; }
.attention .item .what { flex: 1; min-width: 0; }
.attention .item .what b { display: block; font: 600 14px var(--font-head); color: var(--ink); }
.attention .item .what span { font-size: 12.5px; color: var(--muted); }
.attention .item .go { font: 600 12.5px var(--font-head); color: var(--brand-2); white-space: nowrap; }
.attention .item:hover .go { text-decoration: underline; }
/* severity dot — state carried in form as well as colour */
.attention .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--muted); }
.attention .dot.bad { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 18%, transparent); }
.attention .dot.warn { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 18%, transparent); }
.attention .dot.ok { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); }
.attention .clear { padding: 22px 20px; display: flex; align-items: center; gap: 12px; color: var(--muted); }
.attention .clear svg { width: 20px; height: 20px; color: var(--ok); flex: none; }

/* ---------- "where things live" directory: orientation after time away ---------- */
.wayfind { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: 12px; margin-bottom: 8px; }
.wayfind a {
  display: block; padding: 15px 17px; border-radius: 12px; text-decoration: none;
  background: var(--panel); border: 1px solid var(--line); box-shadow: var(--shadow);
  transition: border-color .14s ease, transform .14s ease, box-shadow .14s ease;
}
.wayfind a:hover { text-decoration: none; border-color: color-mix(in srgb, var(--gold) 45%, var(--line));
  transform: translateY(-1px); box-shadow: 0 4px 18px -6px rgba(16,26,46,.18); }
.wayfind a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.wayfind .t { display: flex; align-items: center; gap: 9px; font: 600 14px var(--font-head); color: var(--ink); }
.wayfind .t svg { width: 16px; height: 16px; color: var(--gold-dk); flex: none; }
.wayfind .d { font-size: 12.5px; color: var(--muted); margin-top: 5px; line-height: 1.45; }

/* ---------- section tabs: progressive disclosure for dense workspaces ---------- */
.sectabs { display: flex; gap: 4px; flex-wrap: wrap; margin: 0 0 18px;
  border-bottom: 1px solid var(--line); padding-bottom: 0; }
.sectabs button {
  background: none; border: 0; border-bottom: 2px solid transparent; cursor: pointer;
  font: 600 13.5px var(--font-head); color: var(--muted); padding: 9px 14px 10px;
  transition: color .13s ease, border-color .13s ease;
}
.sectabs button:hover { color: var(--ink); }
.sectabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--gold); }
.sectabs button:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; border-radius: 6px 6px 0 0; }
.sectabs button .chip { margin-left: 6px; }
/* A second level of tabs, inside one section — see _pageindex.html. Sizing only:
   the state chip on a closed tab reuses .badge and its existing ok/warn/bad
   tokens, so no colour is introduced here. */
.sectabs.subtabs { margin: 0 0 14px; }
.sectabs button .badge { margin-left: 7px; font-size: 10.5px; padding: 2px 7px;
  font-weight: 600; vertical-align: 1px; }
.sectabs button .badge::before { width: 5px; height: 5px; }
.secpanel[hidden] { display: none; }

/* ---------- settings: grouped, navigable ---------- */
.set-nav { position: sticky; top: 74px; align-self: start; display: flex; flex-direction: column; gap: 2px; }
.set-nav a { display: block; padding: 8px 12px; border-radius: 8px; font: 500 13.5px var(--font-head);
  color: var(--ink-soft); text-decoration: none; border-left: 2px solid transparent; }
.set-nav a:hover { background: var(--bg-2); color: var(--ink); text-decoration: none; }
.set-nav a.on { border-left-color: var(--gold); color: var(--ink); background: var(--bg-2); }
.set-grid { display: grid; grid-template-columns: 200px 1fr; gap: 26px; align-items: start; }
@media (max-width: 880px) { .set-grid { grid-template-columns: 1fr; } .set-nav { position: static; flex-direction: row; flex-wrap: wrap; } }
.set-card { scroll-margin-top: 80px; }
.set-card > h2:first-child { margin-top: 0; }
.set-card .hint { color: var(--muted); font-size: 13px; margin: 0 0 14px; max-width: 72ch; }

/* ---------- refinements ---------- */
.panel { transition: box-shadow .16s ease; }
.btn:focus-visible, .btn.small:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 4px; }
th { position: relative; }
tbody tr { transition: background .1s ease; }
/* numbers in tables line up */
td { font-variant-numeric: tabular-nums; }
td strong, td b { font-variant-numeric: normal; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  .wayfind a:hover { transform: none; }
}


/* ---------- section index: orientation on long working pages ---------- */
.pageindex { position: sticky; top: 70px; z-index: 15; display: flex; gap: 3px; flex-wrap: wrap;
  background: color-mix(in srgb, var(--panel) 92%, transparent); backdrop-filter: blur(8px);
  border: 1px solid var(--line); border-radius: 10px; padding: 6px; margin: 0 0 18px;
  box-shadow: 0 4px 14px rgba(11,18,32,.05); }
.pageindex a { padding: 6px 11px; border-radius: 7px; font: 600 12.5px var(--font-head);
  color: var(--ink-soft); text-decoration: none; white-space: nowrap; transition: background .12s, color .12s; }
.pageindex a:hover { background: var(--bg-2); color: var(--ink); text-decoration: none; }
.pageindex a.on { background: rgba(201,162,75,.15); color: var(--ink); }
/* GROUP TABS. Five groups, not twenty-five sections: this fits one row at every width,
   which the flat list did not — it wrapped to four rows below 1660px, where the left-rail
   treatment does not apply. Bigger targets and a gold underline on the active one, so it
   reads as primary navigation for the page rather than as a table of contents. */
.pageindex-groups { gap: 2px; padding: 4px; }
.pageindex-groups a { padding: 9px 16px; font-size: 13.5px; border-radius: var(--radius-sm);
  border-bottom: 2px solid transparent; }
.pageindex-groups a.on { background: rgba(201,162,75,.13); color: var(--ink);
  border-bottom-color: var(--gold); }
/* The rail treatment above turns the flat index into a left column on very wide screens.
   Group tabs are already one short row, so they stay a row and keep the content full width. */
@media (min-width: 1660px) {
  #app:not(.guide-open) [data-index]:has(> .pageindex-groups) { display: block; }
  #app:not(.guide-open) [data-index] > .pageindex-groups { position: sticky; flex-direction: row;
    flex-wrap: wrap; margin: 0 0 18px; max-height: none; }
  #app:not(.guide-open) [data-index] > .pageindex-groups a { white-space: nowrap;
    border-left: 0; border-radius: var(--radius-sm); }
  #app:not(.guide-open) [data-index] > .pageindex-groups a.on { border-left-color: transparent;
    border-bottom-color: var(--gold); }
  #app:not(.guide-open) main:has([data-index] > .pageindex-groups) { max-width: 1180px; }
}
/* ---- live password checklist (ui.js data-pw-check) ----------------------------
   Three states on purpose. Before anything is typed every row is NEUTRAL — a wall of red
   in front of somebody who has not started is a telling-off, not help. Typing turns each
   row green as it is satisfied and grey-with-a-dash while it is not; only the met state
   uses colour, so the eye tracks progress rather than scanning failures. */
.pw-check { list-style: none; margin: 8px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 2px 16px; }
.pw-check li { display: flex; align-items: center; gap: 8px;
  font: 500 12.5px var(--font-head); color: var(--muted); padding: 3px 0; }
.pw-check .pw-mark { width: 15px; height: 15px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--line-strong); position: relative; transition: border-color .15s, background .15s; }
/* the dash: not yet met, and the box is not empty */
.pw-check li.unmet .pw-mark::after { content: ""; position: absolute; left: 3px; right: 3px;
  top: 50%; height: 1.5px; background: var(--muted); transform: translateY(-50%); opacity: .55; }
.pw-check li.met { color: var(--ok); }
.pw-check li.met .pw-mark { border-color: var(--ok); background: var(--ok); }
/* the tick, drawn rather than a glyph so it inherits size and needs no font */
.pw-check li.met .pw-mark::after { content: ""; position: absolute; left: 4px; top: 1.5px;
  width: 4px; height: 8px; border-right: 1.7px solid var(--panel);
  border-bottom: 1.7px solid var(--panel); transform: rotate(40deg); }
.pw-check.all-met li.met { color: var(--ok); }
[data-index] h2[id] { scroll-margin-top: 130px; }
@media (max-width: 700px) { .pageindex { position: static; } }

/* ---- section index as a LEFT RAIL on wide screens ------------------------------
   On a long settings or detail page the horizontal bar costs three stacked rows of
   chrome above every section and still needs scanning left-to-right. Down the side
   it reads as a table of contents, the labels line up, and it takes no vertical
   room at all.

   Why a grid rather than a float or a fixed panel: the nav is inserted as a SIBLING
   of the page's panels (see _pageindex.html — it builds itself from the h2 ids and
   knows nothing about the page's structure). Spanning it `grid-row: 1 / -1` in
   column one, and pushing every other child into column two, keeps that ignorance
   intact — a page can restructure its panels freely and this still holds.

   It reverts to the top bar when the guidance panel is open, because a 210px rail
   plus a 420px panel plus the app sidebar leaves the actual work too little room.
   Two competing side columns is worse than one bar. */
/* 1660px is not a round number, it is the arithmetic: the 244px sidebar, main at its
   1180px max-width, and 234px for the rail and its gap. Below it there is no dead space
   to claim, so the rail would come out of the content instead — at 1366 that squeezed the
   users table 364px past the window edge. Above it the content column keeps exactly the
   width it has always had and the rail occupies gutter that was empty. */
@media (min-width: 1660px) {
  #app:not(.guide-open) [data-index] {
    display: grid;
    grid-template-columns: 210px minmax(0, 1fr);
    column-gap: 24px;
    align-items: start;
  }
  #app:not(.guide-open) [data-index] > .pageindex {
    grid-column: 1;
    grid-row: 1 / -1;          /* span the whole page, so sticky has room to travel */
    position: sticky;
    top: 84px;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 1px;
    margin: 0;
    max-height: calc(100vh - 104px);
    overflow-y: auto;
  }
  #app:not(.guide-open) [data-index] > .pageindex a {
    white-space: normal;        /* labels are long; let them wrap rather than clip */
    line-height: 1.35;
    padding: 7px 10px;
    border-left: 2px solid transparent;
    border-radius: 0 6px 6px 0;
  }
  #app:not(.guide-open) [data-index] > .pageindex a.on {
    border-left-color: var(--gold);
  }
  /* Everything that is not the nav belongs in column two, in document order.
     min-width:0 because a grid item defaults to min-width:auto and will refuse to
     shrink below its content — a wide table then pushes the whole page sideways
     rather than scrolling inside its own container. */
  #app:not(.guide-open) [data-index] > *:not(.pageindex) { grid-column: 2; min-width: 0; }
  /* Claim the gutter rather than squeezing the work. main is a centred column with a
     max-width, so on a wide screen there is dead space either side of it; the rail was
     taking its 210px out of the CONTENT instead of out of that. Widen main by exactly
     the rail plus its gap, and the content column ends up the width it always was.
     :has() keeps this to pages that actually have an index. */
  #app:not(.guide-open) main:has([data-index]) { max-width: 1414px; }
  #app:not(.guide-open) [data-index] h2[id] { scroll-margin-top: 92px; }
}

/* ---------- sortable table headings ----------
   The affordance stays quiet until you approach it: a faint neutral chevron on
   hover, a solid gold one once the column is actually sorting. A table full of
   permanently-loud sort arrows reads as clutter, which is what this whole pass
   is trying to remove. */
th.sortable { cursor: pointer; user-select: none; padding-right: 22px; }
th.sortable:hover { color: var(--ink); }
th.sortable:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
th.sortable::after {
  content: ""; position: absolute; right: 8px; top: 50%; width: 8px; height: 8px;
  margin-top: -4px; opacity: 0; transition: opacity .12s ease;
  background: currentColor; clip-path: polygon(50% 0, 100% 70%, 0 70%);
}
th.sortable:hover::after { opacity: .35; }
th.sortable[aria-sort="ascending"]::after { opacity: 1; color: var(--gold-dk); }
th.sortable[aria-sort="descending"]::after { opacity: 1; color: var(--gold-dk);
  clip-path: polygon(0 30%, 100% 30%, 50% 100%); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="green"]) th.sortable[aria-sort="ascending"]::after,
  :root:not([data-theme="light"]):not([data-theme="green"]) th.sortable[aria-sort="descending"]::after { color: var(--gold-lt); }
}
:root[data-theme="dark"] th.sortable[aria-sort="ascending"]::after,
:root[data-theme="dark"] th.sortable[aria-sort="descending"]::after { color: var(--gold-lt); }

/* ---------- filter bar: consistent control row above a table ---------- */
.filterbar { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;
  margin: 0 0 14px; }
.filterbar > div { min-width: 150px; }
.filterbar label { margin-top: 0; }
.filterbar .grow { flex: 1; min-width: 200px; }

/* ---------- table scroll containers ----------
   A wide table has to scroll inside its own box, otherwise it drags the whole
   page sideways and every other column on the screen goes off with it. The
   wrapper is inserted by _tablesort.html around every table; the table keeps
   width:100% so narrow tables still fill the panel and only genuinely wide
   ones scroll. The bottom margin moves to the wrapper so spacing is unchanged
   either way. */
.tablescroll { overflow-x: auto; -webkit-overflow-scrolling: touch;
  margin-bottom: 18px; }
.tablescroll > table { margin-bottom: 0; }

/* ---------- page purpose: what this screen is for ---------- */
.purpose { display: flex; gap: 10px; align-items: flex-start;
  background: var(--accent); border-radius: 10px; padding: 11px 14px;
  margin: 0 0 18px; font-size: 13.5px; color: var(--ink-soft); max-width: 90ch; }
.purpose svg { width: 15px; height: 15px; flex: none; margin-top: 2px; color: var(--brand-2); }
.purpose b { color: var(--ink); font-family: var(--font-head); font-weight: 600; }

/* ---------- form field hints ----------
   A short line under the control saying what it does, instead of a wall of
   prose above the whole section. The explanation belongs next to the decision.

   The measure is 76ch, matching .panel .hint below. It was 46ch, which suits the
   one-liner this started as ("days", "Applied to new purchase orders") but not
   what several of them grew into: Administration now carries eight-line
   explanations of segregation of duties and split-purchase detection, and at 46ch
   those render as a tall narrow ribbon down the left of an otherwise empty panel,
   turning the page into a long scroll. Widening the measure is the whole fix —
   the same words in 39% fewer lines. Do not push it much past this: beyond about
   75 characters the eye starts losing its place on the return sweep. */
.fh { display: block; font-size: 11.5px; line-height: 1.45; color: var(--muted);
  margin: 4px 0 2px; max-width: 76ch; }
.panel .hint { color: var(--ink-soft); font-size: 13.5px; margin: 0 0 14px; max-width: 76ch; }
.panel h2 + .hint { margin-top: -4px; }
#wtotal { font-weight: 600; }
#wtotal.bad { color: var(--bad); }


/* ---- Contextual guide panel -------------------------------------------------
   Sits beside the work rather than replacing it. The main region shrinks instead
   of being covered, so a user can read the step and do it at the same time —
   which is the whole point, and why this is not a modal. */
.guide-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; border: 1px solid var(--line-strong);
  color: var(--ink); border-radius: 8px; padding: 6px 11px;
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  margin-right: 12px;
}
.guide-btn:hover { border-color: var(--gold); color: var(--gold); }
/* The film control is the Guide button's twin: same height, same border, same hover — but
   square, so it reads as an icon rather than a second labelled action competing with it. Gold
   from rest, because it is the one control on the bar that offers something to watch. */
.film-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin-right: 12px;
  background: transparent; border: 1px solid var(--line-strong);
  color: var(--gold-dk); border-radius: 8px; cursor: pointer;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.film-btn:hover { border-color: var(--gold); color: var(--gold);
  background: color-mix(in srgb, var(--gold) 9%, transparent); }
.film-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.guide-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 420px; max-width: 92vw;
  background: var(--panel); border-left: 1px solid var(--line-strong);
  box-shadow: var(--shadow-lg); z-index: 40;
  transform: translateX(100%); transition: transform .18s ease;
  display: flex; flex-direction: column;
}
.guide-panel.open { transform: translateX(0); }
.gp-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.gp-head button {
  background: none; border: 0; font-size: 22px; line-height: 1;
  color: var(--muted); cursor: pointer; padding: 0 4px;
}
.gp-head button:hover { color: var(--ink); }
.gp-body { padding: 16px; overflow-y: auto; flex: 1 1 auto; font-size: 13.5px; }
.gp-step { padding: 0 0 14px; margin-bottom: 14px; border-bottom: 1px solid var(--line); }
.gp-step:last-of-type { border-bottom: 0; }
.gp-step h4 { margin: 0 0 6px; font-size: 13.5px; color: var(--brand); }
.gp-step p { margin: 0 0 6px; }
.gp-step ul { margin: 4px 0 0; padding-left: 18px; }
.gp-step li { margin-bottom: 4px; }
.gp-note {
  border-left: 3px solid var(--gold); padding: 6px 10px; margin: 8px 0;
  background: rgba(201,162,75,.07); border-radius: 0 6px 6px 0;
}
/* Give the page back its room when the panel is open, on screens wide enough to
   share. Below that the panel overlays, because 400px of a phone is the page. */
@media (min-width: 1100px) {
  #app.guide-open main { padding-right: 444px; }
}


/* ---- one section at a time means one column ----------------------------------
   settings.html wraps its top half in a .grid2 so the form and the user list sat
   side by side on a long scrolling page. With data-index="sections" only one of
   them is ever on screen, so the second column is dead space and every section
   should have the width.

   The child combinator is load-bearing: the same .grid2 class is used INSIDE the
   settings form for field pairs, and those must stay two columns. Only the one
   that is a direct child of the sectioned host is collapsed. */
[data-index="sections"] > .grid2 { display: block; }
/* The hidden ATTRIBUTE must beat any display we set, or an element the section
   switcher has hidden keeps its box and its space. The rule above is exactly that
   collision: display:block on a .grid2 outranks the browser's [hidden]{display:none},
   so an emptied container went on occupying the page and pushed every later section
   below the fold. Global and !important on purpose — this is a correctness rule, not
   styling, and it protects anything else that hides by attribute. */
[hidden] { display: none !important; }

/* ---- lookup type-ahead (commodity codes, and anything else that searches) ---- */
.lookup-hits { position: absolute; z-index: 20; left: 0; right: 0; margin-top: 4px;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 9px;
  box-shadow: var(--shadow-lg); max-height: 300px; overflow-y: auto; }
.lookup-hit { padding: 8px 11px; cursor: pointer; border-bottom: 1px solid var(--line); }
.lookup-hit:last-child { border-bottom: 0; }
.lookup-hit:hover { background: var(--accent); }
.lookup-none { padding: 8px 11px; font-size: 12.5px; color: var(--muted); }


/* ---- Skip to content -------------------------------------------------------------
   WCAG 2.4.1 Bypass Blocks (Level A). AODA obliges Ontario public bodies to WCAG 2.0 AA,
   so this is a legal requirement for a large part of the target market, not a nicety.

   Positioned off-screen rather than display:none — a hidden element is not focusable, and
   a skip link that cannot be focused is the same as not having one. It becomes visible on
   :focus, which is the only time it is useful. */
.skip-link{position:absolute;left:-9999px;top:0;z-index:9999;
  padding:10px 16px;background:var(--gold-dk);color:#fff;
  border-radius:0 0 6px 0;text-decoration:none;font-weight:600}
.skip-link:focus{left:0}
/* The target takes focus programmatically, so it must not draw a permanent outline. */
main:focus{outline:none}

/* ---- the customer's own logo, top right --------------------------------------------
   Separated from Atlas's controls by a rule so the two brands do not merge into one row
   of icons. Height-constrained rather than width-constrained: logos arrive in wildly
   different proportions and a wide lockup must shrink to fit the bar's height, not push
   the bar taller. max-width stops an extremely wide one from crowding the account block.
   No filter, no recolouring: it is somebody else's brand and the product has no business
   restyling it -- which is also why the header is the right home for it, being a light
   surface in two themes out of three and a dark one only in the dark theme, where a mark
   with its own background still reads. */
.tenant-logo {
  display: flex; align-items: center; flex: none;
  margin-left: 6px; padding-left: 18px; border-left: 1px solid var(--line);
}
.tenant-logo img { height: 30px; width: auto; max-width: 190px; display: block;
  object-fit: contain; }
@media (max-width: 1100px) { .tenant-logo { display: none; } }

/* ---- the customer's identity on the sign-in card ------------------------------------
   Centred above the form with a rule beneath, so it reads as the card's heading rather
   than as a picture dropped on top of one. BOTH the logo and the name here, unlike the
   in-app header, because the sign-in page has no sidebar carrying the name and a logo
   alone leaves somebody on the wrong workspace with only a picture to go on.
   The logo is capped on both axes and never stretched: it is somebody else's brand and
   arrives in whatever proportions they use. */
.tenant-id { text-align: center; padding-bottom: 16px; margin-bottom: 18px;
  border-bottom: 1px solid var(--line); }
.tenant-id img { max-height: 46px; max-width: 220px; width: auto; height: auto;
  display: block; margin: 0 auto 10px; object-fit: contain; }
.tenant-id-name { font-family: var(--font-head); font-weight: 600; font-size: 1.02rem;
  color: var(--ink); letter-spacing: .01em; }

/* ---- procurement route map -------------------------------------------------------------
   The flowchart page. Every colour inside the SVG comes from the tokens above, so the
   diagrams follow the reader's theme; nothing here introduces a value of its own. */
.rm-controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.rm-lab{font-size:.68rem;letter-spacing:.13em;text-transform:uppercase;color:var(--muted);
  font-weight:600}
.rm-seg{font-size:.84rem;color:var(--muted);border:1px solid var(--line);
  background:var(--panel);border-radius:999px;padding:6px 15px;cursor:pointer;
  transition:color .15s,border-color .15s,background .15s}
.rm-seg:hover{color:var(--ink);border-color:var(--gold)}
.rm-seg[aria-pressed="true"]{color:var(--gold-dk);border-color:var(--gold);
  background:var(--badge-bg)}
.rm-head{display:flex;flex-wrap:wrap;gap:8px 14px;align-items:baseline;margin-bottom:10px}
.rm-rule{color:var(--muted);font-size:.86rem;margin:0;flex:1 1 380px;line-height:1.45}
/* BOTH AXES. A flowchart that fans to eight routes is wider than a column and longer than a
   screen; clipping either one loses the argument. Resizable, because how much of it you want
   on screen at once depends on what you are checking. */
.rm-scroll{overflow:auto;max-height:78vh;resize:vertical;border:1px solid var(--line);
  border-radius:10px;padding:8px;background:var(--bg)}
.rm-scroll svg{display:block;height:auto}
.rm-scroll:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The zoom dial. The chart is drawn at a fixed coordinate size, so fitting it to a column
   shrinks its labels below reading size; these give the reader the size back. */
.rm-zoom{display:inline-flex;align-items:center;gap:4px;margin-left:auto;flex:none}
.rm-zbtn{font-family:inherit;font-size:.8rem;line-height:1;color:var(--muted);
  border:1px solid var(--line);background:var(--panel);border-radius:7px;
  padding:6px 9px;cursor:pointer;transition:color .15s,border-color .15s}
.rm-zbtn:hover:not(:disabled){color:var(--ink);border-color:var(--gold)}
.rm-zbtn:disabled{opacity:.4;cursor:default}
.rm-zoom-pct{font-size:.78rem;color:var(--muted);min-width:44px;text-align:center;
  font-variant-numeric:tabular-nums}
.rm-legend{display:flex;flex-wrap:wrap;gap:10px 20px;align-items:center;margin:6px 0 12px}
.rm-key{display:inline-flex;align-items:center;gap:8px;font-size:.83rem;color:var(--muted)}
.rm-key svg{overflow:visible;flex:none}
.rm-board .nl{font-size:12px;font-weight:500;text-anchor:middle}
.rm-board .el{font-size:10.5px;fill:var(--muted);text-anchor:middle}
.rm-board .br{font-size:11px;font-weight:600;fill:var(--gold-dk);text-anchor:middle}
.rm-board .nd{stroke-width:1.5}
.rm-board .c,.rm-board .c-yes,.rm-board .c-dash,.rm-board .c-back,
.rm-board .c-stop,.rm-board .c-warn{fill:none;stroke:var(--muted);stroke-width:1.6}
.rm-board .c-dash{stroke-dasharray:5 4;stroke:var(--line-strong)}
.rm-board .c-back{stroke-dasharray:3 3;stroke:var(--line-strong)}
.rm-board .c-stop{stroke:var(--bad)}
.rm-board .c-warn{stroke:var(--warn)}
.rm-board .c-yes{stroke:var(--gold-dk)}

/* Symbol kinds. The SVG names a class and nothing else — no fill attribute, no var() inside
   a presentation attribute — so the theme decides every colour and it resolves the same way
   in every browser. Shape carries the meaning; colour stays deliberately quiet. */
.rm-k-term   {fill:var(--badge-bg); stroke:var(--gold)}
.rm-k-proc   {fill:var(--panel);    stroke:var(--line-strong)}
.rm-k-dec    {fill:var(--badge-bg); stroke:var(--gold)}
.rm-k-sys    {fill:var(--accent);   stroke:var(--line-strong)}
.rm-k-prep   {fill:var(--panel);    stroke:var(--gold)}
.rm-k-doc    {fill:var(--panel);    stroke:var(--line-strong)}
.rm-k-wait   {fill:var(--accent);   stroke:var(--gold-dk)}
.rm-k-manual {fill:var(--panel);    stroke:var(--muted)}
.rm-k-data   {fill:var(--accent);   stroke:var(--line-strong)}
.rm-k-stop   {fill:var(--bad-bg);   stroke:var(--bad)}
.rm-k-gap    {fill:url(#hatch);     stroke:var(--warn)}
.rm-k-term-t,.rm-k-proc-t,.rm-k-dec-t,.rm-k-sys-t,.rm-k-prep-t,
.rm-k-doc-t,.rm-k-wait-t,.rm-k-manual-t{fill:var(--ink)}
.rm-k-data-t{fill:var(--ink-soft)}
.rm-k-stop-t{fill:var(--bad)}
.rm-k-gap-t{fill:var(--warn)}
.rm-rule{fill:none}
.rm-arrow{fill:var(--muted)}
.rm-arrow-bad{fill:var(--bad)}
.rm-arrow-yes{fill:var(--gold-dk)}
.rm-arrow-warn{fill:var(--warn)}
.rm-hatch-bg{fill:var(--warn-bg)}
.rm-hatch-line{stroke:var(--warn);stroke-width:1.6;opacity:.45}
/* The selectors sit on one line. The app's form styling makes a select fill its row, which
   is right in a form and wrong in a toolbar. */
.rm-controls{gap:10px}
.rm-controls select{width:auto;min-width:190px;margin:0}
.rm-controls .badge{margin-left:4px}
