/* On-To.AI — design system
   The brand: dark-first, one orange accent (#FF7A45) over blue-black grounds, with
   teal and violet as the secondary signals. Archivo for display, IBM Plex Sans for
   body, IBM Plex Mono for every figure. Lifted from the brand document served at
   /plan and translated from that page's editorial scale into a dense operations
   console.

   THEME ARCHITECTURE — three states, and all three must be defined:
     :root                            -> the LIGHT palette (the bare-root default)
     @media (prefers-color-scheme:dark) :root:not([data-theme='light'])  -> dark
     :root[data-theme='dark']         -> dark, chosen explicitly
   A colour therefore NEVER gets its only definition inside a media query or a
   [data-theme] block: bare :root always defines it, and the dark blocks only
   REDEFINE. Anything that breaks that rule renders unstyled for the reader whose OS
   is light and who has chosen dark (or the reverse).

   The <html data-theme> attribute is written pre-paint by /static/theme.js, which is
   loaded SYNCHRONOUSLY in <head> — app.js is deferred and would run after first
   paint, flashing the wrong theme on every navigation. The app-wide CSP is
   `script-src 'self'`, so an inline pre-paint script is not an option.

   TOKEN CONTRACT
   - The --brand-* values are CONSTANTS: the same ink in both themes. Never redefine
     them per theme; that is what the semantic tokens below are for.
   - `--accent` is the brand orange as a FILL. `--accent-ink` is accent-coloured TEXT,
     and it is NOT the same value: #FF7A45 on white is 2.5:1 and fails WCAG AA for
     text, so light mode darkens it. Same reason `--accent-contrast` is near-black —
     white on #FF7A45 is 2.6:1, black on it is 7.1:1.
   - Each semantic family is (ink, soft, border): `--success` is the TEXT colour,
     `--success-soft` the tint behind it, `--success-border` the hairline. That triple
     inverts cleanly, which a single value could not.
   - Add new variables freely; changing an existing one re-skins the whole app. */

