/* ==========================================================================
   Xeetek Customer Portal — design system + application shell
   ==========================================================================
   Structure: tokens → base → shell → components → page modules → motion →
   responsive → print.

   Two rules keep this file maintainable:

   1. No component ever names a raw colour. Every surface, line and text
      colour comes from a token, which is what makes the dark theme a
      ~90-line override at the top instead of a second stylesheet.
   2. Class names are a contract with the Razor pages. Anything renamed here
      has to be renamed in the views, so the vocabulary stays stable and new
      treatments are added as tokens or modifiers rather than new names.
   ========================================================================== */

/* ===================================================================== fonts
   Self-hosted, because the CSP is `font-src 'self' data:` — Google Fonts and every
   other remote host is blocked, by design. Both files are variable (one weight axis)
   and latin-subset, so the whole type system costs 71 KB and no weight ever has to
   be synthesised.

   Inter for the interface: made for screens, tall x-height, unambiguous 1/l/I and
   0/O, which is what dense case tables and reference numbers need.
   Space Grotesk for display: geometric with a slight technical edge, so headings and
   the big metrics sit with the XEETEK wordmark instead of ignoring it.

   font-display: optional, paired with a <link rel="preload"> in the two layouts.
   `swap` renders the page in the fallback and then reflows every heading and metric
   once the woff2 lands — visible movement on every cold load, and the worst kind,
   because it happens just as the reader starts reading. `optional` never reflows: the
   browser gives the font ~100ms, and the preload means it has almost always arrived
   inside that window. On a connection slow enough to miss it, that one page renders
   in the fallback stack and every page after it has the real font from cache.

   The preload href must match the url() below exactly — no cache-busting query — or
   the browser downloads each file twice. */
@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 100 900;
  font-display: optional;
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
}
@font-face {
  font-family: "Space Grotesk Variable";
  font-style: normal;
  font-weight: 300 700;
  font-display: optional;
  src: url("../fonts/space-grotesk-latin-wght-normal.woff2") format("woff2-variations");
}

/* ==================================================================== tokens */
:root {
  color-scheme: light;

  /* -- brand ------------------------------------------------------------
     The two company colours, and the only place they are written down:
       orange  #EC5F24  --brand (and --brand-logo, its un-themed twin)
       navy    #062643  --navy
     Everything below is derived from them. The logo artwork in wwwroot/img uses exactly
     these two values, so the mark and the interface never drift apart. */
  --brand: #ec5f24;
  --brand-600: #cf4d16;
  --brand-700: #a93c0f;
  --brand-2: #f2941f;
  --brand-soft: #fdeee7;
  --brand-soft-2: #fbe0d4;
  --brand-line: #f8ccb6;
  --brand-ink: var(--brand-600);
  /* Vertical and tight (≈6% luminance range): a gradient should read as a light
     source falling on the surface, not as decoration. A wide diagonal ramp is what
     makes a filled button look like a 2012 web button. */
  --brand-grad: linear-gradient(180deg, #f0682f 0%, #e5561d 100%);
  --brand-grad-hover: linear-gradient(180deg, #f3753f 0%, #ec5f24 100%);
  /* 1px edge one step darker than the bottom of the fill. This is what separates a
     crisp filled button from a flat rectangle of colour — without it no amount of
     shadow tuning reads as "solid object". */
  --brand-edge: #bf4512;
  --brand-glow: 0 6px 14px -6px rgba(191, 69, 18, .28);
  --brand-glow-lg: 0 8px 20px -6px rgba(191, 69, 18, .38);
  /* The orange in the logo artwork. It is the same value as --brand: the artwork and the
     interface are one palette, so a mark sitting beside a button is the same orange as the
     button. Kept as its own token because it must NOT follow the theme — it only appears
     on the fixed dark auth backdrop, where the dark theme's lighter brand would drift away
     from the wordmark printed next to it. */
  --brand-logo: #ec5f24;

  --navy: #062643;
  --navy-700: #0b3457;
  --navy-600: #13436b;
  --navy-line: #1c4f7a;
  /* Navy as a data colour (rings, bars). On dark surfaces the shell navy would vanish, so the
     dark theme lifts it to a light tint of the same hue instead of falling back to generic blue. */
  --navy-accent: #062643;
  --navy-accent-2: #0f4a7a;
  --navy-accent-glow: rgba(6, 38, 67, .28);

  /* -- neutrals ---------------------------------------------------------
     Surfaces carry a slight warmth so they sit with the terracotta instead
     of fighting it; the text ramp stays neutral so body copy reads clean. */
  /* --muted is the secondary text colour and clears 4.5:1 on every surface it lands on
     (4.7:1 on the page, 5.2:1 on a card). It used to be #6e7288, which measured 4.32:1 —
     under AA for anything below 18.66px, which is every line it is used on.

     --muted-2 is a third, quieter step. On a near-white page no colour can be both
     visibly lighter than --muted and clear 4.5:1, so it is not a text colour: it is for
     placeholders, field icons, separators and disabled chrome, and it clears the 3:1 that
     non-text UI needs. Anything carrying words uses --muted. */
  --ink: #16182b;
  --ink-2: #3d4256;
  --muted: #686c82;
  --muted-2: #7f8398;

  --line: #e7e4e0;
  --line-2: #f1efec;
  --line-3: #faf9f7;

  --bg: #f5f4f2;
  --bg-2: #edebe8;
  --card: #ffffff;
  --card-2: #fbfaf9;
  --card-3: #f6f5f3;
  --overlay: rgba(19, 21, 51, .48);

  /* Chrome that must stay light-on-light in both themes (buttons, inputs). */
  --input-bg: #ffffff;
  --input-line: #d9d5d0;
  --input-line-hover: #bdb8b1;
  --btn-face: #ffffff;
  --btn-face-hover: #f5f4f2;
  /* Buttons get a face that falls top to bottom rather than one flat colour —
     the same reason the primary is a gradient. A flat rectangle with a border
     around it is the cheapest-looking control there is. */
  --btn-face-grad: linear-gradient(180deg, #ffffff 0%, #f4f2ef 100%);
  --btn-face-grad-hover: linear-gradient(180deg, #fbfaf8 0%, #eceae6 100%);

  /* -- semantic ---------------------------------------------------------- */
  --blue: #2563eb;   --blue-ink: #1d4ed8;  --blue-soft: #eff5ff;  --blue-line: #c3dafe;
  --green: #15803d;  --green-ink: #15803d; --green-soft: #edfdf3; --green-line: #b7f5cb;
  --amber: #b45309;  --amber-ink: #92400e; --amber-soft: #fffaeb; --amber-line: #fce18f;
  --red: #b91c1c;    --red-ink: #991b1b;   --red-soft: #fef3f2;   --red-line: #fdc9c4;
  /* S1 - Critical: the only severity that gets a filled surface rather than a tint, so it needs its
     own fill/ink pair. --red is a text colour in the dark theme (light on dark) and cannot be a fill. */
  --crit: #b31818;   --crit-ink: #ffffff;  --crit-line: #8f1212;
  --violet: #6d28d9; --violet-soft: #f5f3ff;
  --slate-soft: #f1efec;
  --mark: #fde68a;
  --mark-ink: #422006;

  /* -- shape & elevation ------------------------------------------------
     Shadows are tinted with the ink colour rather than pure black: a warm
     grey page under a black shadow looks dirty, under a tinted one it
     looks lit. Two layers each — a tight contact shadow plus a wide soft
     one — which is what separates "has a box-shadow" from "sits above". */
  --r-xs: 6px;
  --r-sm: 9px;
  --r: 12px;
  --r-lg: 16px;
  --r-xl: 20px;
  --r-2xl: 26px;
  --r-full: 999px;

  --shadow-sm: 0 1px 2px rgba(22, 24, 43, .05), 0 1px 1px rgba(22, 24, 43, .03);
  --shadow: 0 2px 4px rgba(22, 24, 43, .04), 0 12px 28px -10px rgba(22, 24, 43, .12);
  --shadow-md: 0 4px 10px rgba(22, 24, 43, .05), 0 22px 44px -14px rgba(22, 24, 43, .16);
  --shadow-lg: 0 30px 70px -18px rgba(13, 15, 30, .34), 0 8px 20px -10px rgba(13, 15, 30, .2);
  --focus-ring: 0 0 0 3px rgba(236, 95, 36, .26);

  /* -- controls ---------------------------------------------------------
     Buttons are built from four layers, and each one is doing a job:
       sheen   a faint top highlight (not a bevel — .6 alpha reads as plastic)
       contact a 1-2px dark shadow that glues the object to the page
       lift    a wider, softer shadow that puts it above the page
       press   an inset shadow on :active, so the control feels physical
     Filled and outline variants share the same four, which is what makes a row
     of mixed buttons look like one set rather than three. */
  --btn-sheen: inset 0 1px 0 rgba(255, 255, 255, .16);
  --btn-sheen-light: inset 0 1px 0 rgba(255, 255, 255, .9);
  --btn-contact: 0 1px 2px rgba(22, 24, 43, .1);
  --btn-lift: 0 1px 2px rgba(22, 24, 43, .1), 0 3px 6px -2px rgba(22, 24, 43, .07);
  --btn-lift-hover: 0 2px 4px rgba(22, 24, 43, .1), 0 8px 16px -6px rgba(22, 24, 43, .14);
  --btn-press: inset 0 2px 4px rgba(22, 24, 43, .16);
  --btn-press-fill: inset 0 2px 5px rgba(92, 30, 8, .3);
  --btn-h: 40px;
  --btn-h-sm: 32px;
  --btn-h-lg: 48px;

  /* -- layout ------------------------------------------------------------
     --gutter and --card-pad are the two spacing dials the responsive block turns.
     Every page padding and card padding is expressed in them, so one value per
     breakpoint re-tunes density across the whole portal instead of a media query
     per component. --bottom-nav-h is 0 except on phones, where the fixed bottom
     navigation needs the page to reserve room under itself. */
  --sb-w: 306px;
  --sb-w-rail: 76px;
  --topbar-h: 66px;
  --content-max: 1360px;
  --gutter: 30px;
  --card-pad: 20px;
  --bottom-nav-h: 0px;

  /* -- type --------------------------------------------------------------
     The ramp is set once here and bumped as a block at >=1600px (see the
     responsive section). Steps are ~1.13x apart: close enough that two adjacent
     sizes read as one system, far enough that a heading never looks like bold
     body copy. */
  --font: "Inter Variable", system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-display: "Space Grotesk Variable", "Inter Variable", system-ui, -apple-system, "Segoe UI Variable Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;

  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 14.25px;
  --fs-md: 15.5px;
  --fs-lg: 18px;
  --fs-xl: 21px;
  --fs-2xl: 26px;
  --fs-3xl: 32px;

  /* -- motion — one vocabulary so every transition in the portal agrees */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-spring: cubic-bezier(.34, 1.4, .64, 1);
  --t-fast: 120ms;
  --t: 200ms;
  --t-slow: 340ms;
}

/* -------------------------------------------------------------------- dark
   Only tokens are redefined. Soft semantic fills become translucent so they
   tint whatever surface they land on instead of punching a pale hole in it. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --brand: #ef6a35;
  --brand-600: #f37a45;
  --brand-700: #ec5f24;
  --brand-2: #f8ab3c;
  --brand-soft: rgba(239, 106, 53, .16);
  --brand-soft-2: rgba(239, 106, 53, .24);
  --brand-line: rgba(239, 106, 53, .38);
  --brand-ink: #f9a583;
  /* The fill stays the same brightness in both themes, so the sheen and edge carry
     over unchanged; only the neutral shadows go darker. */
  --brand-grad: linear-gradient(180deg, #ec6a38 0%, #dd5526 100%);
  --brand-grad-hover: linear-gradient(180deg, #f2764a 0%, #e5602f 100%);
  --brand-edge: #b8451c;
  --brand-glow: 0 6px 16px -6px rgba(0, 0, 0, .5);
  --brand-glow-lg: 0 8px 22px -6px rgba(0, 0, 0, .6);

  --navy: #051b30;
  --navy-700: #092b48;
  --navy-600: #11395c;
  --navy-line: #1a466c;
  --navy-accent: #8ab8e8;
  --navy-accent-2: #b3d2f2;
  --navy-accent-glow: rgba(138, 184, 232, .3);

  --ink: #edeff7;
  --ink-2: #c4c9dd;
  --muted: #8f96b4;
  --muted-2: #7a81a1;

  --line: #272c47;
  --line-2: #1f2338;
  --line-3: #1a1e31;

  --bg: #0c0e1b;
  --bg-2: #101324;
  --card: #15182b;
  --card-2: #1a1e33;
  --card-3: #1f233b;
  --overlay: rgba(4, 5, 14, .68);

  --input-bg: #1a1e33;
  --input-line: #2d3352;
  --input-line-hover: #3d4468;
  --btn-face: #1c2036;
  --btn-face-hover: #242942;
  --btn-face-grad: linear-gradient(180deg, #232841 0%, #191d31 100%);
  --btn-face-grad-hover: linear-gradient(180deg, #2b3153 0%, #20253c 100%);

  --blue: #7ba7ff;   --blue-ink: #a8c4ff;  --blue-soft: rgba(59, 130, 246, .16);  --blue-line: rgba(96, 165, 250, .32);
  --green: #5fd08a;  --green-ink: #86e0a8; --green-soft: rgba(34, 197, 94, .16);  --green-line: rgba(74, 222, 128, .3);
  --amber: #f3c05f;  --amber-ink: #f7d089; --amber-soft: rgba(245, 158, 11, .15); --amber-line: rgba(251, 191, 36, .3);
  --red: #ff8f8a;    --red-ink: #ffb0ac;   --red-soft: rgba(239, 68, 68, .16);    --red-line: rgba(248, 113, 113, .32);
  --crit: #a11d1d;   --crit-ink: #ffe6e3;  --crit-line: #c93636;
  --violet: #b39cff; --violet-soft: rgba(139, 92, 246, .16);
  --slate-soft: rgba(255, 255, 255, .06);
  --mark: rgba(250, 204, 21, .3);
  --mark-ink: #fde68a;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow: 0 2px 4px rgba(0, 0, 0, .3), 0 14px 30px -12px rgba(0, 0, 0, .6);
  --shadow-md: 0 4px 10px rgba(0, 0, 0, .35), 0 24px 48px -16px rgba(0, 0, 0, .7);
  --shadow-lg: 0 34px 76px -20px rgba(0, 0, 0, .8), 0 10px 24px -12px rgba(0, 0, 0, .6);
  --focus-ring: 0 0 0 3px rgba(239, 106, 53, .34);
}

/* ==================================================================== base */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; background: var(--bg); }
body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease);
}
h1, h2, h3, h4 {
  margin: 0; line-height: 1.24; color: var(--ink);
  font-family: var(--font-display); letter-spacing: -.011em;
  /* Headings are short and set tight; balancing beats a one-word last line. */
  text-wrap: balance;
}
h1 { font-size: var(--fs-2xl); font-weight: 700; letter-spacing: -.018em; }
h2 { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.013em; }
h3 { font-size: var(--fs-md); font-weight: 700; }
p { margin: 0; }
/* Links never underline — not at rest and not on hover. A hover underline on a
   shell that is already built out of plates, pills and buttons reads as a stray
   rule rather than affordance, so hover is carried by colour alone. Prose links
   inside .article-body keep their underline: there the surrounding text has no
   other cue. */
a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-ink); text-decoration: none; }
/* …except a link sitting inside a sentence, which needs more than colour to be findable
   (WCAG 1.4.1: the blue against the surrounding grey is only 1.9:1, well under the 3:1
   that colour-alone distinction would require). A link that is its own object — a nav
   item, a card action, a pill, a button — is distinguished by shape and position and
   keeps the clean treatment. */
p a:not(.btn), li a:not(.btn), .alert a:not(.btn), .help a, dd a:not(.btn) {
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
}
p a:not(.btn):hover, li a:not(.btn):hover, .alert a:not(.btn):hover, .help a:hover, dd a:not(.btn):hover {
  text-decoration-color: currentColor;
}
img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
[hidden] { display: none !important; }
::selection { background: var(--brand-soft-2); color: var(--ink); }
:root[data-theme="dark"] ::selection { color: #fff; }

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -48px; z-index: 1000;
  background: var(--card); color: var(--ink); padding: 10px 14px;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  font-weight: 600; box-shadow: var(--shadow-md); transition: top var(--t) var(--ease-out);
}
.skip-link:focus { top: 0; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

/* scrollbars — the default light scrollbar on a dark shell is jarring */
* { scrollbar-color: var(--line) transparent; scrollbar-width: thin; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line); border: 3px solid transparent; background-clip: content-box; border-radius: var(--r-full); }
*::-webkit-scrollbar-thumb:hover { background: var(--muted-2); background-clip: content-box; }

/* ------------------------------------------------------------- utilities */
.muted { color: var(--muted); }
.small { font-size: var(--fs-sm); }
.mono { font-family: var(--mono); font-size: var(--fs-sm); }
.nums { font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; } .mt-4 { margin-top: 32px; }
.mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 24px; }
.flex { display: flex; gap: 10px; align-items: center; }
.flex-wrap { flex-wrap: wrap; }
.between { justify-content: space-between; }
.grow { flex: 1; min-width: 0; }
/* A stack is a list of things, so its rows keep their own height. Without this the
   grid stretches them to fill whatever the tallest column beside it happens to be, and
   a two-card sidebar next to a long form ends up as two half-empty cards. */
.stack { display: grid; gap: 12px; align-content: start; }
.right { text-align: right; }
.center { text-align: center; }
.eyebrow {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; color: var(--muted);
}
.clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* =================================================================== shell */
.app { display: block; }

/* ------------------------------------------------------------- sidebar
   A vertical gradient plus a brand-tinted glow behind the logo, so the rail
   has a light source instead of reading as a flat block of navy. */
.sidebar {
  position: fixed; inset: 0 auto 0 0; width: var(--sb-w); z-index: 60;
  display: flex; flex-direction: column;
  color: #fff;
  background:
    radial-gradient(100% 26% at 6% 0%, rgba(236, 95, 36, .26), transparent 68%),
    radial-gradient(120% 40% at 0% 0%, rgba(236, 95, 36, .14), transparent 62%),
    linear-gradient(180deg, var(--navy-700) 0%, var(--navy) 42%, #031524 100%);
  border-right: 1px solid var(--navy-line);
  transition: width var(--t-slow) var(--ease-out);
  /* The collapsed (rail) treatment below is written against the sidebar's own
     width rather than the viewport, so one copy of it covers both ways the rail
     is reached: the topbar toggle on desktop, and a narrow screen. */
  container-type: inline-size;
}
/* The brand block. Three things were making a precise technical wordmark read as
   machine output rather than a logo:
     - it was drawn at 142px from a 120px source, so every hairline landed between
       device pixels and came out grey and brittle;
     - the service line under it was 10.5px uppercase at 700 with .15em tracking —
       the typographic setting of a warning label, shouting a phrase nobody needs
       shouted;
     - the block was closed off by a hard full-width rule, boxing the mark into the
       corner with no light on it.
   So: a size whose bars clear a device pixel, a quiet sentence-case service line
   led by a single brand dot, and a rule that fades out before it reaches the edge. */
/* The lockup sits on one line and is centred in a band the height of the topbar, so the
   sidebar's brand and the page's title bar share a baseline across the whole screen.

   The mark is drawn at 136px from the 120px source. Enlarging it is safe here even though
   scaling once wasn't: the artwork is all fills, and its letterforms are 0.92px bars at
   native size, so at 112px they were 0.86px — thinner than a device pixel and grey for it.
   Scaling up takes them past 1px, which is why this reads crisper than the smaller setting
   did, not softer.

   That needed room the 268px rail did not have: at 268 the lockup overflowed its own row and
   flexbox was quietly shrinking the mark back down to 120px. Hence --sb-w at 306 and 16px of
   side padding, which leaves the descriptor about 6px of slack rather than -2. The rail has grown
   twice more since, both times to buy width for the descriptor, never for the mark. */
.sb-brand {
  display: flex; align-items: center; gap: 13px;
  padding: 0 16px; min-height: var(--topbar-h);
  position: relative;
}
/* "Customer Service", set beside the mark behind a hairline.

   Space Grotesk, not Inter. Inter was tried on the reasoning that the geometric face would
   compete with the wordmark, but next to a mark this size it was the opposite: a plain
   interface face beside a drawn wordmark reads as a UI string that happens to sit there, not
   as part of the lockup. The display face shares the wordmark's own construction, so the two
   look related without the descriptor trying to be a second logo.

   450 rather than 600 is what keeps it in its place now that the face matches — weight is
   doing the work size used to do. Tracked caps read better still and are one line away, but
   caps set to balance a 136px mark run much wider than this, and every millimetre of descriptor
   width is paid for out of the rail.

   The colour stays at .58 alpha — 5.3:1 on this navy, which passes AA. It is a white carrying
   a trace of the brand orange rather than a neutral grey, so it sits with the mark's squares. */
.sb-brand .tagline {
  display: block; margin: 0 0 1px; white-space: nowrap; position: relative;
  padding-left: 9px;
  font-family: var(--font-display);
  font-size: 13px; font-weight: 450; letter-spacing: .015em; line-height: 1.25;
  color: rgba(255, 244, 238, .58);
}
.sb-brand .tagline::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 1px; width: 1px;
  background: rgba(255, 255, 255, .22);
}
.sb-brand::after {
  content: ""; position: absolute; inset: auto 16px 0 16px; height: 1px;
  background: linear-gradient(90deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, .02) 78%, transparent);
}
/* Wordmark and descriptor side by side, bottoms aligned, divided by a hairline. Stacking
   them was the problem: two left-aligned blocks of the same width, one under the other, is
   the shape of a machine label whatever the type does. Set side by side the mark clearly
   leads and the descriptor reads as what it is — the name of this particular portal. */
