/* HPLedger design tokens.
 * UI v3 (2026-07-09): teal accent #0E7D6B + Hanken Grotesk everywhere.
 * Surface ramp inherited unchanged from v2.3.
 */

/* Hanken Grotesk — self-hosted variable font (400–700). No Google Fonts CDN:
   the desktop app runs offline and ships through the MS Store. */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/hanken-grotesk-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Newsreader";
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/newsreader-italic-500.ttf") format("truetype");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/hanken-grotesk-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  background-color: var(--hpl-bg);
  color-scheme: light;

  --hpl-default-theme: light;
  --hpl-default-palette: graphite;
  /* Accent = deep teal #0E7D6B (UI v3, operator-confirmed final). The indigo
     trial (hue 272) was rejected — one teal accent for the whole app.
     oklch(0.529 0.0946 177.6) round-trips to exactly #0E7D6B; the earlier
     168/0.18 pair rendered #007A49 (too green). */
  --hpl-accent-hue: 177.6;
  --hpl-accent-chroma: 0.0946;

  /* UI v3: Hanken Grotesk everywhere (self-hosted @font-face above). The
     former UI/mono faces are banned ("AI-default"); numbers use the same
     face with font-variant-numeric: tabular-nums, never a mono typeface.
     --hpl-font-mono is kept as an alias so legacy rules degrade gracefully. */
  --hpl-font-ui: "Hanken Grotesk", -apple-system, system-ui, "Segoe UI", sans-serif;
  --hpl-font-display: "Hanken Grotesk", -apple-system, system-ui, "Segoe UI", sans-serif;
  --hpl-font-mono: "Hanken Grotesk", -apple-system, system-ui, "Segoe UI", sans-serif;

  --hpl-num-features: "tnum" 1, "lnum" 1, "cv11" 1;
  --hpl-num-letter: 0;
  --hpl-num-hero-letter: 0;

  /* Default :root canvas = LIGHT (operator decision 2026-06-29: HPLedger
     default theme flipped dark→light). The dark palette lives in
     [data-theme="dark"] below; ui-settings.js validTheme() now falls back to
     "light", and these values keep the pre-Alpine first paint FOUC-free. */
  /* Surface layering (UI refresh v2.3): strict 4-layer ramp — adjacent
     layers must always be visually distinct; inputs are always white with a
     strong border. L0 --hpl-bg (app frame) · L1 --hpl-bg-content (page
     content behind cards) · L2 --hpl-panel (cards/work surfaces) ·
     L2.5 --hpl-panel-tool (in-card tool zones) · L3 --hpl-input-bg. */
  /* Values machine-extracted from the v3 mock set (all 23 series,
     2026-07-10 census) — the ramp is NEUTRAL white-grey, not warm paper.
     #EEEEEE frame / #FBFBFB content / #fff panels. */
  /* Final interactive prototype (2026-07-19): app frame and page work
     surfaces share one neutral canvas; white is reserved for cards/controls. */
  --hpl-bg: #F0F0F0;
  --hpl-bg-alt: #F0F0F0;
  --hpl-bg-content: #F0F0F0;
  --hpl-panel: #FFFFFF;
  --hpl-panel-alt: #FAFAFA;
  --hpl-panel-mute: #F2F2F2;
  --hpl-panel-deep: #E9E9E9;
  --hpl-panel-tool: #F4F4F4;
  /* Section header bars inside cards (mock: "PERIOD & BASIS" etc. — grey
     band + hairline, uppercase 650 11.5px; NOT a warm tint). */
  --hpl-section-bar: #D9D9D9;
  --hpl-section-bar-line: #D8D8D8;
  --hpl-ink: #0E0E12;
  --hpl-ink-soft: #26262C;
  /* #565D66 is the most-used text colour in the mock set (1891 hits) —
     body/secondary text. The old #6A6A70 was a README-era guess. */
  --hpl-muted: #565D66;
  /* M6 accessibility: these tokens are used for real 10–12px copy across
     the application, not decorative chrome. Keep them above 4.5:1 on both
     the white card and neutral page canvases. */
  --hpl-muted-soft: #686D75;
  --hpl-eyebrow: #686D75;
  --hpl-disabled-ink: #B4B4B4;
  --hpl-line: #E0E0E0;
  --hpl-line-soft: #EEEEEE;
  --hpl-line-strong: #CFCFCF;
  --hpl-line-strong-hover: #BABABA;
  --hpl-line-interactive: #8F8F8F;
  --hpl-chip-line: #D5D5D5;
  --hpl-surface: #E0E0E0;
  --hpl-surface-hover: #CECECE;
  --hpl-overlay: rgba(14, 14, 18, 0.46);
  --hpl-focus-ring: var(--hpl-accent-soft);
  --hpl-input-bg: #FFFFFF;
  --hpl-input-bg-hover: #FFFFFF;
  --hpl-input-border: #8F8F8F;
  --hpl-input-border-hover: #6F6F6F;
  --hpl-input-focus-shadow: 0 0 0 2px var(--hpl-accent-soft), inset 0 -2px 0 var(--hpl-accent);
  --hpl-input-placeholder: #686868;
  --hpl-control-disabled-bg: #E9E9E9;
  --hpl-motion-page: 200ms;
  --hpl-motion-page-easing: cubic-bezier(.16, .84, .44, 1);
  --hpl-motion-reveal: 560ms;
  --hpl-motion-reveal-easing: cubic-bezier(.2, .7, .2, 1);
  --hpl-motion-stagger-step: 50ms;
  --hpl-motion-morph: 260ms;
  --line-strong-hover-dark: #3E3E48;
  --line-strong-hover-light: #BABABA;
  --motion-page: 260ms;
  --motion-page-easing: cubic-bezier(.2, .7, .2, 1);
  --motion-reveal: 560ms;
  --motion-reveal-easing: cubic-bezier(.2, .7, .2, 1);
  --motion-stagger-step: 50ms;
  --motion-morph: 260ms;
  --focus-ring: var(--hpl-focus-ring);
  /* Semantic colour contract (Batch 3): status meaning is independent from
     the selectable accent.  Each family supplies text, quiet fill and line;
     amount direction has separate names even where the initial values match. */
  --hpl-info-ink: #245B78;
  --hpl-info-bg: #EAF4FA;
  --hpl-info-line: #B9D8E8;
  --hpl-success-ink: #256447;
  --hpl-success-bg: #EAF5EE;
  --hpl-success-line: #B9D8C7;
  --hpl-warning-ink: #7A4B00;
  --hpl-warning-bg: #FFF4D6;
  --hpl-warning-line: #E7C46A;
  --hpl-danger-ink: #A52A1A;
  --hpl-danger-bg: #FDECEA;
  --hpl-danger-line: #E7B8B1;
  --hpl-positive-ink: var(--hpl-success-ink);
  --hpl-positive-bg: var(--hpl-success-bg);
  --hpl-positive-line: var(--hpl-success-line);
  --hpl-negative-ink: var(--hpl-danger-ink);
  --hpl-negative-bg: var(--hpl-danger-bg);
  --hpl-negative-line: var(--hpl-danger-line);

  /* Compatibility aliases let existing modules adopt the contract without a
     second parallel theme vocabulary. */
  --hpl-up: var(--hpl-positive-ink);
  --hpl-up-soft: var(--hpl-positive-bg);
  --hpl-down: var(--hpl-negative-ink);
  --hpl-down-soft: var(--hpl-negative-bg);
  --hpl-warn: var(--hpl-warning-ink);
  --hpl-warn-soft: var(--hpl-warning-bg);
  --hpl-warn-line: var(--hpl-warning-line);
  --hpl-info: var(--hpl-info-ink);
  --hpl-info-soft: var(--hpl-info-bg);
  --hpl-success: var(--hpl-success-ink);
  --hpl-success-soft: var(--hpl-success-bg);
  --hpl-warning: var(--hpl-warning-ink);
  --hpl-warning-soft: var(--hpl-warning-bg);
  --hpl-danger: var(--hpl-danger-ink);
  --hpl-danger-soft: var(--hpl-danger-bg);
  --hpl-error: var(--hpl-danger-ink);
  --hpl-error-soft: var(--hpl-danger-bg);
  --hpl-error-line: var(--hpl-danger-line);
  --hpl-accent: oklch(0.51 var(--hpl-accent-chroma) var(--hpl-accent-hue)); /* L 0.51, not 0.529: accent is used AS TEXT on the #F0F0F0 app bg (breadcrumbs, selected tabs, badges, counts) and 0.529 sat at 4.42:1 -- under AA. 0.51 clears 4.5 on every light palette (2026-08-24 UI review P3; guard: tests/test_accent_text_contrast_guard.py). ui-settings.js accentColorFor mirrors this number. */
  --hpl-accent-hover: oklch(0.465 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-accent-dim: oklch(0.7 calc(var(--hpl-accent-chroma) * 0.5) var(--hpl-accent-hue));
  /* accent-soft = focus ring wash ONLY — never a chip or row fill (UI v3). */
  --hpl-accent-soft: oklch(0.955 0.03 var(--hpl-accent-hue));
  /* Secondary/outline button border on accent actions; also the duotone
     done-chip border family (#AECFC7). */
  --hpl-accent-outline: #C2DAD4;
  --hpl-chip-done-line: #AECFC7;
  --hpl-accent-text: #FFFFFF;
  --hpl-accent-active: oklch(0.405 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-on-brand: #0A0A0D;
  --hpl-on-primary: #FFFFFF;
  --hpl-on-accent: #FFFFFF;
  --hpl-on-danger: #FFFFFF;

  /* ── Accent role split (B3, operator ruling 2026-08-26) ──────────────
     Highlights keep ONE brand identity in both themes. --hpl-accent stays
     the TEXT/ICON accent (dark lifts it to L0.72 so it reads on dark
     canvases); anything that PAINTS the accent as a surface uses these:

       fill   — buttons, selected options, discs, toggle tracks: brand
                green with white type. Theme-INVARIANT by design (the dark
                mock value is the light value), so it lives in :root only —
                do not override it in [data-theme="dark"].
       chrome — the shell topbar / sidebar brand band: light = accent
                itself, dark = a deeper green (white type at ~10:1) so the
                bar reads as chrome rather than a control.
       chrome-soft — type on chrome one identity quieter than white: white
                in light, pale mint in dark (active nav label per the mock).

     Everything derives from the hue/chroma knobs — never hard-code a hue,
     or the sage/ink/marine palettes stop working. Guard (both halves):
     tests/test_accent_text_contrast_guard.py. */
  --hpl-accent-fill: oklch(0.51 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-accent-fill-hover: oklch(0.465 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-accent-fill-active: oklch(0.405 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-on-accent-fill: #FFFFFF;
  --hpl-accent-chrome: var(--hpl-accent);
  --hpl-on-accent-chrome: var(--hpl-on-accent);
  --hpl-on-accent-chrome-soft: var(--hpl-on-accent);
  /* Muted type on chrome: 93%, not the old 76% — 76% white on the washed
     pill sat at 3.17:1 (sim 2026-08-26); 93% clears AA in both themes. */
  --hpl-on-accent-muted: color-mix(in srgb, var(--hpl-on-accent-chrome) 93%, transparent);
  /* Topbar pill/search washes are SHADES. The old on-accent mixes flipped
     direction with the theme — lightening the bar under white type in
     light (AA fail) and darkening it in dark. Darkening works under white
     type everywhere. */
  --hpl-accent-wash-faint: rgba(0, 0, 0, 0.04);
  --hpl-accent-wash: rgba(0, 0, 0, 0.14);
  --hpl-accent-wash-strong: rgba(0, 0, 0, 0.18);
  --hpl-accent-wash-active: rgba(0, 0, 0, 0.2);
  /* Fixed high-contrast pair for deliberately inverse controls such as the
     black `+ New` action. Unlike --hpl-ink, it does not invert by theme. */
  --hpl-inverse-bg: #0E0E12;
  --hpl-inverse-bg-hover: #26262C;
  --hpl-inverse-ink: #FFFFFF;

  /* Brand logo mark — fixed four-colour set shared by the payslip logo
     (.hpl-s16-logo) and the series26 palette mark. Never theme-flipped. */
  --hpl-logo-sky: #7CC4D4;
  --hpl-logo-blue: #4A82C4;
  --hpl-logo-navy: #093DA0;
  --hpl-logo-amber: #F7BB55;

  /* Paper surface — printable documents (payslip, report print views)
     stay light even under the dark theme: a payslip is a sheet of paper,
     not chrome. These are static values, deliberately NOT overridden in
     [data-theme="dark"]; forced-light blocks remap the normal theme
     tokens to these (e.g. `--hpl-panel: var(--hpl-paper-panel)`). */
  --hpl-paper-panel: #FFFFFF;
  --hpl-paper-panel-alt: #FAFAFA;
  --hpl-paper-panel-tool: #F4F4F4;
  --hpl-paper-section-bar: #E1E1E1;
  --hpl-paper-ink: #0E0E12;
  --hpl-paper-ink-soft: #26262C;
  --hpl-paper-muted: #565D66;
  --hpl-paper-muted-soft: #686D75;
  --hpl-paper-disabled-ink: #B4B4B4;
  --hpl-paper-line: #E0E0E0;
  --hpl-paper-line-soft: #EEEEEE;
  --hpl-paper-line-strong: #CFCFCF;
  --hpl-paper-rule: #3B3B42;
  --hpl-paper-accent: #0E7D6B;
  --hpl-paper-accent-text: #FFFFFF;
  --hpl-paper-warning-ink: #7A4B00;
  --hpl-paper-warning-bg: #FFF4D6;
  --hpl-paper-warning-line: #E7C46A;
  --hpl-paper-negative-ink: #A52A1A;
  --hpl-paper-net-bg: #F0F6F4;

  /* Dashboard chart series — the teal data family used by the cashflow
     bars and AR/AP aging widgets. Same values in both themes (they sit
     on panel surfaces and stay readable on both). */
  --hpl-viz-teal: #3FA88E;
  --hpl-viz-teal-soft: #A9D3CB;
  --hpl-viz-teal-mute: #7FB3A8;

  /* Shared legacy names used by the page-specific v3 styles. */
  --hpl-text: var(--hpl-ink-soft);
  --hpl-text-secondary: var(--hpl-muted);
  --hpl-text-muted: var(--hpl-muted-soft);
  --hpl-border-strong: var(--hpl-line-strong);
  --hpl-row-hover: var(--hpl-panel-alt);
  --hpl-row-muted: var(--hpl-panel-mute);
  --hpl-surface-2: var(--hpl-panel-tool);
  --hpl-shadow-sm: 0 1px 2px rgba(15,15,18,0.04); /* mock card: single hairline shadow */
  --hpl-shadow-md: 0 2px 4px rgba(15,15,18,0.04), 0 4px 12px rgba(15,15,18,0.06);
  --hpl-shadow-lg: 0 4px 8px rgba(15,15,18,0.05), 0 12px 32px rgba(15,15,18,0.08);
  /* B8 (2026-08-29): the notice/alert/disclaimer panel shadow. Not a fourth
     size — a role. Four files had authored this exact two-layer value in
     three different spellings (payroll .hpl-leave-v3__disclaimer +
     .hpl-payroll-notice, report-packs .hpl-rp-guidance, system-language
     .hpl-form-alert); the light value here is theirs, byte for byte, so
     light rendering is unchanged. It is deliberately NOT folded into
     --hpl-shadow-md, whose geometry (2px/4px + 4px/12px) is visibly
     different from this one (1px/2px + 6px/16px). */
  --hpl-shadow-notice: 0 1px 2px rgba(20,20,15,0.04), 0 6px 16px rgba(20,20,15,0.045);
  /* B8 batch 5: three floating-surface roles. Each collapses sites whose
     geometry already agreed to within a couple of px of blur or 0.02 of
     alpha - i.e. the same shadow spelled slightly differently, not a
     consolidation of genuinely different ones. The rest of the floating
     family (two dropdown panels at 0.18, a card hover lift, an upward
     save-bar) stays literal on purpose: those ARE different values, and
     merging them would be 收敛, which this batch is not. */
  --hpl-shadow-menu: 0 12px 28px rgba(15,15,18,0.14);
  --hpl-shadow-popover: 0 8px 24px rgba(15,15,18,0.12);
  --hpl-shadow-dialog: 0 24px 60px rgba(14,14,18,0.28);
  --hpl-card-border: 1px solid var(--hpl-surface);
  --hpl-card-inner-highlight: inset 0 1px 0 rgba(255,255,255,0.9);

  /* Motion primitives (UI v3 B1) — every transition/animation in the v3 arc
     references these; [data-motion="reduce"] zeroes them via the
     master gate in custom.css. */
  --hpl-dur-fast: 150ms;
  --hpl-dur: 200ms;
  --hpl-dur-slow: 260ms;
  --hpl-ease: cubic-bezier(.2, .8, .25, 1);

  /* ── Size primitives (B8 batch 5, 2026-08-29) ────────────────────────
     Two layers, deliberately. These name a SIZE and nothing else; the role
     tokens below (--hpl-card-radius, --hpl-fs-body, …) are aliases onto
     them.

     Why the split: before this batch the scale was role-named and used as a
     size scale, so routing a literal through it asserted a role the element
     often did not have - 64 of the 119 `border-radius: 10px` sites are not
     cards (icon tiles, steppers, control bars), and only 4 of the 23 `4px`
     sites are a tag/chip/badge. That is the A64 failure mode: one token
     expressing two things. Pointing a literal at --hpl-radius-10 asserts
     only its size, which is always true; --hpl-card-radius stays for the
     places where the role is real.

     The stops are the distribution the app actually paints, not an
     aspiration (batch 4 COMPLETE §4 has the histograms). Consolidating to a
     shorter scale would move 35-60% of the sites - a redesign, not a
     refactor - and is deliberately NOT this batch (operator ruling
     2026-08-29: 收口, not 收敛). Off-scale literals are therefore left
     alone rather than snapped; the ratchet guard holds the count down
     instead of banning them. */
  --hpl-radius-4: 4px;
  --hpl-radius-5: 5px;
  --hpl-radius-6: 6px;
  --hpl-radius-7: 7px;
  --hpl-radius-8: 8px;
  --hpl-radius-9: 9px;
  --hpl-radius-10: 10px;
  --hpl-radius-12: 12px;
  --hpl-radius-14: 14px;
  /* Two shapes, not one: 50% makes an ellipse of a rectangle, 999px makes a
     stadium. They coincide only on a square, and the 97 sites split cleanly
     by intent - discs/dots/avatars vs badges/chips/pills - so they stay two
     tokens. (99px was a third spelling of pill.) */
  --hpl-radius-circle: 50%;
  --hpl-radius-pill: 999px;

  --hpl-fs-10: 10px;
  --hpl-fs-10-5: 10.5px;
  --hpl-fs-11: 11px;
  --hpl-fs-11-5: 11.5px;
  --hpl-fs-12: 12px;
  --hpl-fs-12-5: 12.5px;   /* the most-used size in the app (285 sites) and,
                              until this batch, the one with no token */
  --hpl-fs-13: 13px;
  --hpl-fs-13-5: 13.5px;
  --hpl-fs-15: 15px;
  --hpl-fs-18: 18px;
  --hpl-fs-21: 21px;
  --hpl-fs-24: 24px;
  --hpl-fs-38: 38px;       /* 13th stop: not from the literal histogram, but
                              --hpl-fs-hero needs a primitive to alias onto */

  /* ── Role aliases ───────────────────────────────────────────
     Same values as before this batch, now expressed as aliases. Note what
     this makes visible: 14 --hpl-fs-* names carry only 7 distinct values,
     and 13.5px alone answers to five of them. That is worth knowing before
     anyone reasons about "the type scale" from the name count. */
  --hpl-card-radius: var(--hpl-radius-10);
  --hpl-btn-radius: var(--hpl-radius-7); /* mock buttons: radius 7 */
  --hpl-tag-radius: var(--hpl-radius-4);
  --hpl-btn-sm-padding: 0 11px;
  --hpl-btn-md-padding: 0 16px;
  --hpl-btn-lg-padding: 0 20px;
  --hpl-fs-hero: var(--hpl-fs-38);
  --hpl-fs-title: var(--hpl-fs-21); /* mock H1: 650 21px/1.15 -.015em */
  --hpl-fs-emphasis: var(--hpl-fs-15);
  --hpl-fs-lede: var(--hpl-fs-13-5);
  --hpl-fs-eyebrow: var(--hpl-fs-11);
  --hpl-fs-h2: var(--hpl-fs-15);
  --hpl-fs-body: var(--hpl-fs-13-5);
  --hpl-fs-sidebar: var(--hpl-fs-13-5);
  --hpl-fs-btn-md: var(--hpl-fs-13); /* mock buttons: 600 13px */
  --hpl-fs-btn-sm: var(--hpl-fs-11-5);
  --hpl-fs-tag: var(--hpl-fs-11);
  --hpl-fs-td-num: var(--hpl-fs-13-5);
  --hpl-fs-td-text: var(--hpl-fs-13-5);
  --hpl-fs-th: var(--hpl-fs-11);
  --hpl-btn-sm-size: var(--hpl-fs-btn-sm);
  --hpl-btn-md-size: var(--hpl-fs-btn-md);
  --hpl-btn-lg-size: var(--hpl-fs-13-5);
  --hpl-btn-sm-height: 28px;
  --hpl-btn-md-height: 34px;
  --hpl-btn-lg-height: 40px;
  --hpl-touch-target: 44px;
  /* Final interactive prototype uses a fixed 216px desktop sidebar. */
  --hpl-sidebar-width: 216px;
  --hpl-topbar-height: 57px;
  --hpl-eyebrow-size: var(--hpl-fs-eyebrow);
  --hpl-eyebrow-letter: 0;
  --hpl-hero-number-size: var(--hpl-fs-hero);

  /* ── Overlay / modal z-index scale (single source of truth) ─────────────
     The SaaS platform strip (control_plane/web/app_shell.py) is injected at
     position:fixed; z-index:1000. Every overlay that must render *over* the
     SaaS chrome has to clear 1000; desktop has no strip so higher values are
     harmless there. Templates reference these via Tailwind arbitrary values,
     e.g. class="… z-[var(--hpl-z-modal)]". Keep the numbers here only — never
     inline a magic z-index on a modal. See handover §4. Layers stack:
     page modal < info < destructive-confirm < AI drawer < skip link.

     B8 (2026-08-29) — what this ladder does NOT own: a bare z-index below
     100 is a *local* stacking context (a chip over its row, an open picker
     over a later sibling card — see .hpl-ss--open / .hpl-ss-dropdown below
     in custom.css, a documented 30/40/80 family). Those are layout inside
     one component, not layers of the application, and routing them through
     this ladder would say something false about them. The audit counts
     them; this ladder deliberately leaves them alone. Only overlays that
     must clear the SaaS strip belong here. */
  --hpl-z-platform-strip: 1000; /* reference only — real value set in app_shell.py */
  --hpl-z-modal: 1100;          /* page-level modals (document view, reconcile, BAS, leave, report builder, bank import) */
  --hpl-z-modal-info: 1200;     /* read-only info / help / About modals — stack above page modals */
  --hpl-z-modal-confirm: 1300;  /* global destructive-confirm — stacks above info */
  --hpl-z-drawer: 1400;         /* AI slide-over backdrop */
  --hpl-z-drawer-panel: 1401;   /* AI slide-over panel */
  --hpl-z-skip-link: 10000;     /* keyboard skip-to-content — must clear every
                                   overlay above, including an open AI drawer,
                                   or the bypass link is unreachable exactly
                                   when a keyboard user most needs it. */
}

[data-theme="dark"] {
  color-scheme: dark;

  /* Dark mirrors the same 5-step layer ramp: L0 bg · L1 bg-content ·
     L2 panel · L2.5 panel-tool · L3 input. */
  --hpl-bg: #0A0A0D;
  --hpl-bg-alt: #0E0E12;
  --hpl-bg-content: #101014;
  --hpl-panel: #16161C;
  --hpl-panel-alt: #181820;
  --hpl-panel-mute: #1E1E26;
  --hpl-panel-deep: #22222C;
  --hpl-panel-tool: #1E1E26;
  --hpl-ink: #F2F2F2;
  --hpl-ink-soft: #D4D4D4;
  --hpl-muted: #8A8A8E;
  --hpl-muted-soft: #A0A0A6;
  --hpl-eyebrow: #A0A0A6;
  --hpl-disabled-ink: #6F6F78;
  --hpl-section-bar: #22222C;
  --hpl-section-bar-line: #32323C;
  --hpl-line: #22222A;
  --hpl-line-soft: #1B1B22;
  --hpl-line-strong: #32323C;
  --hpl-line-strong-hover: #3E3E48;
  --hpl-line-interactive: #62626E;
  --hpl-chip-line: #32323C;
  --hpl-surface: #2A2A33;
  --hpl-surface-hover: #36363F;
  --hpl-overlay: rgba(0, 0, 0, 0.7);
  --hpl-focus-ring: var(--hpl-accent-soft);
  --focus-ring: var(--hpl-focus-ring);
  --hpl-input-bg: #101014;
  --hpl-input-bg-hover: #101014;
  --hpl-input-border: #62626E;
  --hpl-input-border-hover: #6E6E7C;
  /* Restated (not inherited from :root): the light value computes its
     var(--hpl-accent-soft) against the light root and inherits down as a
     light literal, so a dark subtree under the light <html> would ring
     focus in the light accent wash. Same alias-layer trap as the semantic
     families below. */
  --hpl-input-focus-shadow: 0 0 0 2px var(--hpl-accent-soft), inset 0 -2px 0 var(--hpl-accent);
  --hpl-input-placeholder: #8A8A8E;
  --hpl-control-disabled-bg: #22222A;
  --hpl-info-ink: #8FD1F2;
  --hpl-info-bg: #14252E;
  --hpl-info-line: #2E596C;
  --hpl-success-ink: #8ED3AD;
  --hpl-success-bg: #14261E;
  --hpl-success-line: #2E5B43;
  --hpl-warning-ink: #F1C66B;
  --hpl-warning-bg: #2B2414;
  --hpl-warning-line: #6B5524;
  --hpl-danger-ink: #F29B8E;
  --hpl-danger-bg: #2D1918;
  --hpl-danger-line: #6D3430;
  --hpl-positive-ink: var(--hpl-success-ink);
  --hpl-positive-bg: var(--hpl-success-bg);
  --hpl-positive-line: var(--hpl-success-line);
  --hpl-negative-ink: var(--hpl-danger-ink);
  --hpl-negative-bg: var(--hpl-danger-bg);
  --hpl-negative-line: var(--hpl-danger-line);
  --hpl-up: var(--hpl-positive-ink);
  --hpl-up-soft: var(--hpl-positive-bg);
  --hpl-down: var(--hpl-negative-ink);
  --hpl-down-soft: var(--hpl-negative-bg);
  --hpl-warn: var(--hpl-warning-ink);
  --hpl-warn-soft: var(--hpl-warning-bg);
  --hpl-warn-line: var(--hpl-warning-line);
  --hpl-accent: oklch(0.72 calc(var(--hpl-accent-chroma) * 0.8) var(--hpl-accent-hue));
  --hpl-accent-hover: oklch(0.78 calc(var(--hpl-accent-chroma) * 0.8) var(--hpl-accent-hue));
  --hpl-accent-dim: oklch(0.55 calc(var(--hpl-accent-chroma) * 0.7) var(--hpl-accent-hue));
  --hpl-accent-soft: oklch(0.26 calc(var(--hpl-accent-chroma) * 0.35) var(--hpl-accent-hue));
  --hpl-accent-outline: oklch(0.42 0.05 var(--hpl-accent-hue));
  --hpl-chip-done-line: oklch(0.42 0.06 var(--hpl-accent-hue));
  --hpl-accent-text: #0A0A0D;
  --hpl-accent-active: oklch(0.64 calc(var(--hpl-accent-chroma) * 0.8) var(--hpl-accent-hue));
  --hpl-on-brand: #FFFFFF;
  --hpl-on-primary: #FFFFFF;
  --hpl-on-accent: #0A0A0D;
  --hpl-on-danger: #0A0A0D;
  /* B3 all-green ruling: dark chrome is a deeper brand green under white
     type; the L0.72 mint accent above keeps only the text/icon role. The
     fill family is deliberately NOT overridden here — dark fills use the
     light brand green (that identity is the whole point of the ruling). */
  --hpl-accent-chrome: oklch(0.37 calc(var(--hpl-accent-chroma) * 0.63) var(--hpl-accent-hue));
  --hpl-on-accent-chrome: #FFFFFF;
  --hpl-on-accent-chrome-soft: oklch(0.88 calc(var(--hpl-accent-chroma) * 0.45) var(--hpl-accent-hue));
  --hpl-shadow-sm: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.4);
  --hpl-shadow-md: 0 2px 4px rgba(0,0,0,0.4), 0 8px 24px rgba(0,0,0,0.5);
  --hpl-shadow-lg: 0 4px 12px rgba(0,0,0,0.5), 0 16px 48px rgba(0,0,0,0.6);
  --hpl-shadow-notice: 0 1px 2px rgba(0,0,0,0.4), 0 6px 16px rgba(0,0,0,0.5); /* same geometry as light, dark-tuned alphas — mirrors how --hpl-shadow-md is darkened */
  --hpl-shadow-menu: 0 12px 28px rgba(0,0,0,0.55);
  --hpl-shadow-popover: 0 8px 24px rgba(0,0,0,0.5);
  --hpl-shadow-dialog: 0 24px 60px rgba(0,0,0,0.65);
  --hpl-card-border: 1px solid var(--hpl-surface);
  --hpl-card-inner-highlight: inset 0 1px 0 rgba(255,255,255,0.04);
}

[data-theme="light"] {
  color-scheme: light;

  /* Values machine-extracted from the v3 mock set (all 23 series,
     2026-07-10 census) — the ramp is NEUTRAL white-grey, not warm paper.
     #EEEEEE frame / #FBFBFB content / #fff panels. */
  --hpl-bg: #F0F0F0;
  --hpl-bg-alt: #F0F0F0;
  --hpl-bg-content: #F0F0F0;
  --hpl-panel: #FFFFFF;
  --hpl-panel-alt: #FAFAFA;
  --hpl-panel-mute: #F2F2F2;
  --hpl-panel-deep: #E9E9E9;
  --hpl-panel-tool: #F4F4F4;
  /* Section header bars inside cards (mock: "PERIOD & BASIS" etc. — grey
     band + hairline, uppercase 650 11.5px; NOT a warm tint). */
  --hpl-section-bar: #D9D9D9;
  --hpl-section-bar-line: #D8D8D8;
  --hpl-ink: #0E0E12;
  --hpl-ink-soft: #26262C;
  /* #565D66 is the most-used text colour in the mock set (1891 hits) —
     body/secondary text. The old #6A6A70 was a README-era guess. */
  --hpl-muted: #565D66;
  --hpl-muted-soft: #686D75;
  --hpl-eyebrow: #686D75;
  --hpl-disabled-ink: #B4B4B4;
  --hpl-line: #E0E0E0;
  --hpl-line-soft: #EEEEEE;
  --hpl-line-strong: #CFCFCF;
  --hpl-line-strong-hover: #BABABA;
  --hpl-chip-line: #D5D5D5;
  --hpl-surface: #E0E0E0;
  --hpl-surface-hover: #CECECE;
  --hpl-focus-ring: var(--hpl-accent-soft);
  --hpl-input-bg: #FFFFFF;
  --hpl-input-bg-hover: #FFFFFF;
  --hpl-input-border: #8F8F8F;
  --hpl-input-border-hover: #6F6F6F;
  --hpl-input-focus-shadow: 0 0 0 2px var(--hpl-accent-soft), inset 0 -2px 0 var(--hpl-accent);
  --hpl-input-placeholder: #686868;
  --focus-ring: var(--hpl-focus-ring);
  /* Semantic families restated here, not only on :root: this block must be a
     COMPLETE light palette, because it is what re-scopes a subtree that pins
     itself light under a dark root (the kernel shell div and the SaaS
     account wrap both carry their own data-theme). Before B4 these four
     families were dark-block-only overrides, so a light-pinned subtree under
     dark inherited the dark amber/red/green — the chimera the pin exists to
     prevent.

     B4 restated the LEAF families but not the ALIAS layer that reads them
     (--hpl-positive-ink: var(--hpl-success-ink) and friends). A custom
     property's var() is substituted where the property is DECLARED, so an
     alias declared only on :root/dark computes against the dark root and
     inherits down as an already-resolved dark literal — the light block
     re-declaring --hpl-success-ink underneath it changes nothing. Measured
     on /ui/style-preview: dark #8ED3AD positive ink on the correctly-light
     #FFFFFF panel, 1.74:1, six pairs. Every token the dark block declares
     must be declared here too; tests/test_theme_token_symmetry_guard.py
     holds the two blocks to the same declaration set. */
  --hpl-info-ink: #245B78;
  --hpl-info-bg: #EAF4FA;
  --hpl-info-line: #B9D8E8;
  --hpl-success-ink: #256447;
  --hpl-success-bg: #EAF5EE;
  --hpl-success-line: #B9D8C7;
  --hpl-warning-ink: #7A4B00;
  --hpl-warning-bg: #FFF4D6;
  --hpl-warning-line: #E7C46A;
  --hpl-danger-ink: #A52A1A;
  --hpl-danger-bg: #FDECEA;
  --hpl-danger-line: #E7B8B1;
  --hpl-positive-ink: var(--hpl-success-ink);
  --hpl-positive-bg: var(--hpl-success-bg);
  --hpl-positive-line: var(--hpl-success-line);
  --hpl-negative-ink: var(--hpl-danger-ink);
  --hpl-negative-bg: var(--hpl-danger-bg);
  --hpl-negative-line: var(--hpl-danger-line);
  --hpl-control-disabled-bg: #E9E9E9;
  --hpl-line-interactive: #8F8F8F;
  --hpl-overlay: rgba(14, 14, 18, 0.46);
  --hpl-up: var(--hpl-positive-ink);
  --hpl-up-soft: var(--hpl-positive-bg);
  --hpl-down: var(--hpl-negative-ink);
  --hpl-down-soft: var(--hpl-negative-bg);
  --hpl-warn: var(--hpl-warning-ink);
  --hpl-warn-soft: var(--hpl-warning-bg);
  --hpl-warn-line: var(--hpl-warning-line);
  --hpl-accent: oklch(0.51 var(--hpl-accent-chroma) var(--hpl-accent-hue)); /* L 0.51, not 0.529: accent is used AS TEXT on the #F0F0F0 app bg (breadcrumbs, selected tabs, badges, counts) and 0.529 sat at 4.42:1 -- under AA. 0.51 clears 4.5 on every light palette (2026-08-24 UI review P3; guard: tests/test_accent_text_contrast_guard.py). ui-settings.js accentColorFor mirrors this number. */
  --hpl-accent-hover: oklch(0.465 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-accent-dim: oklch(0.7 calc(var(--hpl-accent-chroma) * 0.5) var(--hpl-accent-hue));
  --hpl-accent-soft: oklch(0.955 0.03 var(--hpl-accent-hue));
  --hpl-accent-outline: #C2DAD4;
  --hpl-chip-done-line: #AECFC7;
  --hpl-accent-text: #FFFFFF;
  --hpl-accent-active: oklch(0.405 var(--hpl-accent-chroma) var(--hpl-accent-hue));
  --hpl-on-brand: #FFFFFF;
  --hpl-on-primary: #FFFFFF;
  --hpl-on-accent: #FFFFFF;
  --hpl-on-danger: #FFFFFF;
  --hpl-accent-chrome: var(--hpl-accent);
  --hpl-on-accent-chrome: var(--hpl-on-accent);
  --hpl-on-accent-chrome-soft: var(--hpl-on-accent);
  --hpl-shadow-sm: 0 1px 2px rgba(15,15,18,0.04); /* mock card: single hairline shadow */
  --hpl-shadow-md: 0 2px 4px rgba(15,15,18,0.04), 0 4px 12px rgba(15,15,18,0.06);
  --hpl-shadow-lg: 0 4px 8px rgba(15,15,18,0.05), 0 12px 32px rgba(15,15,18,0.08);
  --hpl-shadow-notice: 0 1px 2px rgba(20,20,15,0.04), 0 6px 16px rgba(20,20,15,0.045);
  --hpl-shadow-menu: 0 12px 28px rgba(15,15,18,0.14);
  --hpl-shadow-popover: 0 8px 24px rgba(15,15,18,0.12);
  --hpl-shadow-dialog: 0 24px 60px rgba(14,14,18,0.28);
  --hpl-card-border: 1px solid var(--hpl-surface);
  --hpl-card-inner-highlight: inset 0 1px 0 rgba(255,255,255,0.9);
}

[data-theme="dark"][data-palette="sage"],
[data-theme="dark"] [data-palette="sage"] {
  --hpl-accent-hue: 142;
  --hpl-accent-chroma: 0.13;
}

[data-theme="light"][data-palette="sage"],
[data-theme="light"] [data-palette="sage"] {
  --hpl-accent-hue: 142;
  --hpl-accent-chroma: 0.12;
}

[data-theme="dark"][data-palette="ink"],
[data-theme="dark"] [data-palette="ink"] {
  --hpl-accent-hue: 225;
  --hpl-accent-chroma: 0.11;
}

[data-theme="light"][data-palette="ink"],
[data-theme="light"] [data-palette="ink"] {
  --hpl-accent-hue: 225;
  --hpl-accent-chroma: 0.11;
}

[data-theme="dark"][data-palette="marine"],
[data-theme="dark"] [data-palette="marine"] {
  --hpl-accent-hue: 198;
  --hpl-accent-chroma: 0.12;
}

[data-theme="light"][data-palette="marine"],
[data-theme="light"] [data-palette="marine"] {
  --hpl-accent-hue: 198;
  --hpl-accent-chroma: 0.12;
}