:root {
  /* ---- brand constants (identical in both themes) ---- */
  --brand-orange: #ff7a45;
  --brand-orange-lit: #ffa070;
  --brand-orange-deep: #e85a22;
  --brand-orange-dark: #b93c0c;
  --brand-teal: #189aad;
  --brand-violet: #7b68e8;

  /* ---- LIGHT palette ---- */
  color-scheme: light;

  /* neutrals */
  --bg: #f4f6fb;
  --surface: #ffffff;
  --surface-2: #edf0f7;
  --surface-3: #e2e7f1;
  --border: #dde3ee;
  --border-strong: #c3ccdd;
  --text: #0b1020;
  --text-muted: #505c72;
  --text-subtle: #7a8699;

  /* accent */
  --accent: #ff7a45;
  --accent-hover: #e85a22;
  --accent-ink: #c0430f;
  --accent-soft: #fff0e8;
  --accent-border: #ffcdb4;
  --accent-contrast: #2a1004;

  /* semantic — (ink, soft, border) per family */
  --success: #17754c;
  --success-soft: #e2f5ec;
  --success-border: #a8dcc4;
  --warning: #96650f;
  --warning-soft: #fdf3e0;
  --warning-border: #efd198;
  --danger: #b3271e;
  --danger-soft: #fdeae8;
  --danger-border: #f3b7b1;
  --info: #12707e;
  --info-soft: #e2f3f6;
  --info-border: #a5d8e0;
  --grey: #4a5468;
  --grey-soft: #eaeef5;
  --grey-border: #ccd5e3;

  /* SOLID fills — deliberately NOT the semantic families above.
     `--danger` is the danger INK, and it inverts: light in dark mode, dark in light
     mode. A solid danger button needs the opposite — a saturated fill that stays a
     fill in both themes with light text on it — so it gets its own token rather than
     reusing an ink that would render a pale salmon button on the dark theme.
     Same reasoning for toasts, which are an inverted surface by definition. */
  --danger-solid: #b3271e;
  --danger-solid-hover: #8f1d16;
  --on-solid: #ffffff;
  --toast-bg: #131a2e;
  --toast-ink: #f2f5fc;
  --toast-ok-bg: #14532d;
  --toast-err-bg: #7f1d1d;

  /* The PRIMARY BUTTON's fill, and deliberately not --accent.
     A solid button wants a white label — it reads as a button rather than as a
     coloured slab of text — but white on the brand orange #FF7A45 is 2.59:1 and
     fails AA at any size. Deepening the fill is what earns the white label:
     #C0430F puts it at 5.18:1. So --accent stays the brand orange everywhere it is
     a fill-behind-nothing (the active nav bar, the brand dot, focus rings) and the
     button takes its own deeper ramp. Hover goes DARKER, not lighter, because the
     label is white and a lighter hover would walk it back toward failing. */
  --accent-solid: #c0430f;
  --accent-solid-hover: #a83809;

  /* chrome surfaces that are not simply --surface */
  --topbar-bg: rgba(255, 255, 255, 0.9);
  --scrim: rgba(11, 16, 32, 0.42);
  --auth-glow: rgba(255, 122, 69, 0.11);
  /* The composer's fade-to-page mask. It must be --bg at alpha 0, NOT the keyword
     `transparent`: `transparent` is rgba(0,0,0,0) and Safari interpolates through
     black, greying the fade. Kept as a literal triplet because there is no way to
     take a var()'s channels. */
  --bg-fade: rgba(244, 246, 251, 0);
  /* the manual-override editor's dirty cell — signalled by colour alone */
  --edit-dirty-border: #c67c12;
  --edit-dirty-bg: #fdf6e6;
  --edit-dirty-ring: rgba(239, 209, 152, 0.55);

  /* chrome */
  --sidebar-bg: #fafbfd;
  --sidebar-border: #e4e9f2;
  --nav-text: #3e4a5f;
  --nav-text-dim: #78859a;

  /* surfaces: the brand's card recipe — a soft vertical gradient plus a 1px lit
     top edge. On light ground the highlight is a shadow-side hairline instead;
     a white-on-white highlight is invisible. */
  --surface-grad: linear-gradient(168deg, #ffffff, #f8fafd);
  --hairline: rgba(11, 16, 32, 0.06);
  /* The page's atmosphere layer (body::before). Dark mode makes this the brand's
     three radial washes; light mode keeps a single, far quieter warm one. */
  --atmosphere:
    radial-gradient(1100px 620px at 82% -10%, rgba(255, 122, 69, 0.06), transparent 62%),
    radial-gradient(900px 560px at 4% 6%, rgba(123, 104, 232, 0.045), transparent 64%);

  /* The dashboard assistant panel. Its own tokens rather than a reuse: --surface-grad
     is too quiet to read as the page's primary action, and a flat --accent-soft tint
     reads as a warning banner. Defined in all three theme states, like every colour. */
  --hero-grad: linear-gradient(134deg, #fff3ec 0%, #ffffff 48%, #f3f6fc 100%);
  --hero-glow: rgba(255, 122, 69, 0.1);
  /* The sparkline's area wash under a tone-neutral line. Tone-specific washes come
     from the existing --*-soft families; this is the fallback ground. */
  --spark-fade: rgba(11, 16, 32, 0.05);

  /* elevation */
  --shadow-xs: 0 1px 2px rgba(11, 16, 32, 0.05);
  --shadow-sm: 0 1px 2px rgba(11, 16, 32, 0.06), 0 1px 3px rgba(11, 16, 32, 0.04);
  --shadow-md: 0 4px 12px rgba(11, 16, 32, 0.08), 0 2px 4px rgba(11, 16, 32, 0.04);
  --shadow-lg: 0 18px 44px rgba(11, 16, 32, 0.14), 0 4px 12px rgba(11, 16, 32, 0.07);
  /* The ring a focused control paints. Kept separate from --accent so the focus
     state stays legible when a control sits ON an accent fill. */
  --focus-ring: rgba(255, 122, 69, 0.45);

  /* type */
  --font-display: 'Archivo', 'Archivo Fallback', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font:
    'IBM Plex Sans', 'IBM Plex Sans Fallback', system-ui, -apple-system, 'Segoe UI', Roboto,
    Helvetica, Arial, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* geometry */
  --radius-sm: 6px;
  --radius: 9px;
  --radius-lg: 14px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sidebar-w: 248px;
  --rail-w: 60px;
  --topbar-h: 52px;
  --ease: 150ms ease;
}

/* ---- DARK palette ----------------------------------------------------------
   Declared ONCE in a custom property list and applied by both selectors below, so
   the OS-preference path and the explicit-choice path can never drift apart. CSS
   has no mixin, so the block is repeated verbatim; keep them in step. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --bg: #06080f;
    --surface: #0c1020;
    --surface-2: #131829;
    --surface-3: #1a2036;
    --border: #1d2438;
    --border-strong: #2b344e;
    --text: #f2f5fc;
    --text-muted: #c2cbdd;
    --text-subtle: #8d98b0;

    --accent: #ff7a45;
    --accent-hover: #ffa070;
    --accent-ink: #ff9166;
    --accent-soft: rgba(255, 122, 69, 0.13);
    --accent-border: rgba(255, 122, 69, 0.4);
    --accent-contrast: #200a02;

    --success: #5fd69c;
    --success-soft: rgba(63, 169, 122, 0.15);
    --success-border: rgba(63, 169, 122, 0.42);
    --warning: #edba55;
    --warning-soft: rgba(201, 150, 51, 0.15);
    --warning-border: rgba(201, 150, 51, 0.42);
    --danger: #ff8078;
    --danger-soft: rgba(222, 91, 82, 0.15);
    --danger-border: rgba(222, 91, 82, 0.42);
    --info: #3fc3d6;
    --info-soft: rgba(24, 154, 173, 0.15);
    --info-border: rgba(24, 154, 173, 0.42);
    --grey: #9aa6bc;
    --grey-soft: rgba(160, 178, 220, 0.1);
    --grey-border: rgba(160, 178, 220, 0.26);

    --topbar-bg: rgba(9, 12, 24, 0.88);
    --scrim: rgba(2, 4, 10, 0.62);
    --auth-glow: rgba(255, 122, 69, 0.14);
    --bg-fade: rgba(6, 8, 15, 0);
    --edit-dirty-border: #edba55;
    --edit-dirty-bg: rgba(201, 150, 51, 0.16);
    --edit-dirty-ring: rgba(201, 150, 51, 0.3);

    /* Brighter than the light theme's, for the usual reason a fill inverts: on a
       near-black ground a dark fill recedes into it. White on #CF4713 is 4.60:1. */
    --accent-solid: #cf4713;
    --accent-solid-hover: #e05a22;

    --danger-solid: #c4362c;
    --danger-solid-hover: #d94a3f;
    --on-solid: #ffffff;
    --toast-bg: #1f2740;
    --toast-ink: #f2f5fc;
    --toast-ok-bg: #14503a;
    --toast-err-bg: #6d2420;

    --sidebar-bg: #090c18;
    --sidebar-border: rgba(160, 178, 220, 0.11);
    --nav-text: #c2cbdd;
    --nav-text-dim: #8d98b0;

    --surface-grad: linear-gradient(168deg, #131829, #0c1020);
    --hairline: rgba(200, 214, 255, 0.16);
    --atmosphere:
      radial-gradient(1100px 620px at 82% -10%, rgba(232, 90, 34, 0.16), transparent 62%),
      radial-gradient(900px 560px at 4% 6%, rgba(123, 104, 232, 0.12), transparent 64%),
      radial-gradient(1000px 700px at 50% 108%, rgba(24, 154, 173, 0.09), transparent 62%);

    --hero-grad: linear-gradient(134deg, rgba(232, 90, 34, 0.22) 0%, #131829 48%, #0c1020 100%);
    --hero-glow: rgba(255, 122, 69, 0.16);
    --spark-fade: rgba(200, 214, 255, 0.08);

    --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.5), 0 2px 5px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 22px 50px rgba(0, 0, 0, 0.62), 0 6px 16px rgba(0, 0, 0, 0.42);
    --focus-ring: rgba(255, 122, 69, 0.55);
  }
}

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

  --bg: #06080f;
  --surface: #0c1020;
  --surface-2: #131829;
  --surface-3: #1a2036;
  --border: #1d2438;
  --border-strong: #2b344e;
  --text: #f2f5fc;
  --text-muted: #c2cbdd;
  --text-subtle: #8d98b0;

  --accent: #ff7a45;
  --accent-hover: #ffa070;
  --accent-ink: #ff9166;
  --accent-soft: rgba(255, 122, 69, 0.13);
  --accent-border: rgba(255, 122, 69, 0.4);
  --accent-contrast: #200a02;

  --success: #5fd69c;
  --success-soft: rgba(63, 169, 122, 0.15);
  --success-border: rgba(63, 169, 122, 0.42);
  --warning: #edba55;
  --warning-soft: rgba(201, 150, 51, 0.15);
  --warning-border: rgba(201, 150, 51, 0.42);
  --danger: #ff8078;
  --danger-soft: rgba(222, 91, 82, 0.15);
  --danger-border: rgba(222, 91, 82, 0.42);
  --info: #3fc3d6;
  --info-soft: rgba(24, 154, 173, 0.15);
  --info-border: rgba(24, 154, 173, 0.42);
  --grey: #9aa6bc;
  --grey-soft: rgba(160, 178, 220, 0.1);
  --grey-border: rgba(160, 178, 220, 0.26);

  --topbar-bg: rgba(9, 12, 24, 0.88);
  --scrim: rgba(2, 4, 10, 0.62);
  --auth-glow: rgba(255, 122, 69, 0.14);
  --bg-fade: rgba(6, 8, 15, 0);
  --edit-dirty-border: #edba55;
  --edit-dirty-bg: rgba(201, 150, 51, 0.16);
  --edit-dirty-ring: rgba(201, 150, 51, 0.3);

  /* Brighter than the light theme's, for the usual reason a fill inverts: on a
     near-black ground a dark fill recedes into it. White on #CF4713 is 4.60:1. */
  --accent-solid: #cf4713;
  --accent-solid-hover: #e05a22;

  --danger-solid: #c4362c;
  --danger-solid-hover: #d94a3f;
  --on-solid: #ffffff;
  --toast-bg: #1f2740;
  --toast-ink: #f2f5fc;
  --toast-ok-bg: #14503a;
  --toast-err-bg: #6d2420;

  --sidebar-bg: #090c18;
  --sidebar-border: rgba(160, 178, 220, 0.11);
  --nav-text: #c2cbdd;
  --nav-text-dim: #8d98b0;

  --surface-grad: linear-gradient(168deg, #131829, #0c1020);
  --hairline: rgba(200, 214, 255, 0.16);
  --atmosphere:
    radial-gradient(1100px 620px at 82% -10%, rgba(232, 90, 34, 0.16), transparent 62%),
    radial-gradient(900px 560px at 4% 6%, rgba(123, 104, 232, 0.12), transparent 64%),
    radial-gradient(1000px 700px at 50% 108%, rgba(24, 154, 173, 0.09), transparent 62%);

  --hero-grad: linear-gradient(134deg, rgba(232, 90, 34, 0.22) 0%, #131829 48%, #0c1020 100%);
  --hero-glow: rgba(255, 122, 69, 0.16);
  --spark-fade: rgba(200, 214, 255, 0.08);

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.5), 0 2px 5px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 22px 50px rgba(0, 0, 0, 0.62), 0 6px 16px rgba(0, 0, 0, 0.42);
  --focus-ring: rgba(255, 122, 69, 0.55);
}

/* ---- self-hosted brand faces -----------------------------------------------
   Self-hosted, NOT loaded from Google: the app-wide CSP is `default-src 'self'`
   and this app renders customer invoice data, so neither a stylesheet nor a font
   should be fetched from a third party. Latin subsets only — the console is
   English; the bilingual /plan document loads its own Hebrew faces under its own
   policy.
   The size-adjust fallbacks below are metric-matched to the real faces so the
   swap at font-display:swap does not reflow the page. */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url('/static/fonts/archivo-var-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/static/fonts/ibm-plex-sans-var-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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/ibm-plex-mono-600-latin.woff2') format('woff2');
}
/* Metric-matched stand-ins used only for the few hundred milliseconds before the
   real faces land. Without them the fallback is the system UI face at its own
   metrics and every heading reflows on swap. */