.sb-brand > a { display: flex; align-items: flex-end; gap: 9px; min-width: 0; }
.sb-brand .sb-wordmark {
  width: 136px; height: auto; flex: none;
  /* No drop-shadow. A glow behind a hairline wordmark reads as a print artefact rather
     than a light source — the mark is drawn the way it was supplied. */
  transition: width var(--t-slow) var(--ease-out);
}

/* Mark shown in place of the wordmark when the rail is collapsed — the logo's own X
   glyph, rather than the letter X set in the UI typeface. The plate is navy with a warm
   ring, not a solid orange tile: the glyph carries an orange square of its own, and on
   orange that square disappears into the background. */
/* No coloured halo and no orange ring — the glyph's own square is the only colour the tile
   needs, the same reasoning that took the glow off the wordmark. The border is a neutral
   hairline and the shadow is the shared contact shadow, so the plate reads as an object
   sitting on the rail rather than as something lit from behind. */
.sb-mark {
  display: none; width: 38px; height: 38px; flex: none; border-radius: 12px;
  background: linear-gradient(165deg, #114368 0%, #06283f 100%);
  border: 1px solid rgba(255, 255, 255, .14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), var(--btn-contact);
  place-items: center;
}
.sb-mark img { width: 15px; height: auto; }

.sb-nav { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 14px 12px 8px; display: flex; flex-direction: column; gap: 3px; }
.sb-section {
  font-size: 10px; letter-spacing: .15em; text-transform: uppercase;
  color: rgba(255, 255, 255, .36); padding: 16px 12px 7px; font-weight: 700; white-space: nowrap;
}
.nav-item {
  display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  border-radius: var(--r-sm);
  color: rgba(255, 255, 255, .78); font-weight: 500; font-size: var(--fs-base);
  text-decoration: none; position: relative; white-space: nowrap;
  border: 1px solid transparent;
}
.nav-item > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-item svg { width: 18px; height: 18px; flex: none; opacity: .72; }
.nav-item:hover {
  background: rgba(255, 255, 255, .06); color: #fff; text-decoration: none;
  border-color: rgba(255, 255, 255, .06);
}
/* Active: a brand-tinted plate with a soft inner light and a left rail. The
   rail alone is easy to miss on a long list; the plate carries the weight. */
.nav-item[aria-current="page"] {
  color: #fff; font-weight: 600;
  background: linear-gradient(90deg, rgba(236, 95, 36, .26), rgba(236, 95, 36, .08) 70%, transparent);
  border-color: rgba(236, 95, 36, .26);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08);
}
.nav-item[aria-current="page"] svg { opacity: 1; color: var(--brand-2); }
.nav-item[aria-current="page"]::before {
  content: ""; position: absolute; left: -1px; top: 8px; bottom: 8px; width: 3px;
  border-radius: 0 3px 3px 0; background: linear-gradient(180deg, var(--brand-2), var(--brand));
  box-shadow: 0 0 12px rgba(242, 148, 31, .7);
}
.nav-item .count {
  margin-left: auto; font-size: var(--fs-xs); font-weight: 700;
  background: rgba(255, 255, 255, .12); padding: 2px 8px; border-radius: var(--r-full);
  font-variant-numeric: tabular-nums;
}
.nav-item[aria-current="page"] .count { background: rgba(255, 255, 255, .18); }

.sb-foot { border-top: 1px solid rgba(255, 255, 255, .07); padding: 12px; }
/* The card itself is the link to the profile — the name is the thing a user
   points at, so a separate "Profile" button next to it was one control too many. */
.user-card {
  display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: var(--r);
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .07);
  color: inherit;
}
a.user-card:hover { color: inherit; }
.user-card .chev { width: 16px; height: 16px; flex: none; color: rgba(255, 255, 255, .4); }
.avatar {
  width: 36px; height: 36px; border-radius: 11px; flex: none; display: grid; place-items: center;
  background: var(--brand-grad); color: #fff; font-weight: 700; font-size: var(--fs-sm);
  box-shadow: var(--btn-sheen), var(--btn-contact); letter-spacing: .02em;
}
.avatar.lg { width: 58px; height: 58px; font-size: var(--fs-xl); border-radius: 17px; }
.avatar.sm { width: 28px; height: 28px; font-size: var(--fs-xs); border-radius: 9px; }
.user-card .name { font-weight: 600; font-size: var(--fs-base); color: #fff; }
.user-card .email { font-size: var(--fs-xs); color: rgba(255, 255, 255, .55); }
.user-card .org { font-size: var(--fs-xs); color: rgba(255, 255, 255, .68); }
.sb-actions { display: flex; gap: 8px; margin-top: 9px; }
.sb-actions .btn { flex: 1; justify-content: center; }
.sb-actions form { display: flex; }
.btn-sb {
  background: rgba(255, 255, 255, .04); color: rgba(255, 255, 255, .82);
  border: 1px solid rgba(255, 255, 255, .1);
}
.btn-sb:hover { background: rgba(255, 255, 255, .1); color: #fff; border-color: rgba(255, 255, 255, .18); }

/* rail (collapsed) — the width is chosen by .sb-collapse in the topbar (any
   viewport) or forced on small screens; the appearance follows from the width. */
body.sb-rail { --sb-w: var(--sb-w-rail); }

@container (max-width: 140px) {
  .sb-brand { padding: 19px 0 17px; justify-content: center; }
  .sb-brand::after { inset: auto 14px 0 14px; background: rgba(255, 255, 255, .09); }
  .sb-brand > a, .sb-brand .tagline { display: none; }
  .sb-mark { display: grid; }
  .sb-nav { padding-left: 10px; padding-right: 10px; }
  .sb-section { height: 1px; padding: 12px 8px; overflow: hidden; color: transparent; background: none; }
  .sb-section::after { content: ""; display: block; height: 1px; background: rgba(255, 255, 255, .09); }
  .nav-item { justify-content: center; padding: 11px 0; }
  .nav-item > span, .nav-item .count { display: none; }
  .user-card { justify-content: center; padding: 8px; }
  .user-card .grow, .user-card .chev { display: none; }
  /* Sign out stays reachable in the rail — icon only. */
  .sb-actions .btn .txt { display: none; }
  .sb-actions .btn { padding: 0; }
  /* Label on hover, so a collapsed rail is still navigable. */
  .nav-item::after {
    content: attr(data-label); position: absolute; left: calc(100% + 12px); top: 50%;
    transform: translateY(-50%) translateX(-4px);
    background: var(--navy-600); color: #fff; padding: 6px 11px; border-radius: var(--r-sm);
    font-size: var(--fs-sm); font-weight: 600; white-space: nowrap; pointer-events: none;
    box-shadow: var(--shadow-md); border: 1px solid var(--navy-line);
    opacity: 0; transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease-out);
    z-index: 5;
  }
  .nav-item:hover::after, .nav-item:focus-visible::after { opacity: 1; transform: translateY(-50%); }
}

/* ------------------------------------------------------------- frame & topbar */
.frame {
  margin-left: var(--sb-w); min-height: 100vh; display: flex; flex-direction: column;
  transition: margin-left var(--t-slow) var(--ease-out);
  /* clip, not hidden: `clip` does not create a scroll container, so the sticky topbar
     still resolves against the viewport. This is the backstop that guarantees no page
     can ever scroll sideways — a decorative wash, an over-wide pill row or a long
     unbroken string gets cut at the frame edge instead of adding 100px of dead
     horizontal scroll to every page. Anything that legitimately needs to scroll
     sideways (tables, the status tabs) owns its own overflow container. */
  overflow-x: clip;
}
.topbar {
  position: sticky; top: 0; z-index: 40; min-height: var(--topbar-h);
  display: flex; align-items: center; gap: 14px; padding: 10px var(--gutter);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid var(--line);
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .topbar { background: var(--bg); }
}
.topbar-title { min-width: 0; }
.topbar-title h1 { font-size: var(--fs-xl); letter-spacing: -.016em; }
.topbar-title .subtitle { font-size: var(--fs-sm); color: var(--muted); margin-top: 1px; }
.topbar-search { margin-left: auto; flex: 0 1 320px; position: relative; }
.topbar-search input[type="search"] {
  padding-left: 38px; min-height: var(--btn-h); border-radius: var(--r-full);
  background: var(--card-3); border-color: transparent;
}
.topbar-search input[type="search"]:hover { border-color: var(--input-line); }
.topbar-search input[type="search"]:focus { background: var(--input-bg); }
.topbar-search svg {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--muted-2); pointer-events: none;
}
.topbar-actions { display: flex; align-items: center; gap: 9px; flex: none; }
.topbar-sep { width: 1px; align-self: stretch; margin: 6px 2px; background: var(--line); flex: none; }
.sb-collapse { display: inline-grid; }