@font-face {
  font-family: 'Archivo Fallback';
  src: local('Helvetica Neue'), local('Arial'), local('Segoe UI');
  size-adjust: 101%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'IBM Plex Sans Fallback';
  src: local('Helvetica Neue'), local('Arial'), local('Segoe UI');
  size-adjust: 100%;
  ascent-override: 95%;
  descent-override: 26%;
  line-gap-override: 0%;
}

* {
  box-sizing: border-box;
}

html {
  font-size: 14px;
}
html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The brand's atmosphere: two (light) or three (dark) enormous, very low-opacity
   radial washes behind everything. `position: fixed` so it does not scroll with a
   long run table, and `z-index: -1` rather than a stacking context on <body>, which
   would trap the sticky sidebar and every popover. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--atmosphere);
}

a {
  color: var(--accent-ink);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

/* Archivo, at the expanded width the brand uses for its wordmark and headings.
   `font-variation-settings` rather than font-stretch so the axis is set even where
   the shorthand is not honoured; the fallback face ignores it harmlessly. */
h1,
h2,
h3 {
  margin: 0 0 0.45em;
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 108;
  line-height: 1.28;
  font-weight: 600;
  letter-spacing: -0.018em;
}
h1 {
  font-size: 21px;
  font-weight: 650;
  letter-spacing: -0.026em;
}
h2 {
  font-size: 16px;
}
h3 {
  font-size: 14px;
}
p {
  margin: 0 0 0.75rem;
}
.muted {
  color: var(--text-muted);
}
.subtle {
  color: var(--text-subtle);
}
.small {
  font-size: 0.88rem;
}
code,
.mono {
  font-family: var(--mono);
  font-size: 0.88em;
  font-variant-numeric: tabular-nums;
}
code {
  background: var(--surface-2);
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}

/* Every figure in this app is a money amount, a row count or an id, and all of them
   are read in columns. Tabular figures everywhere they occur, not just in .mono. */
td,
th,
input,
output,
.stat__value,
.num {
  font-variant-numeric: tabular-nums;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------- UA-drawn surfaces ----------
   Everything the browser paints for us rather than us painting it. Each of these
   stays light on a dark theme unless it is named explicitly, and each of them is
   the kind of miss you only see once the theme is live:
   - `color-scheme` (set on :root beside each palette) is what re-tints the <select>
     disclosure arrow and its native popup, the file input's UA button, Chrome's
     autofill wash on the login form, and every scrollbar track in the seven scroll
     containers this app has. It is one line and it does more than the rest combined.
   - `accent-color` decides what a CHECKED checkbox is filled with. Without it the
     bulk-select boxes paint in the operating system's accent, so the app is
     On-To.AI orange everywhere except the one control the operator clicks most. */
input,
textarea,
select,
button {
  accent-color: var(--accent);
}
::selection {
  background: var(--accent-soft);
  color: var(--text);
}
::placeholder {
  color: var(--text-subtle);
  opacity: 1; /* Firefox dims placeholders a second time on top of the colour */
}
input,
textarea {
  caret-color: var(--accent);
}
/* `color-scheme` re-tints the file input's UA button, but it stays a system-grey
   slab that reads as borrowed from another application. Styling it explicitly is
   the only way to make the convert screen's file picker look like this app. */
::file-selector-button {
  font: inherit;
  font-weight: 600;
  font-size: 0.88rem;
  margin-inline-end: 0.7rem;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}
::file-selector-button:hover {
  background: var(--surface-3);
}
::marker {
  color: var(--text-subtle);
}

/* ---------- app shell ---------- */
.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
}

.sidebar {
  background: var(--sidebar-bg);
  border-inline-end: 1px solid var(--sidebar-border);
  color: var(--nav-text);
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overflow-x: hidden;
}

.sidebar__brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: var(--sp-2) var(--sp-2) var(--sp-5);
  color: var(--text);
  line-height: 1.15;
}
/* The mark carries its own colour (a shaded orange ring), so the container is a
   plain box — no tile, no gradient, no plate behind it. That is what lets the same
   file serve as the favicon, the sidebar mark and the login mark unchanged. */
.logo {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  flex: none;
}
.logo svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* The wordmark: Archivo at the brand's expanded width, with `.AI` in accent.
   `em` is the carrier because the markup reads On-To<em>.AI</em> — semantic
   emphasis on the part that is the brand, not an empty span. */
.brand-text {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 118;
  font-weight: 700;
  font-size: 16.5px;
  letter-spacing: -0.03em;
}
.brand-text em {
  font-style: normal;
  color: var(--accent-ink);
}
.sidebar__brand small,
.brand-text small {
  display: block;
  font-family: var(--mono);
  font-weight: 400;
  color: var(--text-subtle);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-top: 3px;
}

/* ---------- company context ----------
   Which company a click lands in. Two shapes of the same fact: a static line for an
   operator who works in one company, and a <select> for On-To.AI staff who do not.
   Both sit directly under the brand, because the answer to "whose data is this?" has
   to be readable before the eye reaches the first destination.

   No colour of their own — the acting bar carries the alarm, and a permanently tinted
   sidebar would spend the attention this design needs to have available. */
.company-badge,
.company-switch {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 var(--sp-2) var(--sp-3);
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  min-width: 0;
}
.company-badge__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-ink);
  flex: none;
}
.company-badge__name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.company-switch {
  flex-wrap: wrap;
  gap: 6px;
}
/* Visible, not sr-only: staff switch context rarely enough that an unlabelled select
   in a sidebar reads as a filter. */
.company-switch__label {
  flex-basis: 100%;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.company-switch__select {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 4px 6px;
}
/* app.js sets this once the change listener is attached; without JS the button is the
   only way to submit, so it must stay visible. */
.company-switch__go[hidden] {
  display: none;
}

/* --- grouped nav --- */
.nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
}

.nav__group {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.nav__group + .nav__group {
  margin-top: var(--sp-4);
}
.nav__group--flat {
  margin-top: 0;
}

.nav__group-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: var(--sp-1) var(--sp-2);
  margin: 0;
  border: none;
  background: transparent;
  font: inherit;
  cursor: pointer;
  color: var(--nav-text-dim);
  border-radius: var(--radius-sm);
  transition:
    color var(--ease),
    background var(--ease);
}
.nav__group-btn:hover {
  color: var(--text-muted);
  background: transparent;
}
.nav__group-label {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  opacity: 0.75;
}
.nav__chevron {
  flex: none;
  width: 12px;
  height: 12px;
  opacity: 0;
  transition:
    transform var(--ease),
    opacity var(--ease);
}
/* Shown when the header is hovered or focused, or whenever the group is closed —
   a collapsed group must always advertise that it can be reopened. */
.nav__group-btn:hover .nav__chevron,
.nav__group-btn:focus-visible .nav__chevron,
.nav__group:not(.is-open) .nav__chevron {
  opacity: 0.75;
}
.nav__group.is-open > .nav__group-btn .nav__chevron {
  transform: rotate(90deg);
}
.nav__group:not(.is-open) > .nav__group-items {
  display: none;
}
.nav__group-items {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px var(--sp-2);
  border-radius: 8px;
  color: var(--nav-text);
  font-weight: 500;
  font-size: 13.5px;
  line-height: 1.45;
  transition:
    background var(--ease),
    color var(--ease);
}
.nav__link:hover {
  background: var(--surface-2);
  text-decoration: none;
  color: var(--text);
}
.nav__link[aria-current='page'] {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}
.nav__link[aria-current='page']::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  top: 6px;
  bottom: 6px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}
.nav__ico {
  flex: none;
  opacity: 0.85;
}
.nav__link[aria-current='page'] .nav__ico {
  opacity: 1;
}
.nav__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__spacer {
  flex: 1;
}

/* The pinned tail: destinations that are not part of the daily loop, separated from
   the main list by a rule rather than by a heading. A heading here would be a third
   group label for a single item — precisely the chrome this removes. */
.nav--pinned {
  padding-top: var(--sp-2);
  border-top: 1px solid var(--sidebar-border);
}

.sidebar__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: var(--sp-1) var(--sp-1) 0;
  margin-top: var(--sp-1);
}
.rail-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: none;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--nav-text-dim);
  border-radius: var(--radius-sm);
  transition:
    background var(--ease),
    color var(--ease);
}
.rail-toggle:hover {
  background: var(--surface-2);
  color: var(--text-muted);
}