/* -------------------------------------------------------- navigation progress
   Shown only when a navigation outlasts ~150ms, so a fast page never flashes a
   bar. It is deliberately not a real progress bar: the server does not report
   progress, so it eases towards 90% and the new document's paint ends it. */
.nav-progress {
  position: fixed; inset: 0 auto auto 0; z-index: 200; height: 2.5px; width: 100%;
  transform: scaleX(0); transform-origin: 0 50%; pointer-events: none;
  background: linear-gradient(90deg, var(--brand), var(--brand-2));
  box-shadow: 0 0 10px rgba(242, 148, 31, .55);
  transition: transform 600ms cubic-bezier(.1, .75, .2, 1), opacity var(--t-fast) var(--ease);
}
.nav-progress.go { transform: scaleX(.9); }
.nav-progress.done { transform: scaleX(1); opacity: 0; transition: transform var(--t-fast) var(--ease), opacity var(--t) var(--ease); }

/* ------------------------------------------------------------- content
   A single brand-tinted wash anchored behind the top of the page. It is the
   cheapest way to stop a long scroll of white cards reading as a spreadsheet. */
.content {
  flex: 1; width: 100%; max-width: var(--content-max); margin: 0 auto;
  padding: 26px var(--gutter) 52px; position: relative; isolation: isolate;
}
/* The wash is inset to the content box rather than bled past it. It used to sit at
   left/right: -10%, which put ~110px of absolutely-positioned pseudo-element outside
   the frame on every screen narrower than ~1900px — that, and nothing else, was what
   gave every page in the portal a horizontal scrollbar. The gradient radii are widened
   to compensate, so it reads the same. */
.content::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 0; right: 0; top: -130px; height: 420px;
  background:
    radial-gradient(56% 62% at 18% 40%, rgba(236, 95, 36, .09), transparent 70%),
    radial-gradient(48% 56% at 86% 22%, rgba(37, 99, 235, .07), transparent 70%);
}
/* Nothing behind the dark page. The wash below is a faint tint that stops a long scroll of
   white cards reading as a spreadsheet — on a near-black background there are no white
   cards to break up, and the orange half of it turned the top of every page a hazy brown. */
:root[data-theme="dark"] .content::before { background: none; }
.foot {
  padding: 18px var(--gutter) calc(26px + var(--bottom-nav-h));
  color: var(--muted); font-size: var(--fs-sm); border-top: 1px solid var(--line);
}
.crumbs { display: flex; align-items: center; gap: 7px; font-size: var(--fs-sm); color: var(--muted); margin-bottom: 14px; flex-wrap: wrap; }
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--brand-ink); text-decoration: none; }
.crumbs .sep { color: var(--muted); }
.crumbs .cur { color: var(--ink); font-weight: 600; }

/* ================================================================= buttons */
/* Every variant is the same object with a different skin: fixed height so a mixed
   row lines up on both edges, a real 1px border (never transparent — a filled
   button without an edge reads flat), and the shared sheen/contact/lift/press
   layers from the token block. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--btn-h); padding: 0 16px;
  border-radius: var(--r-sm); border: 1px solid transparent;
  font-weight: 600; font-size: var(--fs-base); line-height: 1.2;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  letter-spacing: -.008em;
  /* the fill must not bleed past the 1px edge on the rounded corners */
  background-clip: padding-box;
}
.btn:hover { text-decoration: none; }
.btn svg { width: 16px; height: 16px; flex: none; }

.btn-primary {
  background: var(--brand-grad); color: #fff;
  border-color: var(--brand-edge);
  box-shadow: var(--btn-sheen), var(--btn-contact), var(--brand-glow);
  text-shadow: 0 1px 1px rgba(102, 34, 10, .22);
}
.btn-primary:hover { background: var(--brand-grad-hover); box-shadow: var(--btn-sheen), var(--btn-contact), var(--brand-glow-lg); }
.btn-primary:active { box-shadow: var(--btn-press-fill); }
/* One specular pass across the fill on hover. A gradient rectangle reads as a
   coloured box; a highlight travelling over it reads as something physical. It
   sits behind the label so the text never dims underneath it. */
.btn-primary { position: relative; overflow: hidden; isolation: isolate; }
.btn-primary::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(104deg, transparent 38%, rgba(255, 255, 255, .26) 50%, transparent 62%);
  transform: translateX(-130%);
}
.btn-primary:hover::after { transform: translateX(130%); }

.btn-dark {
  background: linear-gradient(180deg, var(--navy-700) 0%, var(--navy) 100%); color: #fff;
  border-color: #000; box-shadow: var(--btn-sheen), var(--btn-contact);
}
.btn-dark:hover { background: linear-gradient(180deg, var(--navy-600) 0%, var(--navy-700) 100%); box-shadow: var(--btn-sheen), var(--btn-lift-hover); }
.btn-dark:active { box-shadow: inset 0 2px 5px rgba(0, 0, 0, .45); }
:root[data-theme="dark"] .btn-dark {
  background: linear-gradient(180deg, var(--card-3) 0%, var(--card-2) 100%); border-color: var(--line);
}

.btn-secondary {
  background: var(--btn-face-grad); color: var(--ink); border-color: var(--input-line);
  box-shadow: var(--btn-sheen-light), var(--btn-lift);
}
.btn-secondary:hover { background: var(--btn-face-grad-hover); border-color: var(--input-line-hover); box-shadow: var(--btn-sheen-light), var(--btn-lift-hover); }
.btn-secondary:active { background: var(--card-3); background-image: none; box-shadow: var(--btn-press); }
:root[data-theme="dark"] .btn-secondary { box-shadow: var(--btn-sheen), var(--btn-lift); }
:root[data-theme="dark"] .btn-secondary:hover { box-shadow: var(--btn-sheen), var(--btn-lift-hover); }

.btn-ghost { background: transparent; color: var(--ink-2); }
.btn-ghost:hover { background: var(--slate-soft); color: var(--ink); }
.btn-ghost:active { background: var(--card-3); box-shadow: var(--btn-press); }

.btn-brand-soft { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--brand-line); }
.btn-brand-soft:hover { background: var(--brand-soft-2); border-color: var(--brand); box-shadow: var(--btn-lift); }
.btn-brand-soft:active { box-shadow: var(--btn-press); }

.btn-danger { background: var(--red-soft); color: var(--red); border-color: var(--red-line); }
.btn-danger:hover { background: var(--red-soft); border-color: var(--red); box-shadow: var(--btn-lift); }
.btn-danger:active { box-shadow: var(--btn-press); }

.btn-sm { min-height: var(--btn-h-sm); padding: 0 11px; font-size: var(--fs-sm); border-radius: var(--r-xs); gap: 6px; }
.btn-sm svg { width: 14px; height: 14px; }
.btn-lg { min-height: var(--btn-h-lg); padding: 0 24px; font-size: var(--fs-md); border-radius: var(--r); gap: 10px; }
.btn-lg svg { width: 18px; height: 18px; }
.btn-block { width: 100%; }

/* Quiet: the skin for a way out rather than a way forward — "Back to home",
   "Back to sign in". These were bare blue anchors dropped under the form, which
   read as leftover markup beside the buttons above them. They are the same object
   as every other button now (one height, one hit area), pill-shaped and dialled
   right down in weight so they never compete with the submit action.
   After the size modifiers on purpose: the pill has to survive .btn-sm. */
.btn-quiet {
  background: var(--slate-soft); color: var(--ink-2);
  border-color: var(--line); border-radius: var(--r-full);
}
.btn-quiet:hover {
  background: var(--btn-face-grad); color: var(--ink);
  border-color: var(--input-line-hover);
  box-shadow: var(--btn-sheen-light), var(--btn-lift-hover);
}
.btn-quiet:active { background: var(--card-3); background-image: none; box-shadow: var(--btn-press); }
:root[data-theme="dark"] .btn-quiet:hover { box-shadow: var(--btn-sheen), var(--btn-lift-hover); }

/* The arrow on a back control is drawn, not marked up: it belongs to the skin,
   and every page needing a way out would otherwise repeat the same SVG. It leads
   the motion on hover, so the direction is felt before the label is read. */
.btn.back::before {
  content: ""; flex: none; width: 7px; height: 7px; margin-right: -1px;
  border-top: 1.8px solid currentColor; border-right: 1.8px solid currentColor;
  border-radius: 1px; transform: rotate(-135deg);
}
.btn.back:hover::before { transform: translateX(-3px) rotate(-135deg); }
/* Disabled resets to a neutral surface rather than a faded fill. A 45%-opacity
   orange goes chalky on white and muddy brown on the dark theme; a flat neutral
   reads as "not available" cleanly in both.
   :not([data-busy]) is the important part — a form mid-submit keeps its own skin. */
.btn[disabled]:not([data-busy]), .btn[aria-disabled="true"] {
  cursor: not-allowed;
  background: var(--card-3); background-image: none; color: var(--muted-2);
  border-color: var(--line); box-shadow: none; text-shadow: none;
}
.btn[disabled]:hover, .btn[aria-disabled="true"]:hover { transform: none; }
.btn[disabled]:not([data-busy]):hover, .btn[aria-disabled="true"]:hover { box-shadow: none; }

/* Working: the variant's own fill, held slightly back, with a spinner in place of
   the icon slot. The ring is drawn from currentColor so it works on any variant. */
.btn[data-busy] { cursor: progress; opacity: .9; }
.btn[data-busy]::before {
  content: ""; flex: none; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .32);
  border: 2px solid color-mix(in srgb, currentColor 28%, transparent);
  border-top-color: currentColor;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.icon-btn {
  display: inline-grid; place-items: center; width: var(--btn-h); height: var(--btn-h);
  border-radius: var(--r-sm); border: 1px solid var(--input-line);
  background: var(--btn-face); color: var(--ink-2); cursor: pointer; flex: none;
  box-shadow: var(--btn-sheen-light), var(--btn-lift); background-clip: padding-box;
}
.icon-btn:hover { background: var(--btn-face-hover); border-color: var(--input-line-hover); color: var(--ink); box-shadow: var(--btn-sheen-light), var(--btn-lift-hover); }
.icon-btn:active { box-shadow: var(--btn-press); }
.icon-btn svg { width: 18px; height: 18px; }
:root[data-theme="dark"] .icon-btn { box-shadow: var(--btn-sheen), var(--btn-lift); }
/* .plain is chrome, not a control — no border, no elevation until hovered */
.icon-btn.plain { border-color: transparent; background: transparent; box-shadow: none; }
.icon-btn.plain:hover { background: var(--slate-soft); border-color: transparent; box-shadow: none; }
.icon-btn.plain:active { box-shadow: var(--btn-press); }
/* An action that has to live inside a sentence, so it stays type instead of
   becoming a box. What it gains is an underline that draws itself from the left
   rather than the browser's rule appearing under the whole phrase at once. */
.link-btn, .text-link {
  position: relative; display: inline-flex; align-items: center; gap: 5px;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-weight: 600; color: var(--brand-ink);
}
.link-btn { font-family: inherit; font-size: inherit; }
.link-btn::after, .text-link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1.5px;
  background: currentColor; border-radius: 2px;
  transform: scaleX(0); transform-origin: left center;
}
.link-btn:hover, .text-link:hover { color: var(--brand); }
.link-btn:hover::after, .text-link:hover::after { transform: scaleX(1); }

/* theme toggle — the two glyphs cross-fade rather than swapping instantly */
.theme-btn { position: relative; overflow: hidden; }
.theme-btn svg { position: absolute; transition: opacity var(--t) var(--ease), transform var(--t-slow) var(--ease-spring); }
.theme-btn .i-sun { opacity: 0; transform: rotate(-70deg) scale(.5); }
.theme-btn .i-moon { opacity: 1; transform: none; }
:root[data-theme="dark"] .theme-btn .i-sun { opacity: 1; transform: none; }
:root[data-theme="dark"] .theme-btn .i-moon { opacity: 0; transform: rotate(70deg) scale(.5); }

/* =================================================================== cards */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); position: relative;
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: var(--card-pad) var(--card-pad) 0; flex-wrap: wrap;
}
.card-head h2 { font-size: var(--fs-md); }
.card-head .sub { font-size: var(--fs-sm); color: var(--muted); margin-top: 3px; }
.card-head .more, .section-head .more { font-size: var(--fs-sm); font-weight: 600; color: var(--brand-ink); }
.card-head .more:hover, .section-head .more:hover { text-decoration: none; color: var(--brand); }
.card-body { padding: var(--card-pad); }
.card-foot {
  padding: 14px var(--card-pad); border-top: 1px solid var(--line-2); background: var(--card-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap;
}
.card.pad { padding: var(--card-pad); }
/* A head that carries two bands — tabs above, a filter bar below, divided full-bleed.
   The negative margin is what lets the rule reach the card edge while the controls
   keep the card's padding. */
.card-head.split {
  flex-direction: column; align-items: stretch; gap: 0; padding-bottom: 0;
}
.card-head.split > .seg { align-self: flex-start; margin-bottom: 15px; }
.card-head.split > .filters {
  margin: 0 calc(var(--card-pad) * -1);
  padding: 14px var(--card-pad) 0;
  border-top: 1px solid var(--line-2);
}
/* Below ~900px the four controls cannot share one line, so rather than let them wrap
   into a ragged two-and-then-one, they become a grid: search across the top, the three
   selects equal underneath. */
@media (max-width: 900px) {
  .card-head.split > .filters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
  .card-head.split > .filters .search { grid-column: 1 / -1; max-width: none; }
  .card-head.split > .filters select, .card-head.split > .filters .xs { width: 100%; min-width: 0; }
}
@media (max-width: 520px) {
  .card-head.split > .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .card-head.split > .filters > :last-of-type { grid-column: 1 / -1; }
}
.card.flat { box-shadow: none; }
.card.raised { box-shadow: var(--shadow); }
.card.quiet { background: var(--card-2); box-shadow: none; }
/* accent: a gradient hairline across the top, inside the radius */
.card.accent { overflow: hidden; }
.card.accent::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px;
  background: linear-gradient(90deg, var(--brand), var(--brand-2) 55%, transparent);
}

.grid { display: grid; gap: 18px; }
/* Every track is minmax(0, …) and every item is min-width: 0. Without both, a grid item
   whose min-content is wider than its share (a long case title, a nowrap meta row) widens
   the whole track past the container instead of wrapping inside it. */
.grid > * { min-width: 0; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-main { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); }
.grid-side { grid-template-columns: minmax(0, 1fr) 320px; }
.grid-tight { gap: 13px; }

/* ------------------------------------------------------------------ hero
   Dashboard welcome band: dark plate, drifting aurora, quick actions. It
   gives the page a top edge and keeps the greeting out of the KPI row. */
.hero {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: var(--r-xl); padding: 26px 28px;
  color: #fff; margin-bottom: 18px;
  background: linear-gradient(120deg, #1b1f4a 0%, #141737 52%, #0c0e22 100%);
  border: 1px solid var(--navy-line);
  box-shadow: var(--shadow-md);
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
}
/* One warm accent anchored in the top-right corner, and nothing else.

   This used to be three large blurred colour fields drifting on a 30-second loop. Over a
   near-black plate the orange and the amber met in the middle of the band and mixed to a
   muddy brown that read as a smudge on the screen rather than as light — and it animated,
   so it was also a continuous compositor repaint for decoration. The plate's own gradient
   and the dot matrix give the band its depth; the orange it needs is already in the
   primary button sitting on it. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -2; pointer-events: none;
  background: radial-gradient(42% 78% at 100% 0%, rgba(236, 95, 36, .2), transparent 64%);
}
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: .42;
  background-image: radial-gradient(rgba(255, 255, 255, .2) 1px, transparent 1px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(80% 90% at 78% 12%, #000 8%, transparent 72%);
  mask-image: radial-gradient(80% 90% at 78% 12%, #000 8%, transparent 72%);
}
.hero-main { flex: 1 1 340px; min-width: 0; }
.hero .eyebrow { color: rgba(255, 255, 255, .62); }
.hero h2 {
  color: #fff; font-size: clamp(21px, 2.5vw, 27px); letter-spacing: -.021em;
  margin: 7px 0 6px; text-wrap: balance;
}
.hero .hero-sub { color: rgba(255, 255, 255, .74); font-size: var(--fs-md); max-width: 54ch; }
.hero-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.hero-actions .btn-secondary {
  background: rgba(255, 255, 255, .1); color: #fff;
  border-color: rgba(255, 255, 255, .2); box-shadow: none;
  backdrop-filter: blur(6px);
}
.hero-actions .btn-secondary:hover { background: rgba(255, 255, 255, .18); border-color: rgba(255, 255, 255, .32); }
/* Filled in after the panels land, when the support plan will not allow another case. It
   is a note rather than a replacement for the button above it: the page has already been
   read by then, and taking a control away under the reader's eyes is worse than telling
   them why it will not work. */
.hero-note {
  display: flex; align-items: flex-start; gap: 9px; margin-top: 14px;
  font-size: var(--fs-sm); color: #ffd9a8; max-width: 54ch;
}
.hero-note svg { width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--brand-2); }
.hero-note strong { color: #fff; font-weight: 700; }
/* glass stat strip on the right of the hero */
.hero-side { flex: 0 1 260px; display: grid; gap: 9px; }
.hero-stat {
  display: flex; align-items: center; gap: 12px; padding: 11px 14px;
  border-radius: var(--r); background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .14); backdrop-filter: blur(8px);
}
.hero-stat .hs-ic { width: 32px; height: 32px; border-radius: var(--r-sm); display: grid; place-items: center; flex: none; background: rgba(255, 255, 255, .12); }
.hero-stat .hs-ic svg { width: 16px; height: 16px; color: var(--brand-2); }
.hero-stat .hs-l, .hero-stat .hs-v { display: block; }
.hero-stat .hs-l { font-size: var(--fs-xs); color: rgba(255, 255, 255, .66); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.hero-stat .hs-v { font-size: var(--fs-md); font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; line-height: 1.35; }
/* "62 of 100" — what matters is the figure left, so the total trails it at the weight of the
   label rather than competing with it. */
.hero-stat .hs-of { font-weight: 500; color: rgba(255, 255, 255, .62); font-size: var(--fs-sm); }
/* A plan that is not in force reads as a warning, not as a neutral fact. --brand-2 is the
   amber already used for "New cases are blocked" in the note below, so the two agree. */
.hero-stat .hs-warn { color: var(--brand-2); }
.hero-stat .hs-ic.warn { background: rgba(242, 148, 31, .16); border: 1px solid rgba(242, 148, 31, .3); }
.hero-stat .hs-ic.warn svg { color: var(--brand-2); }
/* The plan rows link through to /support-plan, which is where the rings and the usage history
   went; the open-case count does not, so only some rows carry a chevron and a hover. */
a.hero-stat { color: inherit; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); }
a.hero-stat:hover { background: rgba(255, 255, 255, .15); border-color: rgba(255, 255, 255, .26); color: inherit; text-decoration: none; }
a.hero-stat:hover .hs-go { opacity: .95; transform: translateX(2px); }
.hero-stat .hs-go {
  width: 15px; height: 15px; flex: none; color: rgba(255, 255, 255, .5); opacity: .65;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease-out);
}

/* ------------------------------------------------------------------- KPI */
/* Centred rather than top-aligned: a KPI tile shares a row with whatever is beside it
   and often ends up taller than its own content, and three lines pinned to the top of a
   tall card with the link stranded at the bottom reads as a layout accident. */
/* The tiles are their own full-width row now (see _DashSummary), so nothing can stretch them.
   Kept as a guard: if the strip is ever put back beside a taller card, the tiles keep their
   own height rather than growing into boxes with a number adrift in the middle. */
.kpi-grid { align-content: start; }
.kpi {
  display: flex; flex-direction: column; gap: 5px; padding: 16px 18px;
  min-height: 112px; justify-content: flex-start; overflow: hidden;
}
/* Pushed to the bottom of the tile, so a row of tiles lines its footers up even when one
   of them wraps onto a second line. */
.kpi .delta { margin-top: auto; }
/* faint tonal corner wash — reads as material, not as a coloured box */
.kpi::after {
  content: ""; position: absolute; inset: auto -30% -60% auto; width: 200px; height: 200px;
  border-radius: 50%; opacity: .5; pointer-events: none;
  background: radial-gradient(circle, var(--kpi-tint, transparent) 0%, transparent 70%);
}
.kpi .kpi-ic {
  position: absolute; top: 15px; right: 16px;
  width: 34px; height: 34px; border-radius: var(--r-sm); display: grid; place-items: center;
  background: var(--kpi-soft, var(--slate-soft)); color: var(--kpi-ink, var(--muted));
  border: 1px solid var(--kpi-line, transparent);
}
.kpi .kpi-ic svg { width: 17px; height: 17px; }
.kpi .label {
  font-size: var(--fs-sm); color: var(--muted); font-weight: 600;
  display: flex; align-items: center; gap: 7px;
  letter-spacing: -.005em;
}
/* Only reserve room for the icon tile on the cards that actually have one — the
   support-plan page reuses .kpi without it. The tile is markup-adjacent to the
   label, so this needs no :has(). */
.kpi .kpi-ic + .label { padding-right: 42px; }
.kpi .label svg { width: 14px; height: 14px; flex: none; }
.kpi .value {
  font-family: var(--font-display);
  font-size: 33px; font-weight: 700; letter-spacing: -.026em; line-height: 1.05;
  font-variant-numeric: tabular-nums; margin-top: 2px; color: var(--kpi-ink, var(--ink));
}
.kpi .delta { font-size: var(--fs-sm); color: var(--muted); margin-top: 5px; display: inline-flex; align-items: center; gap: 4px; }
a.delta { color: var(--muted); font-weight: 600; }
a.delta:hover { color: var(--brand-ink); text-decoration: none; }
.kpi.tone-open    { --kpi-ink: var(--blue-ink);  --kpi-soft: var(--blue-soft);  --kpi-line: var(--blue-line);  --kpi-tint: rgba(37, 99, 235, .1); }
.kpi.tone-resolved{ --kpi-ink: var(--green-ink); --kpi-soft: var(--green-soft); --kpi-line: var(--green-line); --kpi-tint: rgba(21, 128, 61, .1); }
.kpi.tone-cancelled { --kpi-ink: var(--muted); --kpi-soft: var(--slate-soft); --kpi-line: var(--line); --kpi-tint: transparent; }
.kpi.tone-time    { --kpi-ink: var(--brand-ink); --kpi-soft: var(--brand-soft); --kpi-line: var(--brand-line); --kpi-tint: rgba(236, 95, 36, .1); }
.kpi.tone-time .value { font-size: var(--fs-2xl); }
.kpi.tone-cancelled .value { color: var(--ink-2); }

/* ======================================================== pills & badges */
/* A pill is a label, not a button: it gets enough height to be legible and enough
   side padding that the text is not touching the radius, and then it stops. The
   previous 3px/10px box made a column of them read as one striped block — the
   cure for a crowded status column is air inside the pill and air around it, not
   a smaller pill. */
.pill {
  display: inline-flex; align-items: center; gap: 7px; padding: 4px 12px;
  min-height: 26px; line-height: 1.35;
  border-radius: var(--r-full); font-size: var(--fs-sm); font-weight: 600;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-2); white-space: nowrap;
  letter-spacing: -.005em;
}
.pill.sm { min-height: 22px; padding: 2px 9px; font-size: var(--fs-xs); }
.pill .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent); flex: none; }
.pill.open      { background: var(--blue-soft);  border-color: var(--blue-line);  color: var(--blue-ink); }
.pill.waiting   { background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-ink); }
.pill.resolved  { background: var(--green-soft); border-color: var(--green-line); color: var(--green-ink); }
.pill.cancelled { background: var(--slate-soft); border-color: var(--line);       color: var(--muted); }
/* S1 has to out-rank S2, which already owns the red tint — so it takes the solid fill.
   A tint one shade deeper than S2 reads as "another kind of high", not as "worse than high". */
.pill.critical  { background: var(--crit);       border-color: var(--crit-line);  color: var(--crit-ink); font-weight: 700; }
.pill.high      { background: var(--red-soft);   border-color: var(--red-line);   color: var(--red-ink); }
.pill.medium    { background: var(--brand-soft); border-color: var(--brand-line); color: var(--brand-ink); }
.pill.low       { background: var(--blue-soft);  border-color: var(--blue-line);  color: var(--blue-ink); }
.pill.brand     { background: var(--brand-soft); border-color: var(--brand-line); color: var(--brand-ink); }
.pill.ok        { background: var(--green-soft); border-color: var(--green-line); color: var(--green-ink); }
.pill.warn      { background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-ink); }
.pill.bad       { background: var(--red-soft);   border-color: var(--red-line);   color: var(--red-ink); }
.pill.solid     { background: var(--navy); border-color: var(--navy); color: #fff; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px;
  border-radius: var(--r-full); font-size: var(--fs-sm);
  background: var(--slate-soft); color: var(--ink-2); border: 1px solid transparent;
  cursor: pointer; font-weight: 600;
}
.chip:hover { border-color: var(--line); }
.chip[aria-pressed="true"], .chip.active {
  background: var(--brand-grad); color: #fff; border-color: var(--brand-edge);
  box-shadow: var(--btn-sheen), var(--btn-contact);
  text-shadow: 0 1px 1px rgba(102, 34, 10, .2);
}
.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.tag {
  display: inline-block; padding: 2px 9px; border-radius: var(--r-xs);
  font-size: var(--fs-xs); font-weight: 600; background: var(--slate-soft); color: var(--muted);
  border: 1px solid var(--line-2);
}

/* ======================================================== alerts & toasts */
.alert {
  display: flex; gap: 12px; align-items: flex-start; padding: 13px 15px;
  border-radius: var(--r); border: 1px solid var(--line); background: var(--card);
  font-size: var(--fs-base);
}
.alert svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.alert strong { display: block; margin-bottom: 2px; }
.alert.info    { background: var(--blue-soft);  border-color: var(--blue-line);  color: var(--blue-ink); }
.alert.warn    { background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-ink); }
.alert.error   { background: var(--red-soft);   border-color: var(--red-line);   color: var(--red-ink); }
.alert.success { background: var(--green-soft); border-color: var(--green-line); color: var(--green-ink); }
.alert ul { margin: 4px 0 0; padding-left: 18px; }