/* --- main column / topbar --- */
.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--topbar-h);
  padding: 0 var(--sp-6);
  background: var(--topbar-bg);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar__left {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}
.topbar__title {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 108;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.022em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar__menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--text-muted);
  border-radius: var(--radius-sm);
}
.topbar__menu-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
.topbar__user {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 12.5px;
  color: var(--text-muted);
}
.avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  border: 1px solid var(--accent-border);
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 11px;
}
/* The signed-in address is context, not content — it yields the row's width to the
   page title before anything else does. */
.topbar__email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* --- theme toggle ---
   One button, three states. All three glyphs are in the markup and this picks the
   one matching `data-theme-state`, so switching costs no layout and no JS-authored
   markup. The attribute is stamped by app.js on load; the `system` fallback below
   is what shows in the instant before that (and forever, if JS never runs). */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  color: var(--text-subtle);
  border-radius: var(--radius-sm);
  transition:
    background var(--ease),
    color var(--ease),
    border-color var(--ease);
}
.theme-toggle:hover {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}
.theme-toggle__ico {
  display: none;
}
.theme-toggle[data-theme-state='system'] .theme-toggle__ico--system,
.theme-toggle[data-theme-state='light'] .theme-toggle__ico--light,
.theme-toggle[data-theme-state='dark'] .theme-toggle__ico--dark {
  display: block;
}
/* An explicit choice is a state worth seeing; "follow system" is the quiet default. */
.theme-toggle[data-theme-state='light'],
.theme-toggle[data-theme-state='dark'] {
  color: var(--accent-ink);
}

.content {
  padding: var(--sp-6) var(--sp-8);
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
}
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
}
.page-head .actions {
  display: flex;
  gap: var(--sp-2);
}

/* ---------- sidebar rail (whole-sidebar collapse, desktop only) ---------- */
@media (min-width: 901px) {
  .app-shell {
    transition: grid-template-columns var(--ease);
  }
  body.nav-rail .app-shell {
    grid-template-columns: var(--rail-w) 1fr;
  }
  body.nav-rail .sidebar {
    padding-inline: var(--sp-2);
  }
  body.nav-rail .sidebar__brand {
    justify-content: center;
    padding-inline: 0;
  }
  body.nav-rail .brand-text,
  body.nav-rail .sidebar__brand > div {
    display: none;
  }
  body.nav-rail .nav__group-btn {
    display: none;
  }
  /* In the icon rail there is no room to name a company, and a truncated name is
     worse than none: "Ocea…" beside a customer's data is exactly the mis-read the
     acting bar exists to prevent. The bar itself is in the content column and stays. */
  body.nav-rail .company-badge,
  body.nav-rail .company-switch {
    display: none;
  }
  body.nav-rail .nav__group + .nav__group {
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--sidebar-border);
  }
  /* Groups become flat icon lists — a collapsed group still shows its icons. */
  body.nav-rail .nav__group:not(.is-open) > .nav__group-items {
    display: flex;
  }
  body.nav-rail .nav__label {
    display: none;
  }
  body.nav-rail .nav__link {
    justify-content: center;
    padding: 7px 0;
  }
  body.nav-rail .nav__link[aria-current='page']::before {
    top: 4px;
    bottom: 4px;
  }
  body.nav-rail .sidebar__footer {
    justify-content: center;
    padding-inline: 0;
  }
  /* The group headers are hidden in the rail, so the pinned tail's rule is the only
     separator left — it has to reach both edges or it reads as a stray dash. */
  body.nav-rail .nav--pinned {
    margin-inline: calc(var(--sp-3) * -1);
    padding-inline: var(--sp-1);
  }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.42rem 0.9rem;
  font: inherit;
  font-weight: 600;
  font-size: 0.92rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition:
    background var(--ease),
    border-color var(--ease),
    box-shadow var(--ease),
    color var(--ease);
  text-decoration: none;
  box-shadow: var(--shadow-xs);
}
.btn:hover {
  background: var(--surface-2);
  text-decoration: none;
}
.btn--primary {
  background: var(--accent-solid);
  border-color: var(--accent-solid);
  color: var(--on-solid);
}
.btn--primary:hover {
  background: var(--accent-solid-hover);
  border-color: var(--accent-solid-hover);
}
.btn--danger {
  background: var(--danger-solid);
  border-color: var(--danger-solid);
  color: var(--on-solid);
}
.btn--danger:hover {
  background: var(--danger-solid-hover);
  border-color: var(--danger-solid-hover);
}
.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--accent-ink);
  box-shadow: none;
}
.btn--ghost:hover {
  background: var(--accent-soft);
}
.btn--sm {
  padding: 0.28rem 0.6rem;
  font-size: 0.85rem;
}
/* `display: inline-flex` above beats the [hidden] attribute's UA `display: none`, so a
   button toggled with `hidden` would stay visible. Restore it explicitly. */
.btn[hidden] {
  display: none;
}
.btn[disabled],
.btn[aria-disabled='true'] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* ---------- cards ----------
   THE BRAND'S SURFACE RECIPE, carried over from the /plan document: a soft 168deg
   gradient (a light source above-left), a hairline border, and a 1px full-bleed
   specular bevel on the top edge that fades in from nothing, peaks at centre and
   fades out again. The bevel is what makes a dark panel read as a lit object
   rather than a flat hole; `--hairline` inverts it to a shadow-side line on the
   light theme, where a white highlight on white would be invisible.
   `overflow: hidden` is load-bearing — it clips the bevel to the rounded corner. */
.card,
.stat,
.table-wrap {
  position: relative;
  overflow: hidden;
}
.card::before,
.stat::before,
.table-wrap::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hairline), transparent);
  pointer-events: none;
  z-index: 1;
}
.card {
  background: var(--surface-grad);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  padding: var(--sp-5) var(--sp-6);
}
.card + .card {
  margin-top: var(--sp-4);
}
/* Draws the eye to a one-time payload the user must act on before navigating away. */
.card--accent {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.copy-row {
  display: flex;
  gap: var(--sp-2);
  align-items: stretch;
  margin-bottom: var(--sp-4);
}
.copy-row__input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.88rem;
}
.copy-row .btn {
  flex: 0 0 auto;
}
.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-3);
  gap: var(--sp-2);
}
.card__title {
  font-size: 14px;
  font-weight: 600;
  margin: 0;
}
.grid {
  display: grid;
  gap: var(--sp-4);
}
.grid--stats {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.stat {
  background: var(--surface-grad);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-4) var(--sp-5);
  box-shadow: var(--shadow-xs);
}
/* The brand's label voice: mono, uppercase, widely tracked, quiet. Every label in
   the document uses it, and it is what makes the figure beside it read as a figure. */