.toasts { position: fixed; right: 20px; bottom: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; max-width: 380px; }
.toast {
  display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px;
  border-radius: var(--r); background: var(--navy); color: #fff;
  box-shadow: var(--shadow-lg); font-size: var(--fs-base);
  border: 1px solid rgba(255, 255, 255, .1);
  border-left: 3px solid var(--brand);
}
.toast.success { border-left-color: #22c55e; }
.toast.error { border-left-color: #ef4444; }
.toast.warn { border-left-color: #f59e0b; }
:root[data-theme="dark"] .toast { background: var(--card-3); }
.toast button { margin-left: auto; background: none; border: 0; color: rgba(255, 255, 255, .6); font-size: var(--fs-lg); line-height: 1; padding: 0 2px; }
.toast button:hover { color: #fff; }

/* =================================================================== forms */
.form-grid { display: grid; gap: 16px; }
.form-row { display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label, .label { font-weight: 600; font-size: var(--fs-base); color: var(--ink); }
.field .req { color: var(--brand); margin-left: 2px; }
.help { font-size: var(--fs-sm); color: var(--muted); }
.input, .select, .textarea,
input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="number"], input[type="date"], input[type="tel"], input[type="url"], select, textarea {
  width: 100%; border: 1px solid var(--input-line); border-radius: var(--r-sm);
  padding: 9px 12px; background: var(--input-bg); color: var(--ink);
  font-size: 14px; outline: none; min-height: var(--btn-h);
}
textarea { min-height: 140px; resize: vertical; line-height: 1.55; }
select {
  appearance: none; padding-right: 36px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236e7288' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
input:focus, select:focus, textarea:focus { border-color: var(--brand); box-shadow: var(--focus-ring); }
/* --muted, not --muted-2: placeholder text is text, and WCAG makes no exception for it.
   It still reads as a placeholder because a real value is --ink, three times the contrast
   again — the separation comes from the gap between them, not from making the hint faint
   enough to fail. */
input::placeholder, textarea::placeholder { color: var(--muted); }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"], .input-validation-error {
  border-color: var(--red); background: var(--red-soft);
}
.field-error, .field-validation-error { font-size: var(--fs-sm); color: var(--red); font-weight: 600; }
.field-error:empty, .field-validation-error:empty { display: none; }
.validation-summary-errors ul { margin: 0; padding-left: 18px; }
.validation-summary-valid { display: none; }
.checkbox { display: flex; align-items: center; gap: 10px; font-size: var(--fs-base); cursor: pointer; }
.checkbox input { width: 18px; height: 18px; min-height: 0; accent-color: var(--brand); margin: 0; }
.input-wrap { position: relative; }
.input-wrap .input-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--muted-2); pointer-events: none; }
.input-wrap input { padding-left: 38px; }
.input-wrap .pw-toggle {
  position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; color: var(--muted); font-size: var(--fs-sm);
  font-weight: 700; padding: 5px 9px; border-radius: var(--r-xs);
}
.input-wrap .pw-toggle:hover { background: var(--slate-soft); color: var(--ink); }
.counter { font-size: var(--fs-xs); color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
.strength { height: 6px; border-radius: var(--r-full); background: var(--line-2); overflow: hidden; }
.strength > i { display: block; height: 100%; width: 0; border-radius: inherit; background: var(--red); transition: width var(--t-slow) var(--ease-out), background var(--t) var(--ease); }
.strength[data-level="2"] > i { background: var(--amber); }
.strength[data-level="3"] > i, .strength[data-level="4"] > i { background: #22c55e; }
.form-actions { display: flex; justify-content: flex-end; gap: 10px; padding-top: 6px; flex-wrap: wrap; }
.form-actions .left { margin-right: auto; }
/* A segmented control cannot wrap — halves of a pill on two lines is not a control —
   so on a narrow screen it scrolls sideways inside its own track instead of pushing the
   page wide. max-width keeps that track inside the card whatever the label lengths. */
.seg {
  display: inline-flex; background: var(--card-3); border: 1px solid var(--line);
  border-radius: var(--r-full); padding: 3px; gap: 2px;
  max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.seg::-webkit-scrollbar { display: none; }
/* When the tabs are wider than the track, a fade over the end that still has tabs behind
   it says so. portal.js sets data-overflow to start / end / both as the track is scrolled,
   so the fade is never lying about which way there is more to see. Without script there is
   no fade and the control still scrolls — the clipped label is its own hint. */
.seg[data-overflow] {
  --seg-fade: 26px;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--seg-l, 0px), #000 calc(100% - var(--seg-r, 0px)), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 var(--seg-l, 0px), #000 calc(100% - var(--seg-r, 0px)), transparent 100%);
}
.seg[data-overflow="end"], .seg[data-overflow="both"] { --seg-r: var(--seg-fade); }
.seg[data-overflow="start"], .seg[data-overflow="both"] { --seg-l: var(--seg-fade); }
.seg a, .seg button {
  border: 0; background: transparent; padding: 7px 14px; border-radius: var(--r-full);
  font-weight: 600; font-size: var(--fs-sm); color: var(--muted);
  text-decoration: none; cursor: pointer; white-space: nowrap; flex: none;
}
.seg a:hover, .seg button:hover { color: var(--ink); text-decoration: none; }
.seg a.active, .seg button.active, .seg [aria-pressed="true"] {
  background: var(--card); color: var(--ink); box-shadow: var(--shadow-sm);
}
.filters { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.filters select, .filters input { min-height: var(--btn-h); width: auto; }
/* The search grows into the space the selects leave, but stops before it starts
   pushing them onto a second line — a filter bar that reflows as you resize is the
   thing that makes a toolbar look accidental. */
.filters .search { position: relative; flex: 1 1 220px; min-width: 0; max-width: 380px; }
.filters .search input { padding-left: 34px; width: 100%; }
/* Search inputs that want to be roomier than the default say so with a class rather
   than an inline min-width — an inline style outranks every media query, which is how
   the knowledge-base search ended up wider than a phone screen. */
.filters .search.wide { flex-basis: 320px; }
.filters .search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--muted-2); pointer-events: none; }

/* dropzone */
.dropzone {
  border: 1.5px dashed var(--input-line); border-radius: var(--r);
  background: var(--card-2); padding: 20px; text-align: center; position: relative;
}
.dropzone:hover { border-color: var(--brand-line); background: var(--brand-soft); }
.dropzone.drag { border-color: var(--brand); background: var(--brand-soft); }
.dropzone input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.dropzone .dz-icon { width: 38px; height: 38px; margin: 0 auto 9px; color: var(--brand); }
.dropzone strong { color: var(--ink); }
.dropzone .help { margin-top: 4px; }
.file-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.file-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 11px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--card); font-size: var(--fs-base);
}
.file-row svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
.file-row .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.file-row .size { color: var(--muted); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.file-row.bad { border-color: var(--red-line); background: var(--red-soft); color: var(--red); }
.file-row .remove { background: none; border: 0; color: var(--muted); padding: 2px 7px; border-radius: var(--r-xs); }
.file-row .remove:hover { background: var(--slate-soft); color: var(--ink); }

/* ----------------------------------------------------- custom select (xs)
   Progressive enhancement over a native select: portal.js wraps each one,
   keeps the native element as the single source of truth for the form value,
   and mirrors every change back to it (dispatching a change event, so the
   existing autosubmit and filter handlers keep working). With JavaScript off
   the native select is never replaced and everything still works. */
.xs { position: relative; display: block; width: 100%; }
.xs-native {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  opacity: 0; pointer-events: none; overflow: hidden; clip: rect(0 0 0 0); border: 0; min-height: 0;
}
.xs-btn {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: var(--btn-h);
  padding: 9px 12px; border: 1px solid var(--input-line); border-radius: var(--r-sm);
  background: var(--input-bg); color: var(--ink); font-size: 14px; text-align: left; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.xs-btn:hover { border-color: var(--input-line-hover); }
.xs-btn .xs-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xs-btn .xs-value.placeholder { color: var(--muted); }
.xs-btn .xs-caret { width: 16px; height: 16px; flex: none; color: var(--muted); transition: transform var(--t) var(--ease-out); }
.xs[data-open="true"] .xs-btn { border-color: var(--brand); box-shadow: var(--focus-ring); }
.xs[data-open="true"] .xs-caret { transform: rotate(180deg); }
.xs-btn[disabled] { opacity: .55; cursor: not-allowed; background: var(--line-2); }
.xs-pop {
  position: absolute; z-index: 80; left: 0; right: 0; top: calc(100% + 6px);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-lg); padding: 6px; max-height: 288px; overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 0; transform: translateY(-6px) scale(.985); transform-origin: top center;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.xs[data-open="true"] .xs-pop { opacity: 1; transform: none; transition-timing-function: var(--ease-out); }
.xs.up .xs-pop { top: auto; bottom: calc(100% + 6px); transform-origin: bottom center; }
.xs-opt {
  display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: var(--r-xs);
  font-size: var(--fs-base); color: var(--ink-2); cursor: pointer; scroll-margin: 6px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.xs-opt .xs-tick { width: 15px; height: 15px; flex: none; opacity: 0; color: var(--brand); transition: opacity var(--t-fast) var(--ease); }
.xs-opt .xs-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xs-opt[aria-selected="true"] { color: var(--ink); font-weight: 600; }
.xs-opt[aria-selected="true"] .xs-tick { opacity: 1; }
.xs-opt.active { background: var(--brand-soft); color: var(--brand-ink); }
.xs-opt[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.xs-group { padding: 9px 10px 4px; font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.xs-group:first-child { padding-top: 4px; }
.filters .xs, .xs.inline { width: auto; min-width: 172px; }
.filters .xs-btn { min-height: var(--btn-h); }

/* ================================================================== tables */
.table-wrap { overflow-x: auto; border-radius: var(--r-lg); }
.table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-base); }
.table th, .table td { padding: 14px 16px; text-align: left; vertical-align: middle; border-bottom: 1px solid var(--line-2); }
/* The row reaches the card edge instead of stopping short of it. */
.table th:first-child, .table td:first-child { padding-left: 18px; }
.table th:last-child, .table td:last-child { padding-right: 18px; }
.table th {
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; background: var(--card-2); white-space: nowrap;
  border-bottom-color: var(--line); padding-top: 11px; padding-bottom: 11px;
}
.table thead th:first-child { border-radius: var(--r-sm) 0 0 0; }
.table thead th:last-child { border-radius: 0 var(--r-sm) 0 0; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr.row-link { cursor: pointer; }
.table td .title { font-weight: 600; color: var(--ink); display: block; line-height: 1.4; }
.table td .title a { color: inherit; }
.table td .meta { font-size: var(--fs-sm); color: var(--muted); display: block; margin-top: 3px; }
.table .num { font-family: var(--mono); font-size: var(--fs-sm); color: var(--muted); white-space: nowrap; }
.table .clip { width: 15px; height: 15px; color: var(--muted-2); display: inline-block; vertical-align: -3px; margin-left: 6px; }
.table.compact th, .table.compact td { padding: 11px 13px; }

/* Case list: every column but the first is shrink-to-fit (`width: 1%` with nowrap is
   the reliable way to say "only as wide as your content"), so the title column takes
   all the slack. Without it the browser shares the width out evenly and the status and
   severity pills end up boxed into narrow columns with the title wrapping beside them —
   which is what made the list read as crowded. */
.table.cases td:not(.primary), .table.cases th:not(:first-child) { width: 1%; white-space: nowrap; }
.table.cases td.primary { min-width: 16rem; }

/* =================================================================== lists */
.list { display: flex; flex-direction: column; }
.list-item { display: flex; gap: 12px; align-items: flex-start; padding: 13px 0; border-bottom: 1px solid var(--line-2); }
.list-item:last-child { border-bottom: 0; }
.list-item .ic {
  width: 34px; height: 34px; border-radius: var(--r-sm); display: grid; place-items: center;
  flex: none; background: var(--brand-soft); color: var(--brand); border: 1px solid var(--brand-line);
}
.list-item .ic svg { width: 16px; height: 16px; }
.list-item .ic.blue { background: var(--blue-soft); color: var(--blue); border-color: var(--blue-line); }
.list-item .ic.green { background: var(--green-soft); color: var(--green); border-color: var(--green-line); }
.list-item .ic.violet { background: var(--violet-soft); color: var(--violet); border-color: transparent; }
.list-item .t { font-weight: 600; font-size: var(--fs-base); color: var(--ink); }
.list-item .d { font-size: var(--fs-sm); color: var(--muted); margin-top: 1px; }
.list-item .x { font-size: var(--fs-sm); color: var(--ink-2); margin-top: 5px; line-height: 1.5; }

/* ------------------------------------------------------------- KB cards */
.kb-card {
  display: flex; flex-direction: column; gap: 8px; padding: 17px; height: 100%;
  overflow: hidden;
}
.kb-card::after {
  content: ""; position: absolute; inset: auto -40% -70% auto; width: 190px; height: 190px;
  border-radius: 50%; pointer-events: none; opacity: 0;
  background: radial-gradient(circle, var(--brand-soft-2) 0%, transparent 70%);
  transition: opacity var(--t) var(--ease);
}
.kb-card:hover { border-color: var(--brand-line); text-decoration: none; }
.kb-card:hover::after { opacity: 1; }
.kb-card .kb-title { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-md); color: var(--ink); letter-spacing: -.015em; }
.kb-card:hover .kb-title { color: var(--brand-ink); }
.kb-card .kb-desc { font-size: var(--fs-base); color: var(--muted); line-height: 1.55; }
.kb-card .kb-meta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: auto; padding-top: 6px; font-size: var(--fs-sm); color: var(--muted); }
.kb-card .kb-num { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }

/* ================================================================== bars
   The progress rings that used to sit on the dashboard went with the support-plan card; the
   plan is summarised as figures in the hero strip now and broken down on /support-plan, which
   has always used these bars rather than rings. */
.bar { height: 8px; border-radius: var(--r-full); background: var(--line-2); overflow: hidden; }
:root[data-theme="dark"] .bar { background: var(--card-3); }
.bar > i {
  display: block; height: 100%; width: 0; border-radius: inherit;
  background: linear-gradient(90deg, var(--brand), var(--brand-2));
  transition: width 1s var(--ease-out);
}
.bar.navy > i { background: linear-gradient(90deg, var(--navy-accent), var(--navy-accent-2)); }
.bar.green > i { background: linear-gradient(90deg, #16a34a, #4ade80); }
.bar.thin { height: 6px; }
.usage-rows { display: grid; gap: 10px; }
.usage-row { display: grid; grid-template-columns: 96px 1fr 44px; align-items: center; gap: 10px; font-size: var(--fs-sm); }
.usage-row .l { color: var(--ink-2); font-weight: 600; }
.usage-row .c { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
.usage-row .bar { height: 10px; }

/* ================================================================= dialogs */
dialog.modal {
  border: 0; padding: 0; border-radius: var(--r-xl); background: var(--card); color: var(--ink);
  width: min(720px, calc(100vw - 32px)); max-height: calc(100vh - 48px);
  box-shadow: var(--shadow-lg); border: 1px solid var(--line);
}
dialog.modal.narrow { width: min(460px, calc(100vw - 32px)); }
dialog.modal::backdrop { background: var(--overlay); backdrop-filter: blur(3px); }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 17px 20px; border-bottom: 1px solid var(--line-2); }
.modal-head h2 { font-size: var(--fs-lg); }
.modal-body { padding: 18px 20px; overflow: auto; max-height: calc(100vh - 180px); }
.modal-foot { padding: 13px 20px 17px; border-top: 1px solid var(--line-2); display: flex; justify-content: flex-end; gap: 10px; }

/* ======================================================= empty & skeleton */
.empty { text-align: center; padding: 40px 20px; color: var(--muted); }
.empty .empty-ic {
  width: 60px; height: 60px; margin: 0 auto 16px; border-radius: var(--r-lg);
  display: grid; place-items: center;
  background: var(--card-3); color: var(--muted-2); border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}
.empty .empty-ic svg { width: 26px; height: 26px; }
.empty h3 { color: var(--ink); margin-bottom: 5px; font-size: var(--fs-md); }
.empty p { max-width: 400px; margin: 0 auto; line-height: 1.6; }
.empty .btn { margin-top: 16px; }
.empty.compact { padding: 22px 20px; }
.empty.compact .empty-ic { width: 46px; height: 46px; border-radius: var(--r); margin-bottom: 11px; }
.empty.compact .empty-ic svg { width: 21px; height: 21px; }
.skeleton {
  background: linear-gradient(90deg, var(--line-2), var(--line-3), var(--line-2));
  background-size: 200% 100%; animation: shimmer 1.3s infinite;
  border-radius: var(--r-xs); min-height: 14px;
  display: block;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* Placeholders for the dashboard panels, which arrive one request after the page.
   They are built from the real components (.kpi, .kb-card, .hero-stat) so the page does not
   move when the answer lands: same padding, same radius, same grid, same height. A skeleton
   that is not the shape of the thing it stands in for is just a different kind of flash. */
.sk > * { pointer-events: none; }
.hero-stat.sk .hs-ic { background: rgba(255, 255, 255, .1); }
.hero-stat.sk .skeleton {
  background: linear-gradient(90deg, rgba(255, 255, 255, .1), rgba(255, 255, 255, .2), rgba(255, 255, 255, .1));
  background-size: 200% 100%;
}
.card.sk, .sk-plan { box-shadow: var(--shadow-sm); }
/* Sized to the real plan card — rings, their labels and the date line — so the page does not
   lurch downwards when the answer arrives. */
.sk-plan .card-body { min-height: 244px; display: grid; place-items: center; }
.sk-ring { width: 152px; height: 152px; border-radius: 50%; }
.sk-rows { display: grid; gap: 1px; }
.sk-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 17px 2px; border-bottom: 1px solid var(--line-2);
}
.sk-row:last-child { border-bottom: 0; }
.sk-row .skeleton { height: 13px; }
/* A case row carries a title and a line of meta under it; a news or announcement row carries a
   title and a clamped paragraph. Both are taller than one line of text. */
.sk-cases .sk-row { padding: 24px 2px; }
.sk-tall .sk-row { padding: 26px 2px; }
.kb-card.sk { gap: 9px; min-height: 148px; }

/* =================================================================== pager */
.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 16px; flex-wrap: wrap; font-size: var(--fs-base); color: var(--muted); }
.pager .pages { display: flex; gap: 4px; }
.pager .pages a, .pager .pages span {
  min-width: 34px; height: 34px; display: inline-grid; place-items: center;
  border-radius: var(--r-xs); border: 1px solid var(--line); background: var(--card);
  color: var(--ink-2); text-decoration: none; padding: 0 8px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.pager .pages a:hover { background: var(--card-3); }
.pager .pages .cur { background: var(--brand-grad); color: #fff; border-color: var(--brand-edge); box-shadow: var(--btn-sheen), var(--btn-contact); }
.pager .pages .gap { border: 0; background: transparent; }

/* ============================================================ page modules */
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }
.page-head h1 { font-size: var(--fs-2xl); }
.page-head p { color: var(--muted); margin-top: 4px; font-size: var(--fs-md); }
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 28px 0 12px; flex-wrap: wrap; }
.section-head h2 { font-size: var(--fs-lg); }

/* -------------------------------------------------------------- plan card */
.plan-card .plan-name { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-lg); letter-spacing: -.014em; }
.plan-card .plan-dates { font-size: var(--fs-sm); color: var(--muted); }
.plan-rings { display: flex; justify-content: center; gap: 28px; flex-wrap: wrap; padding: 6px 0 4px; }
.plan-blocked { margin-top: 14px; }

/* ------------------------------------------------------------ case detail */
.detail-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 15px 18px; margin: 0; }
.dl.one { grid-template-columns: minmax(0, 1fr); }
.dl > div { min-width: 0; }
/* A hairline between two blocks inside one card. Cheaper than splitting the card and
   clearer than a margin, which at small sizes reads as "badly spaced" rather than
   "separate". */
/* A hairline between two blocks inside one card. Painted as a 1px box rather than a
   border: a hairline border on an odd-width element lands between device pixels on a
   fractional-DPI display and renders with a notch in it.

   Named .rule, not .divider — .divider is already taken further down by the sign-in
   page's "or" separator, which is a flex row with a line either side of a word. Two
   rules of the same name is how you get a horizontal rule that quietly turns into a
   flex container with a gap in the middle of it. */
.rule { border: 0; height: 1px; background: var(--line-2); margin: 16px 0; }
.ident-name { font-weight: 700; font-size: var(--fs-md); letter-spacing: -.01em; }
.dl dt { font-size: var(--fs-xs); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); font-weight: 700; margin-bottom: 3px; }
.dl dd { margin: 0; font-size: var(--fs-base); font-weight: 600; overflow-wrap: anywhere; }
.desc { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; line-height: 1.65; color: var(--ink-2); }
.timeline { display: flex; flex-direction: column; gap: 14px; }
.note { display: flex; gap: 12px; }
.note .avatar { width: 32px; height: 32px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
.note.agent .avatar { background: linear-gradient(135deg, var(--navy-600), var(--navy)); box-shadow: var(--shadow-sm); }
.note .bubble { flex: 1; min-width: 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 11px 13px; box-shadow: var(--shadow-sm); }
.note.agent .bubble { background: var(--blue-soft); border-color: var(--blue-line); }
.note .who { font-weight: 700; font-size: var(--fs-sm); display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.note .who time { font-weight: 500; color: var(--muted); font-size: var(--fs-sm); }
.note .txt { white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-base); margin-top: 5px; line-height: 1.55; }
.attachment { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--card); }
.attachment .ft { width: 34px; height: 34px; border-radius: var(--r-xs); background: var(--card-3); display: grid; place-items: center; font-size: 10px; font-weight: 800; color: var(--muted); text-transform: uppercase; flex: none; border: 1px solid var(--line-2); }
.attachment .fn { flex: 1; min-width: 0; }
.attachment .fn .n { font-weight: 600; font-size: var(--fs-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attachment .fn .m { font-size: var(--fs-xs); color: var(--muted); }
.attachment .acts { display: flex; gap: 6px; }
.status-banner {
  display: flex; gap: 14px; align-items: center; padding: 18px 20px; border-radius: var(--r-xl);
  color: #fff; position: relative; overflow: hidden; isolation: isolate;
  background: linear-gradient(120deg, #1b1f4a 0%, #141737 55%, #0c0e22 100%);
  border: 1px solid var(--navy-line); box-shadow: var(--shadow-md);
}
.status-banner::before {
  content: ""; position: absolute; inset: -50%; z-index: -1; pointer-events: none;
  background: radial-gradient(30% 44% at 88% 20%, rgba(236, 95, 36, .5), transparent 68%);
  filter: blur(14px);
}
.status-banner .num { font-family: var(--mono); font-size: var(--fs-base); opacity: .7; }
.status-banner h1 { color: #fff; font-size: var(--fs-xl); }
.status-banner .pills { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.status-banner .pill { background: rgba(255, 255, 255, .12); border-color: rgba(255, 255, 255, .22); color: #fff; backdrop-filter: blur(6px); }

/* ------------------------------------------------------------------ events */
.event { display: grid; grid-template-columns: 66px 1fr; gap: 16px; padding: 17px; }
.event .date {
  text-align: center; border-radius: var(--r); background: var(--brand-soft);
  color: var(--brand-ink); padding: 9px 4px; align-self: start; border: 1px solid var(--brand-line);
}
.event .date .d { font-family: var(--font-display); font-size: 23px; font-weight: 700; line-height: 1; letter-spacing: -.023em; }
.event .date .m { font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .07em; }
.event.past .date { background: var(--slate-soft); color: var(--muted); border-color: var(--line); }
.event .ev-title { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-md); letter-spacing: -.015em; }
.event .ev-meta { display: flex; gap: 14px; flex-wrap: wrap; font-size: var(--fs-sm); color: var(--muted); margin-top: 5px; }
.event .ev-meta span { display: inline-flex; gap: 6px; align-items: center; }
/* Each timestamp stays whole, so a date range that has to wrap breaks at the dash instead
   of leaving "PM" on a line of its own with the dash floating between the two halves. */
.event .ev-meta time { white-space: nowrap; }
.event .ev-meta svg { width: 14px; height: 14px; }
.event .ev-desc { margin-top: 9px; font-size: var(--fs-base); color: var(--ink-2); white-space: pre-wrap; line-height: 1.6; }
.event .ev-actions { margin-top: 12px; display: flex; gap: 8px; }

/* ------------------------------------------------------- announcements/news */
.ann { padding: 17px 18px; }
.ann .ann-title { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-md); letter-spacing: -.015em; }
.ann .ann-date { font-size: var(--fs-sm); color: var(--muted); margin: 3px 0 9px; }
.ann .ann-body { font-size: var(--fs-base); color: var(--ink-2); white-space: pre-wrap; line-height: 1.6; }
.ann [data-expand] { margin-top: 7px; font-size: var(--fs-base); }
.news-card { padding: 15px 16px; }
.news-card:hover { border-color: var(--line); box-shadow: var(--shadow-sm); }
.news-card .n-top { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; }
.news-card .n-title { font-weight: 700; font-size: var(--fs-base); color: var(--ink); letter-spacing: -.01em; }
.news-card .n-body { font-size: var(--fs-base); color: var(--muted); margin-top: 6px; line-height: 1.55; }

/* ------------------------------------------------------------- search page */
.result-group h2 { font-size: var(--fs-sm); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 9px; }
mark { background: var(--mark); color: var(--mark-ink); padding: 0 3px; border-radius: 3px; font-weight: 600; }

/* ----------------------------------------------------------------- article */
.article { max-width: 800px; }
.article-body { font-size: var(--fs-md); line-height: 1.78; color: var(--ink-2); }
.article-body h1, .article-body h2, .article-body h3 { margin: 30px 0 12px; color: var(--ink); }
.article-body h1 { font-size: var(--fs-2xl); }
.article-body h2 { font-size: var(--fs-xl); }
.article-body p { margin: 0 0 16px; }
.article-body ul, .article-body ol { margin: 0 0 16px; padding-left: 24px; }
.article-body li { margin-bottom: 6px; }
.article-body a { font-weight: 500; text-decoration: underline; text-decoration-color: var(--blue-line); text-underline-offset: 2px; }
.article-body img { border-radius: var(--r); margin: 10px 0; box-shadow: var(--shadow-sm); }
.article-body table { width: 100%; border-collapse: collapse; margin: 12px 0 18px; font-size: 14px; }
.article-body th, .article-body td { border: 1px solid var(--line); padding: 9px 11px; text-align: left; }
.article-body th { background: var(--card-2); font-weight: 700; }
.article-body pre { background: var(--navy); color: #f1f5f9; padding: 15px; border-radius: var(--r); overflow: auto; font-size: var(--fs-base); border: 1px solid var(--navy-line); }
.article-body code { font-family: var(--mono); font-size: var(--fs-base); background: var(--slate-soft); padding: 1px 5px; border-radius: 4px; color: var(--brand-ink); }
.article-body pre code { background: none; padding: 0; color: inherit; }
.article-body blockquote {
  border-left: 3px solid var(--brand); margin: 0 0 16px; padding: 8px 16px;
  color: var(--ink-2); background: var(--brand-soft); border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.article-body hr { border: 0; border-top: 1px solid var(--line); margin: 26px 0; }
.feedback-box {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding: 16px 18px; background: var(--brand-soft); border-top: 1px solid var(--brand-line);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.stars { display: inline-flex; gap: 2px; }
.stars button { background: none; border: 0; padding: 2px; color: var(--line); cursor: pointer; }
.stars button svg { width: 23px; height: 23px; fill: currentColor; }
.stars button.on { color: var(--brand-2); }
.stars:hover button { color: var(--brand-2); }
.stars:hover button:hover ~ button { color: var(--line); }

/* -------------------------------------------------------------------- auth
   A single column centred on the branded backdrop. */
.auth {
  min-height: 100vh; display: grid; place-items: center;
  padding: clamp(28px, 5vh, 64px) 20px;
  position: relative; overflow: hidden; isolation: isolate;
  background: linear-gradient(168deg, #1b1f52 0%, #141738 46%, #0b0d24 100%);
}
/* Aurora: three soft colour fields drifting on a long loop. A flat gradient
   reads as a screenshot; this gives the page depth and a sense of being alive. */
.auth::before {
  content: ""; position: absolute; inset: -30%; z-index: -2; pointer-events: none;
  background:
    radial-gradient(36% 40% at 76% 14%, rgba(236, 95, 36, .6), transparent 68%),
    radial-gradient(32% 36% at 16% 84%, rgba(37, 99, 235, .5), transparent 68%),
    radial-gradient(28% 30% at 64% 68%, rgba(248, 142, 21, .3), transparent 68%);
  filter: blur(14px);
  animation: aurora 26s var(--ease) infinite alternate;
}
/* Fine dot matrix, vignetted so it dissolves toward the edges. */
.auth::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: .5;
  background-image: radial-gradient(rgba(255, 255, 255, .18) 1px, transparent 1px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(70% 70% at 50% 45%, #000 12%, transparent 74%);
  mask-image: radial-gradient(70% 70% at 50% 45%, #000 12%, transparent 74%);
}
@keyframes aurora {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(3%, -2.5%, 0) scale(1.1); }
  100% { transform: translate3d(-2.5%, 3%, 0) scale(1.05); }
}
.auth-card { position: relative; width: min(452px, 100%); }
/* The lockup: wordmark over service line.
   The XEETEK mark is hairline strokes, wide letter gaps and a handful of solid
   orange squares. A 34px/650 orange line under it read as a second, much heavier
   logo bolted on — and it fought the card's own <h1> for the job of page heading.
   This is a sub-wordmark instead, built out of the mark's own grammar:
   letterspaced caps, hairline rules, one orange square terminating each rule.
   Same vocabulary as the sidebar's .tagline, scaled up for the hero. */
.auth-brand { margin: 0 0 clamp(22px, 4vh, 32px); }
.auth-logo { display: block; width: 172px; height: auto; margin: 0 auto 17px; }
.auth-title {
  margin: 0; display: flex; align-items: center; justify-content: center;
  /* Everything below is sized in em off this one font-size — rules, gaps and the
     tracking pullback — so the lockup scales as a single object instead of its
     parts drifting against each other at different viewport widths. */
  --tracking: clamp(.24em, .8vw, .3em);
  gap: 1.15em;
  font-family: var(--font); color: rgba(255, 255, 255, .8);
  font-size: clamp(11px, 2.4vw, 12.5px); font-weight: 500; line-height: 1;
  letter-spacing: var(--tracking); text-transform: uppercase;
  white-space: nowrap;
}
/* letter-spacing also lands after the final E, which would push the glyphs right of
   centre by that much. Cancelling it against the same custom property keeps the two
   in lockstep when the clamp resolves differently. */
.auth-title > span { margin-inline-end: calc(var(--tracking) * -1); }
/* Each flank is one element carrying two background layers: the hairline rule,
   and the 4px orange square that terminates it on the text side. Drawn as
   backgrounds so the lockup stays a single <p> in the markup. */
.auth-title::before, .auth-title::after {
  /* Shrinkable, with a floor: at 320px the caps alone very nearly fill the column, so
     the rules yield first and the wordline stays intact. The floor keeps the orange
     square — the part that ties back to the mark — from being squeezed out. */
  content: ""; flex: 0 1 6.5em; min-width: .9em; height: 4px;
  background-repeat: no-repeat; background-size: 4px 4px, 100% 1px;
}
.auth-title::before {
  background-image:
    linear-gradient(var(--brand-logo), var(--brand-logo)),
    linear-gradient(90deg, transparent, rgba(255, 255, 255, .34));
  background-position: right center, left center;
}
.auth-title::after {
  background-image:
    linear-gradient(var(--brand-logo), var(--brand-logo)),
    linear-gradient(270deg, transparent, rgba(255, 255, 255, .34));
  background-position: left center, right center;
}
.auth-card .card {
  padding: 34px 34px 28px; border-radius: var(--r-2xl);
  /* Three layers, because one drop shadow on a dark backdrop leaves the card
     looking cut out of the page: the stock lift, a tight dark contact ring that
     defines the edge against the aurora, and a wide warm halo so the card sits
     in the light rather than on top of it. */
  box-shadow:
    var(--shadow-lg),
    0 0 0 1px rgba(8, 10, 26, .45),
    0 0 90px -24px rgba(236, 95, 36, .26);
}
.auth-card h1 { font-size: var(--fs-2xl); letter-spacing: -.019em; margin-bottom: 7px; }
.auth-card .intro { color: var(--muted); margin-bottom: 24px; font-size: 14px; line-height: 1.6; }
.auth-card .form-grid { gap: 15px; }
/* The card's footer: a hairline to close the form off, then the way out. Centred,
   because there is one control in it — a lone link shoved to the left edge by
   space-between was most of why this corner looked unfinished. */
.auth-links {
  display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line);
  font-size: var(--fs-base);
}
.auth-foot { text-align: center; color: rgba(255, 255, 255, .6); font-size: var(--fs-sm); margin: 18px 0 0; }
.auth-fine { text-align: center; color: rgba(255, 255, 255, .36); font-size: var(--fs-sm); margin: 6px 0 0; }
.divider { display: flex; align-items: center; gap: 12px; color: var(--muted-2); font-size: var(--fs-sm); margin: 20px 0 15px; font-weight: 600; }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* landing (anonymous home) */
.landing-actions { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 22px; }

/* error page */
.error-wrap { min-height: 60vh; display: grid; place-items: center; }
.error-code {
  font-family: var(--font-display); font-size: 76px; font-weight: 700;
  letter-spacing: -.04em; line-height: 1;
  background: var(--brand-grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--brand);
}

/* ============================================= motion & interaction polish
   One easing vocabulary, one set of durations. Nothing in this layer carries
   meaning on its own, so it is all switched off under prefers-reduced-motion. */
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes rise-in { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
@keyframes vt-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rail { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes modal-in { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes toast-in { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }

/* ------------------------------------------------------------ page entrance
   This used to be a per-block stagger: every child of .content rose in turn, so
   the last block on a dashboard landed 550ms after the first. It read as the page
   still loading long after the server was done, which is most of why navigation
   felt slow. It is now one short move for the whole content column. */
.content { animation: rise-in 200ms var(--ease-out) both; }
.auth-card { animation: rise var(--t-slow) var(--ease-out) both; }

/* Cross-document view transitions: the shell (sidebar, topbar, footer) is held
   still and only the content column cross-fades, which is what makes clicking a
   nav item feel like moving within one app rather than replacing a document.
   Chromium today; everywhere else simply gets the entrance above. */
@view-transition { navigation: auto; }
@supports (view-transition-name: none) {
  /* The entrance animation would otherwise play *inside* the transition snapshot. */
  .content { animation: none; }
  .content { view-transition-name: page; }
  /* The shell is everything outside .content, so its snapshot differs only in the
     topbar title. A short cross-fade there is invisible on the sidebar and stops
     the old title hanging around while the content column animates. */
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 140ms; }
  ::view-transition-old(page) { animation: vt-out 90ms var(--ease) both; }
  ::view-transition-new(page) { animation: vt-in 220ms var(--ease-out) both; }
  ::view-transition-group(page) { animation-duration: 220ms; }
}

/* cards */
.card { transition: box-shadow var(--t) var(--ease), border-color var(--t) var(--ease), transform var(--t) var(--ease), background-color var(--t) var(--ease); }
.kpi:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--kpi-line, var(--line)); }
a.kb-card { transition: transform var(--t) var(--ease-out), box-shadow var(--t) var(--ease), border-color var(--t) var(--ease); }
a.kb-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
a.kb-card .kb-title { transition: color var(--t-fast) var(--ease); }
.hero-stat { transition: background var(--t) var(--ease), border-color var(--t) var(--ease), transform var(--t) var(--ease); }
.hero-stat:hover { background: rgba(255, 255, 255, .13); transform: translateX(3px); }

/* buttons — the press is the whole point: down 1px with an inset shadow reads as a
   key being depressed, where a scale() or a bounce reads as a toy. The shadow
   transition is deliberately faster on the way down (t-fast) than the hover lift. */
.btn { transition: background var(--t) var(--ease), border-color var(--t-fast) var(--ease), box-shadow var(--t) var(--ease), transform var(--t-fast) var(--ease), color var(--t-fast) var(--ease); }
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(1px); transition-duration: var(--t-fast); }
.btn.back::before { transition: transform var(--t) var(--ease-spring); }
.btn-primary::after { transition: transform 640ms var(--ease-out); }
.link-btn::after, .text-link::after { transition: transform var(--t) var(--ease-out); }
.icon-btn { transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t) var(--ease), transform var(--t-fast) var(--ease); }
.icon-btn:hover { transform: translateY(-1px); }
.icon-btn:active { transform: translateY(1px); }
.icon-btn.plain:hover { transform: none; }

/* pills & chips */
.pill, .tag { transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease); }
.chip { transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), box-shadow var(--t) var(--ease); }
.chip:active { transform: scale(.96); }

/* tables — a brand rail reads faster than a full-width colour wash */
.table tbody tr.row-link { transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); }
.table tbody tr.row-link:hover { background: var(--card-2); box-shadow: inset 3px 0 0 var(--brand); }
.table tbody tr.row-link:active { background: var(--card-3); }

/* lists */
.list-item .ic { transition: transform var(--t) var(--ease-spring), background var(--t) var(--ease); }
.list-item:hover .ic { transform: scale(1.08) rotate(-5deg); }

/* sidebar */
.nav-item { transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); }
.nav-item svg { transition: opacity var(--t-fast) var(--ease), transform var(--t) var(--ease-spring), color var(--t) var(--ease); }
.nav-item:hover svg { opacity: 1; transform: scale(1.12); }
.nav-item[aria-current="page"]::before { animation: rail var(--t-slow) var(--ease-out) both; }
.avatar { transition: transform var(--t) var(--ease-spring), box-shadow var(--t) var(--ease); }
.user-card { transition: background var(--t) var(--ease), border-color var(--t) var(--ease); }
.user-card:hover { background: rgba(255, 255, 255, .08); }
.user-card:hover .avatar { transform: scale(1.06); }
.user-card .chev { transition: transform var(--t) var(--ease-out), color var(--t) var(--ease); }
.user-card:hover .chev { transform: translateX(2px); color: rgba(255, 255, 255, .7); }

/* forms */
input, select, textarea { transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), background var(--t-fast) var(--ease); }
input:hover:not(:focus), textarea:hover:not(:focus), select:hover:not(:focus) { border-color: var(--input-line-hover); }
.checkbox input { transition: transform var(--t-fast) var(--ease-spring); }
.checkbox:active input { transform: scale(.9); }
.dropzone { transition: border-color var(--t) var(--ease), background var(--t) var(--ease), transform var(--t) var(--ease); }
.dropzone.drag { transform: scale(1.01); }
.file-row { animation: rise var(--t) var(--ease-out) both; transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease); }
.seg a, .seg button { transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); }
.pw-toggle { transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease); }

/* pager */
.pager .pages a { transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); }
.pager .pages a:hover { border-color: var(--input-line-hover); transform: translateY(-1px); }

/* dialogs & toasts */
dialog.modal { animation: modal-in var(--t) var(--ease-out); }
dialog.modal::backdrop { animation: fade-in var(--t) var(--ease); }
.toast { animation: toast-in var(--t-slow) var(--ease-out); }
a { transition: color var(--t-fast) var(--ease); }
.empty .empty-ic { transition: transform var(--t-slow) var(--ease-spring); }
.empty:hover .empty-ic { transform: translateY(-2px) rotate(-3deg); }

/* ====================================================== background posts
   The comment box and the new-case form post without leaving the page (portal.js);
   these are the states they move through. Pending is dashed and dimmed, sent flashes
   a brand halo that fades, failed goes red and keeps the text with its actions. */
@keyframes note-in { from { opacity: 0; transform: translateY(8px) scale(.985); } to { opacity: 1; transform: none; } }
@keyframes note-settle { from { box-shadow: 0 0 0 0 rgba(236, 95, 36, .38); } to { box-shadow: 0 0 0 12px rgba(236, 95, 36, 0); } }
@keyframes shake { 0%, 100% { transform: translateX(0); } 20%, 60% { transform: translateX(-4px); } 40%, 80% { transform: translateX(4px); } }
.note.pending, .note.sent, .note.failed { animation: note-in var(--t-slow) var(--ease-out) both; }
.note .who .state { font-weight: 500; color: var(--muted); font-size: var(--fs-sm); display: inline-flex; align-items: center; gap: 6px; }
.note .who .state:empty, .note .who time:empty { display: none; }
.note.pending .bubble { border-style: dashed; opacity: .78; box-shadow: none; }
.note.pending .who .state::before {
  content: ""; width: 10px; height: 10px; border-radius: 50%; flex: none;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent); border-top-color: currentColor;
  animation: spin .7s linear infinite;
}
.note.sent .bubble { border-color: var(--brand-line); animation: note-settle 1.5s var(--ease-out) both; }
.note.failed .bubble { border-color: var(--red-line); background: var(--red-soft); animation: shake .42s var(--ease) both; }
.note.failed .who .state { color: var(--red-ink); font-weight: 600; }
.note .note-actions { display: flex; gap: 8px; margin-top: 10px; }
.note .note-actions[hidden] { display: none; }
textarea[data-autogrow] { min-height: 88px; resize: none; overflow-y: auto; }
kbd {
  font-family: var(--mono); font-size: 10.5px; padding: 1px 5px; border-radius: 5px;
  border: 1px solid var(--line); border-bottom-width: 2px; background: var(--card-2); color: var(--ink-2);
}