.stat__label {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--text-subtle);
  font-weight: 500;
}
.stat__value {
  font-family: var(--mono);
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1.08;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.stat__hint {
  font-size: 0.85rem;
  color: var(--text-subtle);
  margin-top: 2px;
}

/* ---------- tables ---------- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-grad);
  box-shadow: var(--shadow-xs);
}
table.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.table th,
.table td {
  padding: 0.55rem 0.85rem;
  text-align: start;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.table th {
  background: var(--surface-2);
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-subtle);
  font-weight: 500;
}
.table tbody tr:last-child td {
  border-bottom: none;
}
.table tbody tr:hover {
  background: var(--surface-2);
}
.table td.num,
.table th.num {
  text-align: end;
  font-variant-numeric: tabular-nums;
}
.table--fixed td {
  white-space: normal;
}
/* The manual-override editor: compact per-cell inputs, changed cells highlighted. */
.table--edit td {
  padding: 0.25rem;
}
.cell-input {
  width: 100%;
  min-width: 5.5rem;
  font-size: 0.85rem;
  padding: 0.3rem 0.4rem;
  font-family: var(--mono, ui-monospace, monospace);
}
.cell-input--changed {
  border-color: var(--edit-dirty-border);
  background: var(--edit-dirty-bg);
  box-shadow: 0 0 0 2px var(--edit-dirty-ring);
}

/* Several small actions in one table cell (e.g. Resend + Revoke). */
.row-actions {
  display: flex;
  gap: 0.35rem;
  justify-content: flex-end;
  align-items: center;
}

/* ---------- badges ----------
   The brand's pill: mono, uppercase, widely tracked, with a 6px status dot that
   inherits `currentColor` — so every variant below only has to state its ink, its
   tint and its hairline, and the dot follows for free. The status vocabulary this
   carries ([VALIDATED] / draft / gated / retired) is what an operator reads at a
   glance before trusting a CSV, so each variant is a distinct hue, not a shade. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.34rem;
  padding: 0.16rem 0.55rem;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
/* `--muted` is the absence of a status, not a status — it gets no dot. */
.badge--muted::before {
  display: none;
}
.badge--draft {
  background: var(--warning-soft);
  color: var(--warning);
  border-color: var(--warning-border);
}
.badge--frozen {
  background: var(--success-soft);
  color: var(--success);
  border-color: var(--success-border);
}
.badge--retired {
  background: var(--grey-soft);
  color: var(--grey);
  border-color: var(--grey-border);
}
.badge--info {
  background: var(--info-soft);
  color: var(--info);
  border-color: var(--info-border);
}
.badge--danger {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: var(--danger-border);
}
.badge--ok {
  background: var(--success-soft);
  color: var(--success);
  border-color: var(--success-border);
}
.badge--muted {
  background: var(--surface-2);
  color: var(--text-muted);
  border-color: var(--border);
}
.badge--gated {
  background: var(--warning-soft);
  color: var(--warning);
  border-color: var(--warning-border);
}

/* ---------- banners ---------- */
.banner {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.7rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  margin-bottom: var(--sp-4);
  font-size: 0.92rem;
}
.banner .banner__ico {
  font-size: 1.05rem;
  line-height: 1.3;
  flex: none;
}
.banner strong {
  font-weight: 650;
}
.banner--warn {
  background: var(--warning-soft);
  border-color: var(--warning-border);
  color: var(--warning);
}
.banner--danger {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  color: var(--danger);
}
.banner--info {
  background: var(--info-soft);
  border-color: var(--info-border);
  color: var(--info);
}
.banner--sandbox {
  background: var(--warning-soft);
  border-color: var(--warning-border);
  color: var(--warning);
}

/* ---------- forms ---------- */
.field {
  margin-bottom: var(--sp-4);
}
.field > label {
  display: block;
  font-weight: 600;
  font-size: 0.88rem;
  margin-bottom: 0.3rem;
}
.field__hint {
  font-size: 0.82rem;
  color: var(--text-subtle);
  margin-top: 0.25rem;
}
input[type='text'],
input[type='email'],
input[type='password'],
input[type='file'],
select,
textarea {
  width: 100%;
  padding: 0.45rem 0.65rem;
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition:
    border-color var(--ease),
    box-shadow var(--ease);
}
input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea {
  min-height: 90px;
  font-family: var(--mono);
  font-size: 0.85rem;
}
.form-grid {
  display: grid;
  gap: 0.2rem 1rem;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
/* A console-style filter row: inputs sized to their content, actions inline and
   NORMAL sized. Buttons must never be direct children of .form-grid — that grid is
   auto-fit/minmax, so a button placed in it stretches to a full 240px cell. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3);
}
.filter-bar .field {
  margin-bottom: 0;
  flex: 1 1 200px;
  min-width: 160px;
}
.filter-bar .field--wide {
  flex: 2 1 280px;
}
.filter-bar .field--auto {
  flex: 0 0 auto;
}
.filter-bar__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-bottom: 1px;
}
.filter-bar__actions .btn {
  white-space: nowrap;
}

/* Pagination + bulk-selection toolbar under a table. */
.table-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
}
.table-toolbar__info {
  font-size: 0.86rem;
  color: var(--text-muted);
}
.pager {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.bulk-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  margin-bottom: var(--sp-3);
}
.bulk-bar[hidden] {
  display: none;
}
.bulk-bar__count {
  font-weight: 600;
  font-size: 0.9rem;
}

.checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.checkbox input {
  width: auto;
}

/* ---------- empty / loading ---------- */
.empty {
  text-align: center;
  padding: var(--sp-8) var(--sp-4);
  color: var(--text-muted);
}
.empty .empty__ico {
  font-size: 2rem;
  opacity: 0.5;
}
.empty h3 {
  color: var(--text);
  margin-top: var(--sp-2);
}
.spinner {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: 0.7;
  display: inline-block;
  animation: spin 0.7s linear infinite;
  vertical-align: -3px;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---------- toasts ---------- */
.toast-region {
  position: fixed;
  inset-inline-end: var(--sp-4);
  bottom: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  z-index: 100;
}
.toast {
  background: var(--toast-bg);
  color: var(--toast-ink);
  padding: 0.6rem 0.95rem;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  font-size: 0.9rem;
  max-width: 360px;
  animation: toast-in 0.18s ease-out;
}
.toast--success {
  background: var(--toast-ok-bg);
}
.toast--error {
  background: var(--toast-err-bg);
}
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ---------- modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: grid;
  place-items: center;
  z-index: 90;
}
.modal {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--sp-6);
  max-width: 440px;
  width: calc(100% - 2rem);
}
.modal h2 {
  margin-bottom: var(--sp-2);
}
.modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}

/* ---------- misc layout ---------- */
.row {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  flex-wrap: wrap;
}
.between {
  justify-content: space-between;
}
.stack > * + * {
  margin-top: var(--sp-4);
}
.kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1.2rem;
  font-size: 0.92rem;
}
.kv dt {
  color: var(--text-muted);
}
.kv dd {
  margin: 0;
  font-weight: 500;
}
.divider {
  height: 1px;
  background: var(--border);
  margin: var(--sp-4) 0;
  border: none;
}
.tag-gated {
  font-size: 10px;
  font-weight: 700;
  color: var(--warning);
  background: var(--warning-soft);
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ---------- auth (login) ---------- */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--sp-6);
  background: radial-gradient(1200px 600px at 50% -10%, var(--auth-glow), transparent 70%);
}
.auth-card {
  width: 100%;
  max-width: 384px;
}
/* The login card is the only place the brand is the whole point of the screen, so
   the lockup is stacked and given room rather than squeezed into a row. */
.auth-card .brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: var(--sp-6);
}
.auth-card .brand .logo {
  width: 46px;
  height: 46px;
}
.auth-card .brand .logo svg {
  width: 46px;
  height: 46px;
}
.auth-card .brand .brand-text {
  font-size: 24px;
  font-variation-settings: 'wdth' 122;
  letter-spacing: -0.04em;
}
.auth-card .card {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--sp-6);
}

/* ---------- account button + dialog ----------
   The signed-in operator sits at the bottom of the sidebar, under Settings — the
   Claude/ChatGPT placement, and the right one: an account control is not a page
   action, so it should not share the top bar with the page title. */
.account-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2);
  margin-top: var(--sp-1);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--nav-text);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  text-align: start;
  transition:
    background var(--ease),
    border-color var(--ease);
}
.account-btn:hover {
  background: var(--surface-2);
  border-color: var(--border);
}
.account-btn__email {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-btn__ico {
  flex: none;
  opacity: 0.5;
}

/* A native <dialog>: the platform gives focus trapping, Escape, the backdrop and
   focus restoration on close, all of which are easy to hand-roll badly. */
.account-dialog {
  width: min(440px, calc(100vw - 2 * var(--sp-5)));
  max-height: min(86vh, 780px);
  overflow-y: auto;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-grad);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}
.account-dialog::backdrop {
  background: var(--scrim);
  /* backdrop-filter on ::backdrop is honoured where supported and ignored where not,
     so the scrim alone has to be dark enough to read against on its own. */
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.account-dialog__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.account-dialog__title {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 108;
  font-weight: 650;
  font-size: 17px;
  letter-spacing: -0.025em;
}
.account-dialog__email {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--text-subtle);
  margin-top: 3px;
  /* An address is LTR even in a right-to-left interface. */
  direction: ltr;
  unicode-bidi: isolate;
}
.account-dialog__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-subtle);
  cursor: pointer;
}
.account-dialog__close:hover {
  background: var(--surface-2);
  color: var(--text);
}
.account-section {
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
}
.account-section:last-child {
  border-bottom: 0;
}
.account-section__title {
  font-size: 12px;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-subtle);
  font-weight: 500;
  margin: 0 0 var(--sp-3);
}
.account-form {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.account-form--stack {
  display: grid;
  gap: var(--sp-3);
  align-items: stretch;
}
.account-form--stack .btn {
  justify-self: start;
}
.account-select {
  flex: 1;
  min-width: 0;
}
.account-hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-subtle);
}
/* Deactivation is the one irreversible control in here, so it is visually set
   apart rather than sitting in the same rhythm as changing a password. */
.account-section--danger {
  background: var(--danger-soft);
}
.account-section--danger .account-section__title {
  color: var(--danger);
}

/* In the collapsed rail only the avatar survives; the address and chevron would
   have nowhere to go. */
@media (min-width: 901px) {
  body.nav-rail .account-btn {
    justify-content: center;
    padding-inline: 0;
  }
  body.nav-rail .account-btn__email,
  body.nav-rail .account-btn__ico {
    display: none;
  }
}

@media print {
  .account-btn,
  .account-dialog {
    display: none !important;
  }
}

/* ---------- responsive: sidebar becomes an overlay drawer ---------- */
@media (max-width: 900px) {
  .app-shell {
    display: block;
  }
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    inset-inline-start: 0;
    width: var(--sidebar-w);
    height: 100vh;
    z-index: 60;
    /* -100% moves toward the inline START in both directions once the drawer is
       anchored with a logical inset, so this needs no RTL variant. */
    transform: translateX(-100%);
  }
  :root[dir='rtl'] body:not(.nav-open) .sidebar {
    transform: translateX(100%);
    transition: transform 0.2s ease;
    box-shadow: none;
  }
  body.nav-open .sidebar {
    transform: translateX(0);
    box-shadow: var(--shadow-md);
  }
  .sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 50;
    background: var(--scrim);
  }
  body.nav-open .sidebar-backdrop {
    display: block;
  }
  body.nav-open {
    overflow: hidden;
  }
  .rail-toggle {
    display: none;
  }
  .topbar {
    padding: 0 var(--sp-4);
  }
  .topbar__menu-btn {
    display: inline-flex;
  }
  .content {
    padding: var(--sp-4);
  }
}
@media (min-width: 901px) {
  .sidebar-backdrop {
    display: none;
  }
}

/* ---------- print ---------- */
@media print {
  .sidebar,
  .sidebar-backdrop,
  .topbar__menu-btn,
  .toast-region,
  .rail-toggle,
  .theme-toggle {
    display: none !important;
  }
  .app-shell {
    display: block;
  }
  .topbar {
    position: static;
    border-bottom: 1px solid var(--border-strong);
  }
  .content {
    max-width: none;
    padding: 0;
  }
  .card,
  .table-wrap,
  .stat {
    box-shadow: none;
  }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* --- chat --- */
/* Focused Claude/ChatGPT-style conversation: ONE centered column (780px) owns the
   page, history lives in a top-right popover, and the composer is a floating card
   pinned to the bottom of the column with the page fading behind it. */

.chat {
  display: flex;
  flex-direction: column;
  max-width: 780px;
  width: 100%;
  margin: 0 auto;
  height: calc(100vh - var(--topbar-h) - var(--sp-6) - var(--sp-6));
  min-height: 340px;
}

/* top-right History button + popover */
.chat__top {
  position: relative;
  display: flex;
  justify-content: flex-end;
  padding: 0 var(--sp-3);
  z-index: 30;
}
.chat__history-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius);
  cursor: pointer;
  transition:
    background var(--ease),
    color var(--ease);
}
.chat__history-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
.chat__history {
  position: absolute;
  top: calc(100% + 4px);
  inset-inline-end: var(--sp-3);
  width: 300px;
  max-height: 380px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--sp-2);
}
.chat__history[hidden] {
  display: none;
}
.chat__history-new {
  margin: 0 0 2px;
}
.chat__history-item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--nav-text);
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition:
    background var(--ease),
    color var(--ease);
}
.chat__history-item:hover {
  background: var(--surface-2);
  text-decoration: none;
}
.chat__history-item.is-active {
  background: var(--accent-soft);
  color: var(--accent);
}
.chat__history-item--new {
  font-weight: 600;
  color: var(--accent);
}
.chat__history-title {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat__history-meta {
  font-size: 11px;
  color: var(--text-subtle);
}
.chat__history-item.is-active .chat__history-meta {
  color: inherit;
  opacity: 0.75;
}
.chat__history-empty {
  margin: 0;
  padding: 0.3rem 0.6rem;
}

/* conversation column */
.chat__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.chat__thread {
  flex: 1;
  width: 100%;
  padding: var(--sp-2) var(--sp-3) var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* empty state: greeting + suggestion chips */
.chat__hello {
  margin: auto;
  text-align: center;
  padding: var(--sp-8) var(--sp-4);
  max-width: 560px;
  align-self: center;
}
.chat__hello-title {
  font-size: 26px;
  letter-spacing: -0.02em;
  margin-bottom: 0.35rem;
}
.chat__hello-sub {
  color: var(--text-muted);
  margin: 0;
}
.chat__suggest {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}
.chat__chip {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 999px;
  padding: 0.5rem 0.95rem;
  font: inherit;
  font-size: 13px;
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition:
    transform var(--ease),
    box-shadow var(--ease),
    color var(--ease),
    border-color var(--ease);
}
.chat__chip:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
  border-color: var(--border-strong);
  color: var(--text);
}

/* messages */
.chat-msg {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  align-items: flex-start;
}
.chat-msg--user {
  align-items: flex-end;
}
.chat-msg__bubble {
  background: var(--surface-2);
  border-radius: 16px;
  border-bottom-right-radius: 6px;
  padding: 0.6rem 0.95rem;
  max-width: 85%;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.55;
}
.chat-msg--assistant .chat-msg__text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.65;
  max-width: 100%;
}
.chat-msg__sys {
  font-size: 12px;
  font-family: var(--mono);
  color: var(--text-subtle);
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.55rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: 100%;
}
.chat-msg__file {
  font-size: 12px;
  color: var(--text-subtle);
}

/* tool chips (collapsed details in history, live pills while streaming) —
   tiny, quiet pills so the conversation text stays the star */
.chat-tool {
  font-size: 11.5px;
  color: var(--text-subtle);
  max-width: 100%;
}
.chat-tool summary {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  width: max-content;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.12rem 0.6rem;
  font-weight: 500;
  list-style: none;
}
.chat-tool summary::-webkit-details-marker {
  display: none;
}
.chat-tool summary:hover {
  border-color: var(--border-strong);
  color: var(--text);
}
.chat-json {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin: 0.35rem 0 0;
  padding: 0.5rem 0.65rem;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  overflow-x: auto;
  max-height: 260px;
  overflow-y: auto;
  white-space: pre;
}
.chat-tool-chip {
  display: inline-flex;
  width: max-content;
  max-width: 100%;
  align-items: center;
  gap: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.12rem 0.6rem;
  margin: 0.2rem 0;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-subtle);
}
.chat-tool-chip.is-busy {
  color: var(--text-muted);
}
/* subtle spinner while the tool runs */
.chat-tool-chip.is-busy::before {
  content: '';
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}

/* streaming cursor */
.chat-cursor {
  display: inline-block;
  width: 7px;
  height: 1.05em;
  vertical-align: text-bottom;
  margin-inline-start: 2px;
  border-radius: 2px;
  background: var(--accent);
  animation: chat-blink 1s steps(2, start) infinite;
}
@keyframes chat-blink {
  to {
    visibility: hidden;
  }
}

/* proposed-action cards */
.chat-action {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: var(--sp-4) var(--sp-5);
  box-shadow: var(--shadow-sm);
  max-width: 100%;
  width: 100%;
}
.chat-action--pending {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.chat-action__label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.35rem;
}
.chat-action__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.chat-action__title {
  margin: 0;
  font-size: 14px;
}
.chat-action__summary {
  margin: 0.3rem 0 0;
  font-size: 12.5px;
}
.chat-action__payload {
  margin-top: var(--sp-2);
  font-size: 12px;
}
.chat-action__payload summary {
  cursor: pointer;
  color: var(--text-muted);
}
.chat-action__forms {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.chat-action__forms form {
  margin: 0;
}
.chat-action__meta {
  margin-top: var(--sp-2);
  font-size: 12px;
  color: var(--text-subtle);
}

/* stream error note */
.chat-error {
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius);
  padding: 0.5rem 0.8rem;
  font-size: 0.9rem;
  max-width: 640px;
}