/* new-case submit status: ring while working, progress bar for the upload, check on success */
.submit-status {
  display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 6px 14px; align-items: center;
  padding: 14px 16px; border-radius: var(--r); border: 1px solid var(--brand-line);
  background: var(--brand-soft); animation: rise var(--t-slow) var(--ease-out) both;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease);
}
.submit-status[hidden] { display: none; }
.submit-status .ss-icon { position: relative; width: 40px; height: 40px; grid-row: span 2; }
.submit-status .ss-icon svg { position: absolute; inset: 0; width: 40px; height: 40px; }
.submit-status .ss-ring circle {
  fill: none; stroke: var(--brand); stroke-width: 2.4; stroke-linecap: round; stroke-dasharray: 40 17;
  transform-origin: 50% 50%; animation: spin 1s linear infinite;
}
.submit-status .ss-check { color: var(--green); opacity: 0; transform: scale(.4); transition: opacity var(--t) var(--ease), transform var(--t-slow) var(--ease-spring); }
.submit-status .ss-text { min-width: 0; }
.submit-status .ss-text strong { display: block; font-size: var(--fs-base); color: var(--ink); }
.submit-status .ss-bar { grid-column: 2; height: 6px; border-radius: var(--r-full); background: rgba(236, 95, 36, .18); overflow: hidden; }
.submit-status .ss-bar > i { display: block; height: 100%; width: 0; border-radius: inherit; background: linear-gradient(90deg, var(--brand), var(--brand-2)); transition: width var(--t) var(--ease-out); }
.submit-status.indeterminate .ss-bar > i { width: 40%; animation: ss-slide 1.2s var(--ease) infinite; }
@keyframes ss-slide { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
.submit-status.is-success { border-color: var(--green-line); background: var(--green-soft); }
.submit-status.is-success .ss-ring { display: none; }
.submit-status.is-success .ss-check { opacity: 1; transform: none; }
.submit-status.is-success .ss-bar > i { width: 100%; background: linear-gradient(90deg, #16a34a, #4ade80); animation: none; }
.alert.rise-in { animation: rise var(--t-slow) var(--ease-out) both; }

/* ------------------------------------------------------- mobile bottom nav
   Five slots is the most a thumb can aim at on a 360px screen. Four of them are the
   destinations customers actually use, plus the one action they come here to perform;
   the rest of the portal — plan, news, events, announcements, profile, sign out —
   lives behind "More", a <details> sheet rather than a popover or a dialog so it
   works with no script at all. portal.js only adds the niceties.

   The bar's frosted surface is a ::before layer rather than a background on the bar
   itself, deliberately: backdrop-filter makes an element the containing block for its
   fixed descendants, which would trap the sheet inside a 66px-tall box. */
.mobile-nav {
  display: none; position: fixed; inset: auto 0 0 0; z-index: 70;
  align-items: stretch; height: var(--bottom-nav-h);
  padding: 0 4px calc(env(safe-area-inset-bottom, 0px) * .6);
}
.mobile-nav::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: saturate(150%) blur(16px);
  -webkit-backdrop-filter: saturate(150%) blur(16px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px -16px rgba(22, 24, 43, .4);
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .mobile-nav::before { background: var(--card); }
}
.mobile-nav > * { flex: 1 1 0; min-width: 0; position: relative; z-index: 1; }
.mobile-nav details { height: 100%; }
.mn-item {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  height: 100%; border: 0; background: none; padding: 6px 2px 8px;
  color: var(--muted); font-size: 10px; font-weight: 600; line-height: 1.2;
  text-decoration: none; text-align: center; position: relative;
  -webkit-tap-highlight-color: transparent;
}
.mn-item svg { width: 21px; height: 21px; flex: none; }
.mn-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.mn-item[aria-current="page"], .mobile-nav details[open] > .mn-item { color: var(--brand-ink); }
.mn-item[aria-current="page"]::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 26px; height: 3px; border-radius: 0 0 3px 3px;
  background: linear-gradient(90deg, var(--brand), var(--brand-2));
}
/* The centre action is the one thing on the bar that is not navigation, so it is the
   one thing that does not look like the rest of it. */
.mn-item.mn-new { color: #fff; justify-content: center; gap: 0; padding-top: 0; }
.mn-item.mn-new::before { display: none; }
.mn-item.mn-new .mn-fab {
  display: grid; place-items: center; width: 46px; height: 46px; border-radius: 16px;
  background: var(--brand-grad); border: 1px solid var(--brand-edge);
  box-shadow: var(--btn-sheen), var(--brand-glow);
  margin-top: -14px;
}
.mn-item.mn-new .mn-fab svg { width: 23px; height: 23px; }
.mn-item.mn-new span { color: var(--muted); margin-top: 3px; }
.mn-item.mn-new[aria-current="page"] span { color: var(--brand-ink); }

.mobile-nav summary { list-style: none; cursor: pointer; height: 100%; }
.mobile-nav summary::-webkit-details-marker { display: none; }
.mn-sheet {
  position: fixed; inset: auto 0 var(--bottom-nav-h) 0; z-index: 2;
  background: var(--card); border-top: 1px solid var(--line);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--shadow-lg);
  padding: 8px 10px calc(14px + env(safe-area-inset-bottom, 0px) * .6);
  max-height: min(70vh, 520px); overflow-y: auto; overscroll-behavior: contain;
  animation: sheet-up 220ms var(--ease-out);
}
@keyframes sheet-up { from { transform: translateY(14px); opacity: 0; } to { transform: none; opacity: 1; } }
.mn-sheet::before {
  content: ""; display: block; width: 38px; height: 4px; border-radius: var(--r-full);
  background: var(--line); margin: 2px auto 10px;
}
.mn-sheet a, .mn-sheet button {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px; border-radius: var(--r-sm); border: 0; background: none;
  color: var(--ink); font-size: var(--fs-base); font-weight: 600; text-align: left;
}
.mn-sheet a:hover, .mn-sheet button:hover { background: var(--card-3); color: var(--ink); }
.mn-sheet a[aria-current="page"] { color: var(--brand-ink); background: var(--brand-soft); }
.mn-sheet svg { width: 19px; height: 19px; flex: none; color: var(--muted); }
.mn-sheet a[aria-current="page"] svg { color: var(--brand); }
.mn-sheet hr { border: 0; border-top: 1px solid var(--line-2); margin: 8px 4px; }
.mn-sheet .mn-user { gap: 11px; padding: 8px 12px 12px; }
.mn-sheet .mn-user .grow { min-width: 0; }
.mn-sheet .mn-user .name, .mn-sheet .mn-user .org { display: block; }
.mn-sheet .mn-user .name { font-weight: 700; font-size: var(--fs-base); }
.mn-sheet .mn-user .org { font-size: var(--fs-sm); color: var(--muted); font-weight: 500; }
/* Dimmed backdrop, and the tap target that closes the sheet without any script.
   z-index: -1 keeps it under the bar surface but inside the bar's stacking context,
   so it still covers the page. */
.mobile-nav details[open] > summary::after {
  content: ""; position: fixed; inset: 0 0 var(--bottom-nav-h) 0; z-index: -1;
  background: var(--overlay); backdrop-filter: blur(2px);
}
.topbar-brand { display: none; align-items: center; flex: none; margin-right: 4px; }
/* No opacity: the wordmark renders at the brand's own navy and orange, not a tint of them. */
.topbar-brand img { width: 76px; height: auto; }
.topbar-brand .brand-dark { display: none; }
:root[data-theme="dark"] .topbar-brand .brand-light { display: none; }
:root[data-theme="dark"] .topbar-brand .brand-dark { display: block; }

/* ============================================================== responsive
   Four steps, and each one is a change of layout rather than a nudge:

     >= 1600   big screens — a wider content column and one step up the type ramp,
               so a 27" monitor gets more portal rather than more margin
     <= 1180   two-column page grids fold to one
     <= 1024   the sidebar is forced to the labelled rail: 268px of navigation on a
               tablet leaves nothing for the page, and the toggle that would fix it
               is not discoverable enough to rely on
     <=  860   tables stop being tables and become one card per row
     <=  640   phones — navigation leaves the side entirely for a bottom bar, and
               the page finally gets the whole width

   The per-step dials are --gutter, --card-pad and --bottom-nav-h from the token
   block; components read them and need no breakpoints of their own. */

@media (min-width: 1600px) {
  :root {
    --content-max: 1520px;
    --gutter: 40px;
    --card-pad: 22px;
    --fs-xs: 12.5px;
    --fs-sm: 13.5px;
    --fs-base: 15px;
    --fs-md: 16px;
    --fs-lg: 19px;
    --fs-xl: 22px;
    --fs-2xl: 28px;
    --fs-3xl: 34px;
  }
  .kpi .value { font-size: 36px; }
  .kpi { min-height: 128px; }
}

/* One more step for very large monitors. Past this the cap stops growing: the case
   table is the widest thing on the page, and a row you have to track across 2000px is
   harder to read than one with margin either side of it. */
@media (min-width: 2000px) {
  :root { --content-max: 1680px; --gutter: 48px; }
}

@media (max-width: 1180px) {
  :root { --gutter: 22px; }
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-main, .grid-side { grid-template-columns: minmax(0, 1fr); }
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
  .hero { padding: 22px; }
  .hero-side { flex: 1 1 100%; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}

/* The sidebar is docked at every width down to the phone breakpoint. It used to slide
   in as an overlay drawer below 1024px, opened by a hamburger and dimming the page
   behind a blurred backdrop; the drawer, its button and the backdrop are all gone.
   Tablets get the labelled rail below, which is always on screen; phones get the
   bottom bar, which is always on screen and costs the page no width at all. */
@media (max-width: 1024px) {
  :root { --gutter: 20px; --card-pad: 18px; }
  /* Forced rail — wider than the desktop rail so every icon can carry its label:
     hover tooltips are no use on a touch screen. */
  body, body.sb-rail { --sb-w: 92px; }
  .sb-collapse { display: none; }
  .nav-item { flex-direction: column; gap: 5px; padding: 10px 2px; white-space: normal; }
  /* Wrap rather than ellipsize: "Knowledge base" over two lines still reads,
     "Knowledge b…" does not. */
  /* Wraps at spaces and nowhere else. overflow-wrap: break-word here produced
     "Announcement / s", which is worse than either wrapping or truncating. */
  .nav-item > span {
    display: block; font-size: 9px; line-height: 1.3; font-weight: 600;
    letter-spacing: -.005em; text-align: center; width: 100%;
    overflow: visible; text-overflow: clip; overflow-wrap: normal; word-break: normal;
  }
  .nav-item svg { width: 20px; height: 20px; }
  .nav-item::after { display: none; }
  .sb-brand { min-height: 56px; padding: 14px 0 12px; }
  .sb-nav { padding: 10px 6px 8px; }
  .topbar-search { flex: 0 1 210px; }
  .topbar-title h1 { font-size: var(--fs-lg); }
}

@media (max-width: 860px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
  /* …except number tiles, which stay two-up all the way down to a phone: they hold a
     label and a figure, so one per row is a column of mostly empty card. Cards with
     prose in them do go to one. */
  .grid-2:has(> .kpi), .grid-3:has(> .kpi), .grid-4:has(> .kpi) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .form-row { grid-template-columns: minmax(0, 1fr); }
  .topbar-search { display: none; }
  .topbar-title { flex: 1; }
  .topbar-title h1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topbar-title .subtitle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .hero { padding: 20px; border-radius: var(--r-lg); }
  .hero-side { grid-template-columns: minmax(0, 1fr); }
  .plan-rings { gap: 18px; }
  .status-banner { flex-wrap: wrap; }
  .status-banner .pills { margin-left: 0; width: 100%; }

  /* Tables collapse to cards. The fields inside a card sit two-up rather than one per
     line: on a tablet a single column of label/value rows makes one case as tall as the
     screen, and a list you cannot see two of is not a list. Below 560px they stack. */
  /* The whole table drops out of table layout, not just the rows: leaving <table> and
     <tbody> as table boxes around grid rows makes the browser generate anonymous table
     boxes, and one of them was drawing a stray rule above the first card. */
  .table.responsive, .table.responsive tbody { display: block; }
  .table.responsive thead { display: none; }
  .table.responsive tr {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px;
    border: 1px solid var(--line); border-radius: var(--r);
    margin-bottom: 11px; padding: 12px 15px; background: var(--card);
  }
  .table.responsive td.primary { grid-column: 1 / -1; }
  .table.responsive td,
  .table.cases td:not(.primary) {
    display: flex; justify-content: space-between; gap: 14px; padding: 9px 0;
    border-bottom: 1px solid var(--line-2); text-align: right; width: auto; white-space: normal;
  }
  /* An odd number of label/value cells leaves the last one alone on its row, so it takes
     the full width rather than half of it with a gap beside it. */
  .table.responsive td:last-child { border-bottom: 0; grid-column: 1 / -1; }
  .table.responsive td::before {
    content: attr(data-label); font-size: var(--fs-xs); letter-spacing: .07em;
    text-transform: uppercase; color: var(--muted); font-weight: 700; text-align: left; flex: none;
  }
  .table.responsive td.primary { flex-direction: column; text-align: left; align-items: flex-start; }
  .table.responsive td.primary,
  .table.cases td.primary { min-width: 0; padding-left: 0; padding-right: 0; }
  .table.responsive td:first-child, .table.responsive td:last-child { padding-left: 0; padding-right: 0; }
  .table.responsive td.primary::before, .table.responsive td.no-label::before { display: none; }
}

@media (max-width: 560px) {
  .table.responsive tr { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

@media (max-width: 640px) {
  /* Phones. The navigation moves to a bottom bar, which is where a thumb already is,
     and the page gets the full width back — a docked rail was taking a quarter of a
     390px screen and squeezing every case title into three lines. */
  :root { --gutter: 16px; --card-pad: 16px; }
  body, body.sb-rail { --sb-w: 0px; --bottom-nav-h: 66px; }
  .sidebar { display: none; }
  .mobile-nav, .topbar-brand { display: flex; }

  .topbar { gap: 10px; padding: 8px var(--gutter); min-height: 58px; }
  .topbar-title h1 { font-size: var(--fs-md); }
  .topbar-title .subtitle { font-size: var(--fs-xs); }
  .topbar .btn .txt { display: none; }
  .topbar .btn { padding: 0 11px; }
  .topbar-sep { display: none; }
  /* The topbar already carries a "New case" button on every page, so the bottom bar's
     centre action would be the same control twice on a 390px header. */
  .topbar-actions .btn-primary { display: none; }

  .page-head { margin-bottom: 16px; }
  .dl { grid-template-columns: minmax(0, 1fr); }

  /* The hero is the first thing on the dashboard and on a phone it was most of the
     first two screens: a heading, three stacked buttons and three stacked stat rows.
     Two of the buttons — cases and the knowledge base — are tabs on the bottom bar,
     one tap away, so they go; the stats become single-line rows instead of stacked
     label-over-value tiles. What is left is a greeting, one action and three numbers. */
  .hero { padding: 18px; gap: 16px; }
  .hero h2 { font-size: var(--fs-xl); }
  .hero .hero-sub { font-size: var(--fs-base); }
  .hero-actions { margin-top: 14px; }
  .hero-actions .btn-secondary { display: none; }
  .hero-actions .btn-primary { width: 100%; }
  .hero-side { gap: 7px; }
  .hero-stat { padding: 9px 12px; gap: 10px; }
  .hero-stat .hs-ic { width: 28px; height: 28px; }
  .hero-stat .grow { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
  .hero-stat .hs-v { font-size: var(--fs-base); }

  .grid-2:has(> .kpi), .grid-3:has(> .kpi), .grid-4:has(> .kpi) { gap: 12px; }
  .kpi .value { font-size: 26px; }
  .kpi { min-height: 0; padding: 14px; }
  .kpi .kpi-ic { width: 28px; height: 28px; top: 12px; right: 12px; }
  .kpi .kpi-ic svg { width: 15px; height: 15px; }
  .kpi .kpi-ic + .label { padding-right: 34px; }
  .kpi .delta { font-size: var(--fs-xs); margin-top: 6px; }
  .filters { width: 100%; }
  .filters > * { flex: 1 1 140px; }
  .filters .search, .filters .search.wide { flex: 1 1 100%; }
  .filters .xs, .xs.inline { min-width: 0; width: 100%; }
  .event { grid-template-columns: minmax(0, 1fr); }
  .event .date { display: inline-flex; gap: 6px; align-items: baseline; padding: 6px 11px; width: max-content; }
  .event .date .d { font-size: var(--fs-lg); }
  .usage-row { grid-template-columns: 72px 1fr 38px; }
  .card-head { gap: 12px; }
  .form-actions .btn { flex: 1 1 auto; }
  .plan-rings { gap: 14px; }
  .ring { width: 128px; height: 128px; }

  /* The drifting aurora on the hero and the sign-in backdrop is a 26-30 second loop of
     large blurred gradients — a continuous compositor repaint for decoration nobody is
     watching, and on a mid-range phone it is one of the few things on the page that
     costs battery. The gradients stay; only the movement stops. */
  .hero::before, .auth::before { animation: none; }
}


@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  .btn:hover, .kpi:hover, a.kb-card:hover, .hero-stat:hover,
  .pager .pages a:hover, .list-item:hover .ic, .empty:hover .empty-ic { transform: none; }
  .btn.back:hover::before { transform: rotate(-135deg); }
  /* A specular sweep is decoration with nothing to say; it goes rather than snaps. */
  .btn-primary::after { display: none; }
  /* The busy spinner reports state rather than decorating, so it keeps turning —
     slowed rather than frozen, because a stopped spinner tells the user nothing. */
  .btn[data-busy]::before, .note.pending .who .state::before, .submit-status .ss-ring circle,
  .submit-status.indeterminate .ss-bar > i { animation-duration: 1.5s !important; animation-iteration-count: infinite !important; }
  /* The view-transition pseudo tree is not matched by the universal selector above. */
  @view-transition { navigation: none; }
  ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .nav-progress { transition-duration: .01ms !important; }
}

@media print {
  /* Nothing animates on the way to the printer. The frame's margin-left is transitioned
     (340ms, for the rail toggle), and a browser that snapshots the page as soon as the
     print stylesheet applies catches it mid-transition and prints a 268px left margin
     where the sidebar used to be. */
  *, *::before, *::after { transition: none !important; animation: none !important; }
  .sidebar, .sb-collapse, .nav-progress, .topbar-search, .topbar-actions, .mobile-nav,
  .toasts, .foot, .form-actions, .hero-actions { display: none !important; }
  .frame { margin-left: 0; }
  .card { box-shadow: none; break-inside: avoid; }
  .content::before, .hero::before, .hero::after { display: none; }
  .hero, .status-banner { background: none; color: var(--ink); border-color: var(--line); }
  .hero h2, .status-banner h1 { color: var(--ink); }
  body { background: #fff; }
}