/* composer — the signature element: a floating card pinned bottom-center with the
   page fading behind it (the ::before gradient paints over the scroll area above) */
.chat__composer-outer {
  position: relative;
  z-index: 5;
  width: 100%;
  margin: 0 auto;
  padding: 0 var(--sp-3) var(--sp-2);
}
.chat__composer-outer::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: -34px;
  height: 34px;
  background: linear-gradient(to bottom, var(--bg-fade), var(--bg));
  pointer-events: none;
}
.chat__composer {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: var(--shadow-md);
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: 0;
  transition:
    border-color var(--ease),
    box-shadow var(--ease);
}
.chat__composer:focus-within {
  border-color: var(--border-strong);
  box-shadow:
    var(--shadow-md),
    0 0 0 3px var(--accent-soft);
}
.chat__composer textarea {
  font-family: var(--font);
  font-size: 0.98rem;
  line-height: 1.5;
  min-height: 0;
  max-height: 190px;
  border: none;
  background: transparent;
  padding: 0.2rem 0.4rem;
  resize: none;
  overflow-y: auto;
}
.chat__composer textarea:focus {
  border: none;
  box-shadow: none;
}
.chat__composer-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.chat__composer-spacer {
  flex: 1;
  min-width: 0;
}
.chat__attach-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background var(--ease),
    color var(--ease);
}
.chat__attach-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
.chat__hint {
  margin: 0.45rem 0 0;
  text-align: center;
  font-size: 11px;
  color: var(--text-subtle);
}
.chat__model {
  width: auto;
  flex: none;
  font-size: 12px;
  padding: 0.28rem 0.35rem;
  color: var(--text-muted);
  border: none;
  background: transparent;
  cursor: pointer;
}
.chat__model:focus {
  border: none;
  box-shadow: none;
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.chat__send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  cursor: pointer;
  transition:
    background var(--ease),
    opacity var(--ease);
}
.chat__send:hover {
  background: var(--accent-hover);
}
.chat__send[disabled] {
  opacity: 0.5;
  cursor: default;
}

/* slim inline "AI is not configured" notice above the composer */
.chat__notice {
  font-size: 12.5px;
  color: var(--warning);
  background: var(--warning-soft);
  border: 1px solid var(--warning-border);
  border-radius: var(--radius);
  padding: 0.4rem 0.7rem;
  margin-bottom: var(--sp-2);
}
.chat__notice a {
  font-weight: 600;
}
.chat__attach-row {
  display: flex;
  padding: 0 0.15rem;
}
/* display:flex would otherwise defeat the hidden attribute the JS toggles. */
.chat__attach-row[hidden] {
  display: none;
}
.chat__attach-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 12px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  padding: 0.15rem 0.35rem 0.15rem 0.6rem;
  max-width: 100%;
}
.chat__attach-chip [data-chat-filename] {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat__attach-chip button {
  border: none;
  background: transparent;
  cursor: pointer;
  color: inherit;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  padding: 0 0.15rem;
}

/* visually hidden (file input + its label text stay accessible) */
.chat-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 900px) {
  .chat {
    height: calc(100vh - var(--topbar-h) - var(--sp-4) - var(--sp-4));
  }
  .chat__history {
    inset-inline-end: var(--sp-2);
    width: min(300px, calc(100vw - var(--sp-6)));
  }
  .chat__hello-title {
    font-size: 22px;
  }
  .chat__hint {
    display: none;
  }
}

/* --- floating assistant button (every authenticated page except the chat) --- */
/* ---------- acting as a customer ----------
   A safety device. A vendor operator inside a customer's account sees a screen that is
   otherwise identical to their own, and the cost of mis-reading it is a conversion, a
   freeze or a delivery in somebody else's name.

   So: sticky (true at every scroll position, not just at the top of the page), warning
   coloured, and reinforced by an outline drawn around the whole viewport — peripheral
   vision notices a frame long before it reads a bar. */
.acting-bar {
  position: sticky;
  top: var(--topbar-h);
  z-index: 15;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: calc(var(--sp-6) * -1) calc(var(--sp-8) * -1) var(--sp-6);
  padding: 10px var(--sp-8);
  background: var(--warning-soft);
  border-bottom: 1px solid var(--warning-border);
  color: var(--warning);
  font-size: 12.5px;
  line-height: 1.35;
}
.acting-bar__ico {
  font-size: 1rem;
  flex: none;
}
.acting-bar__text {
  min-width: 0;
}
.acting-bar__leave {
  margin-inline-start: auto;
  flex: none;
}
/* The frame. `outline` rather than a border so it costs no layout, and fixed so it
   follows the viewport rather than the document. */
body.is-acting::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 40;
  outline: 3px solid var(--warning-border);
  outline-offset: -3px;
}
@media (max-width: 720px) {
  .acting-bar {
    flex-wrap: wrap;
  }
  .acting-bar__leave {
    margin-inline-start: 0;
  }
}

.chat-fab {
  position: fixed;
  inset-inline-end: var(--sp-5);
  bottom: var(--sp-5);
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  box-shadow: var(--shadow-md);
  transition:
    transform var(--ease),
    background var(--ease);
}
.chat-fab:hover {
  transform: scale(1.07);
  background: var(--accent-hover);
  text-decoration: none;
}
@media (prefers-reduced-motion: reduce) {
  .chat-fab,
  .chat-fab:hover {
    transform: none;
  }
}
@media (max-width: 900px) {
  .chat-fab {
    inset-inline-end: var(--sp-4);
    bottom: var(--sp-4);
    width: 48px;
    height: 48px;
  }
  /* keep toasts clear of the FAB (the FAB precedes the toast region in the DOM) */
  .chat-fab ~ .toast-region {
    bottom: calc(var(--sp-4) + 60px);
  }
}
@media print {
  .chat-fab {
    display: none !important;
  }
}

/* --- dashboard: the assistant panel ------------------------------------------
   The product's differentiator, so it gets the weight of the page's primary action:
   a full-width lit panel above the metrics rather than a card below them. It is still
   a plain GET form to /chat — the CSP (`script-src 'self'`, no 'unsafe-inline')
   forbids the inline handler a live composer here would need, and the composer on
   /chat is the real one anyway. */
.assistant-hero {
  position: relative;
  overflow: hidden;
  margin-top: var(--sp-4);
  padding: var(--sp-6);
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-lg);
  background: var(--hero-grad);
  box-shadow:
    var(--shadow-sm),
    0 0 0 4px var(--hero-glow);
}
/* The lit top edge every raised surface in this system carries. */
.assistant-hero::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hairline), transparent);
  pointer-events: none;
}
.assistant-hero__body {
  position: relative;
  max-width: 760px;
}
.assistant-hero__eyebrow {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--accent-ink);
  font-weight: 500;
  margin: 0 0 var(--sp-2);
}
.assistant-hero__title {
  font-family: var(--font-display);
  font-size: 26px;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 var(--sp-2);
}
.assistant-hero__sub {
  margin: 0 0 var(--sp-4);
  font-size: 13.5px;
  color: var(--text-muted);
  max-width: 62ch;
}
.assistant-hero__form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.assistant-hero__form input {
  flex: 1 1 320px;
  min-width: 0;
  height: 42px;
  font-size: 14px;
  background: var(--surface);
}
.assistant-hero__form .btn {
  flex: 0 0 auto;
  height: 42px;
  padding-inline: var(--sp-6);
}
.assistant-hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.assistant-hero__chip {
  display: inline-block;
  padding: 4px 10px;
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
  transition:
    color var(--ease),
    border-color var(--ease);
}
.assistant-hero__chip:hover {
  color: var(--accent-ink);
  border-color: var(--accent);
  text-decoration: none;
}

/* --- dashboard: the metrics band --- */
.metrics {
  margin-top: var(--sp-6);
}
.metrics__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.metrics__title {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--text-subtle);
  font-weight: 500;
  margin: 0;
}
/* The mapping counts. They are a STATE, not a trend — a sparkline of "3 draft" would
   draw a line through numbers that never moved — so they sit here as one quiet line
   rather than taking a chart tile from a metric that does move. */
.metrics__note {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  margin: var(--sp-3) 0 0;
  font-size: 12px;
  color: var(--text-subtle);
}
.metrics__note span + span {
  position: relative;
}
.metrics__note span + span::before {
  content: '·';
  position: absolute;
  inset-inline-start: calc(var(--sp-4) / -2 - 2px);
}

/* Four across on a wide console, two on a laptop, one on a phone — an EXPLICIT
   column count rather than auto-fit. auto-fit off a 190px minimum lands on three
   columns at common laptop widths, and an orphaned fourth tile on its own row reads
   as a layout fault rather than as a fourth metric. */
.grid--metrics {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1100px) {
  .grid--metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 560px) {
  .grid--metrics {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- metric tile: number, move, trend --- */
.stat--metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.stat__row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.stat__row .stat__value {
  flex: 0 0 auto;
}
/* The chart is context, never the fact: it sits UNDER the number and takes a fixed
   band, so a tile whose series is all zeros is the same height as one that moves. */
.stat__chart {
  margin-top: var(--sp-3);
  height: 34px;
}

/* The period-over-period move. Colour is never the only signal — the arrow glyph and
   the wording ("12 vs Jul") say the same thing without it. */
.delta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--grey-border);
  background: var(--grey-soft);
  color: var(--grey);
  white-space: nowrap;
}
.delta__arrow {
  font-size: 9px;
  line-height: 1;
}
.delta--good {
  border-color: var(--success-border);
  background: var(--success-soft);
  color: var(--success);
}
.delta--bad {
  border-color: var(--danger-border);
  background: var(--danger-soft);
  color: var(--danger);
}

/* --- sparklines (server-rendered inline SVG; see views/components.ts) --- */
.spark {
  display: block;
  width: 100%;
  height: 100%;
}
.spark__area {
  fill: var(--spark-fade);
  stroke: none;
}
.spark__line {
  fill: none;
  stroke: var(--grey);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* A zero-length subpath with a round cap: a perfectly round dot that survives the
   non-uniform scaling `preserveAspectRatio="none"` applies, which a <circle> would not. */
.spark__last {
  fill: none;
  stroke: var(--grey);
  stroke-width: 5;
  stroke-linecap: round;
}
.spark--accent .spark__line,
.spark--accent .spark__last {
  stroke: var(--accent);
}
.spark--accent .spark__area {
  fill: var(--accent-soft);
}
.spark--success .spark__line,
.spark--success .spark__last {
  stroke: var(--success);
}
.spark--success .spark__area {
  fill: var(--success-soft);
}
.spark--danger .spark__line,
.spark--danger .spark__last {
  stroke: var(--danger);
}
.spark--danger .spark__area {
  fill: var(--danger-soft);
}
.spark--info .spark__line,
.spark--info .spark__last {
  stroke: var(--info);
}
.spark--info .spark__area {
  fill: var(--info-soft);
}

@media (max-width: 900px) {
  .assistant-hero {
    padding: var(--sp-5);
  }
  .assistant-hero__title {
    font-size: 21px;
  }
  .assistant-hero__form input {
    flex: 1 1 100%;
  }
  .assistant-hero__form .btn {
    flex: 1 1 auto;
  }
}
@media (max-width: 560px) {
  /* Below this the tiles are one per row, and a full-width chart band would push the
     fourth number off the fold. The chart keeps its meaning at half the height. */
  .stat__chart {
    height: 26px;
  }
  .assistant-hero__chips {
    display: none;
  }
}
@media print {
  .assistant-hero {
    display: none !important;
  }
}

/* ============================================================================
   HEBREW AND RIGHT-TO-LEFT
   Appended last so it wins without editing the rules above, and so the whole
   RTL story is readable in one place rather than scattered through the file.
   ========================================================================== */

/* --- 1. Hebrew faces ---------------------------------------------------------
   Archivo and IBM Plex Sans contain NO Hebrew glyphs. Left alone, a Hebrew
   interface silently falls back to whatever the OS supplies and loses the entire
   visual identity — the same problem the /plan document solved, with the same
   answer: Heebo is the engineered grotesque closest to Archivo, Assistant is the
   most readable Hebrew body face at length. Redefining the two font tokens under
   [lang='he'] swaps the whole app's typography in one place.
   IBM Plex Mono is deliberately NOT overridden: figures are Latin digits and
   stay Latin, which is what keeps a money column aligned. */
@font-face {
  font-family: 'Heebo';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('/static/fonts/heebo-var-hebrew.woff2') format('woff2');
  unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face {
  font-family: 'Assistant';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/static/fonts/assistant-var-hebrew.woff2') format('woff2');
  unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
:root[lang='he'] {
  --font-display: 'Heebo', 'Archivo', system-ui, sans-serif;
  --font: 'Assistant', 'IBM Plex Sans', system-ui, sans-serif;
}
/* Hebrew has no capitals, so the uppercase-plus-wide-tracking treatment used on
   every label produces noise rather than emphasis: text-transform does nothing
   and the tracking just pulls the letters apart. */
:root[lang='he'] .stat__label,
:root[lang='he'] .nav__group-label,
:root[lang='he'] .table th,
:root[lang='he'] .badge,
:root[lang='he'] .account-section__title,
:root[lang='he'] .sidebar__brand small,
:root[lang='he'] .brand-text small {
  letter-spacing: 0.02em;
}
/* Heebo's counters need marginally more air than Archivo's tight display setting. */
:root[lang='he'] h1,
:root[lang='he'] h2,
:root[lang='he'] h3,
:root[lang='he'] .topbar__title {
  letter-spacing: -0.005em;
  font-variation-settings: normal;
}

/* --- 2. Bidi isolation — THE critical rule -----------------------------------
   A Hebrew sentence containing a Latin run reorders it. "[VALIDATED]" loses its
   brackets to opposite ends, "JO/12951" comes out as "12951/JO", an email address
   breaks at the @ and a decimal amount moves its sign. None of this is a font
   problem and none of it is fixed by translating more — the runs below are
   EXACTLY the things Adam confirmed must stay in English, so they are precisely
   the ones that would be mangled.
   `unicode-bidi: isolate` with `direction: ltr` pins each run to its own LTR
   embedding without affecting the block's alignment. */
.mono,
code,
.badge,
.stat__value,
.num,
.avatar,
.account-dialog__email,
.account-btn__email,
.topbar__email,
.copy-row__input,
.cell-input,
.table td.num,
.table th.num {
  direction: ltr;
  unicode-bidi: isolate;
}
/* Tables of Amital output are a fixed LTR artefact — column order is the CSV's
   column order, and mirroring it would misrepresent the file. */
.table--csv,
.table--csv th,
.table--csv td {
  direction: ltr;
  text-align: left;
}
/* The wordmark is Latin and must not be reordered by the Hebrew around it. */
.brand-text,
.account-dialog__title {
  unicode-bidi: isolate;
}

/* --- 3. Direction-dependent glyphs ------------------------------------------
   The nav chevron points along the reading direction, so it mirrors. The rest of
   the icon set is symmetrical or non-directional and must NOT be flipped —
   mirroring a document or shield icon just makes it look wrong. */
:root[dir='rtl'] .nav__chevron {
  transform: scaleX(-1);
}
:root[dir='rtl'] .nav__group.is-open > .nav__group-btn .nav__chevron {
  transform: scaleX(-1) rotate(90deg);
}
:root[dir='rtl'] .account-btn__ico {
  transform: scaleX(-1);
}

/* --- 4. Gradients and shadows that carry a direction ------------------------- */
:root[dir='rtl'] .assistant-hero::before,
:root[dir='rtl'] .card::before,
:root[dir='rtl'] .stat::before,
:root[dir='rtl'] .table-wrap::before {
  /* A symmetrical fade, so the 90deg gradient is identical mirrored — declared
     anyway so a future asymmetric bevel does not silently point the wrong way. */
  background: linear-gradient(270deg, transparent, var(--hairline), transparent);
}
