/* MUST stay on line 1. CSS requires @import to precede every other rule except
   @charset/@layer — this sat below :root for a long time, so browsers dropped
   it and the whole app silently rendered in the Trebuchet MS fallback rather
   than the brand faces. index.html, login.html, manager.html and upload.html
   all link this file, so keeping the import here covers all four. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');

/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN TOKENS — enc0ded CI on an iOS-derived token system
   ═══════════════════════════════════════════════════════════════════════════

   Modelled on Apple's Human Interface Guidelines: a semantic colour system
   with grouped backgrounds and a label hierarchy, the SF type ramp, iOS radii
   and materials, and the standard spring curves.

   Two rules this system enforces that the previous palette did not:

   1. FILL vs TEXT are different colours. Every iOS system colour fails WCAG AA
      as body text on white — systemRed is 3.55:1, systemOrange 2.20:1,
      systemGreen 2.22:1, and even systemBlue only reaches 4.02:1. Apple gets
      away with it because those colours are used for fills, glyphs and large
      type. Anywhere a status colour carries *text*, use the -text variant,
      which is the same hue darkened until it clears 4.5:1.

   2. Tokens are grouped so a dark theme is a drop-in. Only the colour block
      below changes; nothing references a raw hex outside it. Dark is not
      defined yet (light-only was the call) but the seams are cut for it.

   enc0ded CI: deep green-black #0D1613, pale mint #E6EDE9, copper #C08A4E
   graded to bronze #8A5C24 and gold #E6C187. Dark is the brand's native
   scheme and uses the official values as-is (copper text 5.8:1 on a card,
   ink #1B130A on a copper fill 6.1:1). Light inverts the canvas to pale mint
   and moves the tint to bronze, the step of the copper ramp that carries
   body text on white (5.8:1). Type is Inter for reading and JetBrains Mono
   for the console voice: labels, table heads, metrics, eyebrows.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── Tint (brand) ──────────────────────────────────────────────────────
     What "interactive" looks like: links, selected states, filled buttons. */
  --tint:            #8A5C24;
  --tint-dark:       #74491A;   /* pressed */
  --tint-light:      #C08A4E;   /* secondary brand, gradients */
  --tint-wash:       rgba(138, 92, 36, .10);   /* tinted button fill */
  --tint-wash-hi:    rgba(138, 92, 36, .16);

  /* A tint that TEXT sits on is a different colour from a tint that is text.
     In dark the accent has to lighten to stay readable on black (#409CFF),
     but white on #409CFF is only 2.8:1 — so a filled button needs its own,
     darker fill. Light mode happens to be able to use one value for both. */
  --tint-fill:       #8A5C24;   /* filled button / selected row background */
  --on-tint:         #FFFFFF;   /* text and glyphs on --tint-fill           */
  --red-solid:       #B42318;   /* filled destructive; white reaches 5.4:1  */
  --on-red:          #FFFFFF;
  /* The raised pill in a segmented control, and control hover washes. */
  --raise:           rgba(255, 255, 255, .60);

  /* Legacy aliases. The app has ~200 references to these names across 3,600
     lines; re-pointing them here re-skins every screen at once and lets the
     old names retire gradually instead of in one risky sweep. */
  --logo-blue:    var(--tint);
  --logo-cyan:    var(--tint-light);
  --logo-ice:     #E6C187;
  --logo-teal:    #8A5C24;

  /* ── Grouped backgrounds ───────────────────────────────────────────────
     iOS layers content on a grey "grouped" canvas with white cards on top.
     That inversion — grey page, white content — is most of why an iOS screen
     reads as calm: the card edge does the separating, not a border. */
  --bg:              #EEF2EF;   /* systemGroupedBackground      */
  --bg-soft:         #E4EAE6;
  --surface:         #FBFCFB;   /* secondarySystemGrouped       */
  --surface2:        #EEF2EF;   /* tertiarySystemGrouped        */
  --surface3:        #E4EAE6;
  --card-bg:         #FBFCFB;

  /* ── Labels ────────────────────────────────────────────────────────────
     Apple's four-step hierarchy. Values are opaque equivalents of their
     rgba(60,60,67,α) definitions, darkened where needed to hold AA. */
  --text:            #0D1613;   /* label            — 15.9:1 on white */
  --muted:           #4B5C53;   /* secondaryLabel   —  6.9:1          */
  --text-tertiary:   #7A8A81;   /* tertiaryLabel    — decorative only */
  --text-quaternary: #BCC7C1;

  /* ── Separators ────────────────────────────────────────────────────────
     iOS hairlines are far lighter than the old 1.5px #B0D8EE border. Almost
     all "boxes" become a background change plus a hairline. */
  --separator:       rgba(13, 22, 19, .16);
  --separator-solid: #CFD8D3;
  --border:          var(--separator-solid);

  /* ── Fills ── translucent greys for inert controls and wells. */
  --fill:            rgba(36, 51, 43, .16);
  --fill-2:          rgba(36, 51, 43, .12);
  --fill-3:          rgba(36, 51, 43, .09);
  --fill-4:          rgba(36, 51, 43, .06);

  /* ── Semantic status ───────────────────────────────────────────────────
     iOS system colours for fills; darkened companions for text. See rule 1. */
  --red:             #D64B3B;   --red-text:    #B42318;   --red-dim:    #FBEAE7;
  --orange:          #D9934A;   --orange-text: #9A4A06;   --orange-dim: #FBF0E3;
  --green:           #3FAE72;   --green-text:  #18794E;   --green-dim:  #E6F4EC;
  --yellow:          #D8C06A;
  --teal:            #4E9C8A;
  --indigo:          #6C6FB8;
  --blue:            var(--tint);  --blue-text: var(--tint);  --blue-dim: #F4ECE1;
  /* Retained: --amber is referenced widely; orange is its iOS counterpart. */
  --amber:           var(--orange);
  --amber-dim:       var(--orange-dim);
  --cyan-glow:       rgba(192, 138, 78, .22);
  /* The light tint as bare channels, for the rgba() shadows and washes below
     the token blocks. Light-only on purpose: those literals were always the
     light blue in both schemes, and /branding.css re-points these two. */
  --tint-rgb:        138, 92, 36;
  --tint-light-rgb:  192, 138, 78;

  /* ── Materials ── translucency is the other half of the iOS look. */
  --material-thick:  rgba(251, 252, 251, .82);
  --material-thin:   rgba(251, 252, 251, .70);
  --material-blur:   saturate(180%) blur(20px);

  /* ── Typography ────────────────────────────────────────────────────────
     SF Pro on Apple hardware, then the platform equivalents. Manrope stays as
     the Reflex face for display type only — brand where it is seen, system
     where it is read.

     Sizes are in px, not rem, on purpose: iOS type is an absolute point ramp,
     and the root here is 15px, which would turn every step into an awkward
     fraction. Browser zoom still scales px; only a custom minimum font size
     would not apply. */
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text',
          'Segoe UI', system-ui, sans-serif;
  --font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-rounded:  var(--font);
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', 'Consolas', monospace;

  --text-large-title: 34px;   /* screen titles                */
  --text-title-1:     28px;
  --text-title-2:     22px;
  --text-title-3:     20px;
  --text-headline:    17px;   /* semibold row/section heads   */
  --text-body:        17px;   /* the iOS reading size         */
  --text-callout:     16px;
  --text-subhead:     15px;
  --text-footnote:    13px;
  --text-caption:     12px;
  --text-caption-2:   11px;

  /* Tracking. Apple tightens large type and opens up small type. */
  --track-large:  -0.4px;
  --track-title:  -0.3px;
  --track-body:   -0.2px;
  --track-caption: 0;

  /* Legacy type aliases, re-pointed onto the ramp. */
  --text-xs:      var(--text-caption-2);
  --text-sm:      var(--text-caption);
  --text-base:    var(--text-subhead);
  --text-md:      var(--text-headline);
  --text-lg:      var(--text-title-3);
  --text-xl:      var(--text-title-2);
  --text-2xl:     var(--text-large-title);

  /* ── Shape ─────────────────────────────────────────────────────────────
     iOS corner radii cluster around a few values by role, not by taste. */
  --radius-capsule: 999px;   /* buttons, chips, switches   */
  --radius-sheet:   16px;    /* sheets, large surfaces     */
  --radius-card:    12px;    /* grouped list containers    */
  --radius-control: 10px;    /* fields, small controls     */
  --radius-tight:   8px;

  --radius:    var(--radius-card);
  --radius-sm: var(--radius-control);

  /* ── Elevation ─────────────────────────────────────────────────────────
     Deliberately far lighter than before. iOS separates by background and
     hairline, not by drop shadow; heavy shadows are the fastest way to make
     a UI look non-native. Only genuinely floating surfaces get one. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 3px rgba(0, 0, 0, .06);
  --shadow:    0 4px 12px rgba(0, 0, 0, .08);
  --shadow-lg: 0 -2px 40px rgba(0, 0, 0, .16);   /* sheets rise from below */

  /* ── Spacing ── 4pt grid, absolute so it does not drift with the root. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;

  /* Minimum comfortable target. Apple's floor is 44pt and the app had chips
     and close buttons well under it. */
  --target: 44px;

  /* ── Motion ────────────────────────────────────────────────────────────
     The first curve is UIKit's sheet/navigation spring — it starts fast and
     settles, which is what makes iOS motion feel physical rather than linear. */
  --ease-spring: cubic-bezier(0.32, 0.72, 0, 1);
  --ease:        cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast:    150ms;
  --dur:         250ms;
  --dur-sheet:   400ms;

  /* ── Focus ── */
  --focus-ring: 0 0 0 4px rgba(138, 92, 36, .28);

  /* Tells the UA to render its own furniture (scrollbars, form control
     internals, the date-picker calendar) in the matching scheme. Without it
     you get light scrollbars on a dark page. Overridden per theme below. */
  color-scheme: light;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DARK THEME
   ═══════════════════════════════════════════════════════════════════════════

   iOS dark is not "light with the lightness flipped". Three things change
   structurally, and all three are why a naive inversion looks wrong:

   1. The GROUPED CANVAS GOES TO PURE BLACK and cards become #1C1C1E — the
      opposite arrangement to light, where the canvas is grey and cards are
      white. Elevation therefore reads as *lighter*, not as a shadow, which is
      why shadows are nearly useless in dark and the surface step does the work.

   2. EVERY ACCENT LIGHTENS. Reflex blue at #0066CC measures 3.06:1 on a
      #1C1C1E card — unreadable. Even Apple's own systemBlue (#0A84FF) only
      reaches 3.82:1 on an elevated #2C2C2E surface. The tint is lightened to
      #409CFF, which holds 4.92:1 at worst while staying recognisably Reflex.

   3. STATUS TEXT LIGHTENS FURTHER THAN THE FILLS. systemRed #FF453A fails on
      elevated surfaces (4.09:1), and on its own 16% wash it is worse. Badge
      labels use #FF8A80 / #6BB4FF, measured to clear 4.5:1 on the canvas, on
      a card, and on an elevated surface.

   Applied two ways, deliberately: the media query follows the OS (the Apple
   default — the system decides, not the app), and the [data-theme] attribute
   lets a person override it. The media query is guarded with
   :not([data-theme="light"]) so an explicit light choice wins over a dark OS.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    /* Surfaces. Lifted off pure black on purpose — see the note above the
       media query. Each step is a real, visible change in value, and cards
       additionally get a hairline edge (--card-edge) because at this end of
       the scale a background step alone is not enough to define a shape. */
    --bg:              #0D1613;   /* app canvas   */
    --bg-soft:         #0F1A16;
    --surface:         #111D18;   /* cards, sheets, nav          */
    --surface2:        #16241E;   /* wells, inputs, recessed     */
    --surface3:        #1C2C25;   /* hover on a recessed surface */
    --card-bg:         #111D18;
    --card-edge:       rgba(230, 237, 233, .08);
    --well-edge:       rgba(0, 0, 0, .30);

    --tint:            #C08A4E;
    --tint-dark:       #E6C187;   /* pressed reads LIGHTER in dark */
    --tint-light:      #E6C187;
    --tint-wash:       rgba(192, 138, 78, .14);
    --tint-wash-hi:    rgba(192, 138, 78, .22);
    /* Darker than --tint so white clears 4.5:1 on the filled button, while
       still standing off the card at 3.3:1. */
    --tint-fill:       #C08A4E;
    --on-tint:         #1B130A;
    --red-solid:       #B93A2C;
    --on-red:          #FFFFFF;
    --raise:           rgba(230, 237, 233, .07);

    /* Never pure white for body text — it glares against a dark field and is
       the classic tell of an inverted rather than designed dark theme. */
    --text:            #E6EDE9;
    --muted:           #93A59B;
    --text-tertiary:   #6B7D73;
    --text-quaternary: #3E4F46;

    --separator:       rgba(230, 237, 233, .09);
    --separator-solid: #24332B;
    --border:          #24332B;

    --fill:            rgba(230, 237, 233, .14);
    --fill-2:          rgba(230, 237, 233, .10);
    --fill-3:          rgba(230, 237, 233, .07);
    --fill-4:          rgba(230, 237, 233, .04);

    --red:             #E06A5A;   --red-text:    #F08A7C;   --red-dim:    #2E1A16;
    --orange:          #D9934A;   --orange-text: #E8A964;   --orange-dim: #2E2214;
    --green:           #5DBB86;   --green-text:  #7CD3A0;   --green-dim:  #142A1F;
    --yellow:          #D8C06A;
    --teal:            #6FB8A6;
    --indigo:          #9A9CE0;
    --blue:            #C08A4E;   --blue-text:   #D9A86C;   --blue-dim:   #2A2116;

    --material-thick:  rgba(17, 29, 24, .82);
    --material-thin:   rgba(17, 29, 24, .70);

    /* Shadows do little at this end of the scale; the surface step and the
       card edge carry elevation instead. Kept soft so nothing floats oddly. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .30);
    --shadow:    0 6px 18px rgba(0, 0, 0, .40);
    --shadow-lg: 0 -8px 48px rgba(0, 0, 0, .60);

    --focus-ring: 0 0 0 4px rgba(192, 138, 78, .35);
}
}

/* Explicit override. Kept as a separate block declaring the SAME properties as
   the media query above — a check in the harness compares the two token sets,
   because the failure mode here is silent drift where the toggle and the OS
   produce different-looking dark themes. */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Surfaces. Lifted off pure black on purpose — see the note above the
     media query. Each step is a real, visible change in value, and cards
     additionally get a hairline edge (--card-edge) because at this end of
     the scale a background step alone is not enough to define a shape. */
  --bg:              #0D1613;   /* app canvas   */
  --bg-soft:         #0F1A16;
  --surface:         #111D18;   /* cards, sheets, nav          */
  --surface2:        #16241E;   /* wells, inputs, recessed     */
  --surface3:        #1C2C25;   /* hover on a recessed surface */
  --card-bg:         #111D18;
  --card-edge:       rgba(230, 237, 233, .08);
  --well-edge:       rgba(0, 0, 0, .30);

  --tint:            #C08A4E;
  --tint-dark:       #E6C187;   /* pressed reads LIGHTER in dark */
  --tint-light:      #E6C187;
  --tint-wash:       rgba(192, 138, 78, .14);
  --tint-wash-hi:    rgba(192, 138, 78, .22);
  /* Darker than --tint so white clears 4.5:1 on the filled button, while
     still standing off the card at 3.3:1. */
  --tint-fill:       #C08A4E;
  --on-tint:         #1B130A;
  --red-solid:       #B93A2C;
  --on-red:          #FFFFFF;
  --raise:           rgba(230, 237, 233, .07);

  /* Never pure white for body text — it glares against a dark field and is
     the classic tell of an inverted rather than designed dark theme. */
  --text:            #E6EDE9;
  --muted:           #93A59B;
  --text-tertiary:   #6B7D73;
  --text-quaternary: #3E4F46;

  --separator:       rgba(230, 237, 233, .09);
  --separator-solid: #24332B;
  --border:          #24332B;

  --fill:            rgba(230, 237, 233, .14);
  --fill-2:          rgba(230, 237, 233, .10);
  --fill-3:          rgba(230, 237, 233, .07);
  --fill-4:          rgba(230, 237, 233, .04);

  --red:             #E06A5A;   --red-text:    #F08A7C;   --red-dim:    #2E1A16;
  --orange:          #D9934A;   --orange-text: #E8A964;   --orange-dim: #2E2214;
  --green:           #5DBB86;   --green-text:  #7CD3A0;   --green-dim:  #142A1F;
  --yellow:          #D8C06A;
  --teal:            #6FB8A6;
  --indigo:          #9A9CE0;
  --blue:            #C08A4E;   --blue-text:   #D9A86C;   --blue-dim:   #2A2116;

  --material-thick:  rgba(17, 29, 24, .82);
  --material-thin:   rgba(17, 29, 24, .70);

  /* Shadows do little at this end of the scale; the surface step and the
     card edge carry elevation instead. Kept soft so nothing floats oddly. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .30);
  --shadow:    0 6px 18px rgba(0, 0, 0, .40);
  --shadow-lg: 0 -8px 48px rgba(0, 0, 0, .60);

  --focus-ring: 0 0 0 4px rgba(192, 138, 78, .35);
}

/* ── Reset / base ──────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

html { font-size: 15px; }

body {
  font-family: var(--font);
  /* A flat grouped canvas. The page previously carried three radial brand
     glows, a 40px graph-paper grid and a diagonal gradient — six layers
     competing with the content for attention. iOS puts nothing behind the
     content and lets the white cards do the separating; removing the texture
     is what makes the cards read as raised at all. */
  background: var(--bg);
  color: var(--text);
  font-size: var(--text-subhead);
  letter-spacing: var(--track-body);
  min-height: 100vh;
  /* Previously unset, so every table, form and paragraph in the app ran at the
     UA default. Headings and dense components override this below. */
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: var(--blue); }

/* ── Header ────────────────────────────────────────────────────────────────── */
/* iOS navigation bar: a translucent material with a hairline underneath, and
   nothing else. The `saturate` is the part people miss — it is what makes
   content read as *through* the bar rather than behind frosted glass, and it
   is why Apple's materials look alive when you scroll under them. The 2px
   cyan rule and the 28px blue glow both go; a nav bar should not announce
   itself. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-6);
  background: var(--material-thick);
  border-bottom: 1px solid var(--separator);
  box-shadow: none;
  backdrop-filter: var(--material-blur);
  -webkit-backdrop-filter: var(--material-blur);
  position: sticky;
  top: 0;
  z-index: 100;
  gap: var(--space-4);
}

.header-left {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.header-logo-svg {
  height: 40px;
  width: auto;
  flex-shrink: 0;
  display: block;
  filter: drop-shadow(0 3px 10px rgba(var(--tint-rgb), .20));
}

.header-brand {
  display: flex;
  align-items: baseline;
  gap: 0.28rem;
  white-space: nowrap;
  line-height: 1;
}

.brand-reflex {
  font-size: 1.05rem;
  font-weight: 700;
  font-family: var(--font-display);
  color: var(--text);
  letter-spacing: -0.2px;
}

.brand-secops {
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--logo-blue);
  letter-spacing: 2px;
  text-transform: uppercase;
  font-family: var(--font-display);
  text-shadow: 0 2px 14px rgba(var(--tint-light-rgb),.35);
}

.header-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}

/* ── Tenant switcher (header) ─────────────────────────────── */
.tenant-switcher {
  max-width: 180px;
  padding: 0.4rem 0.6rem;
  font-size: 0.82rem;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font);
  cursor: pointer;
}

/* ── Global tenant selector (superadmin — header, shared across tabs) ─── */
.global-tenant-filter-wrap {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

/* A client's own logo in the header — beside ours in the portal, beside the
   organisation dropdown for staff. Height-constrained and object-fit:contain so
   any aspect ratio a client sends us sits correctly rather than stretching. */
.header-client-logo {
  height: 28px;
  max-width: 120px;
  width: auto;
  object-fit: contain;
  flex-shrink: 0;
  display: block;
  border-radius: 3px;
}

.global-tenant-label {
  font-size: 0.78rem;
  color: var(--text-muted, #a0aec0);
  white-space: nowrap;
  font-family: var(--font);
}

.global-tenant-select {
  max-width: 200px;
  padding: 0.4rem 0.6rem;
  font-size: 0.82rem;
  border: 1.5px solid var(--tint);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font);
  cursor: pointer;
}

/* ── Tenant multi-select (admin forms) ────────────────────── */
.tenant-multi-select {
  width: 100%;
  min-height: 90px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  color: var(--text-primary);
  font-size: 0.85rem;
  font-family: var(--font);
}
.tenant-multi-select option {
  padding: 2px 4px;
}

/* ── Buttons ───────────────────────────────────────────────────────────────── */
/* iOS has three button styles and they mean different things: filled (the one
   action that commits), tinted (secondary, tint-coloured on a wash), and plain
   (text only). None of them has a border, a gradient or a drop shadow — the
   fill is the affordance. The old .btn carried a 1.5px border, a blue-tinted
   shadow and a -2px hover lift all at once.

   Base is the "plain/grey" style; .btn-primary and friends fill it in. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 34px;
  padding: 7px 16px;
  /* An <a class="btn"> was inheriting the browser's link underline, so every
     anchor-as-button rendered underlined — the sidebar's Upload link included.
     A button that looks like a link is a button people hesitate over. */
  text-decoration: none;
  border: none;
  border-radius: var(--radius-capsule);
  background: var(--fill-3);
  color: var(--tint);
  font-family: var(--font);
  font-size: var(--text-subhead);
  font-weight: 590;              /* SF's semibold; falls back cleanly */
  letter-spacing: var(--track-body);
  line-height: 1.2;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  transition: background var(--dur-fast) var(--ease),
              transform  var(--dur-fast) var(--ease-spring),
              opacity    var(--dur-fast) var(--ease);
}

/* iOS presses respond by dimming and shrinking a touch — never by lifting.
   Hover is a desktop affordance the platform does not have, so it stays
   subtle and the press does the talking. */
.btn:hover  { background: var(--fill-2); }
.btn:active { transform: scale(0.97); opacity: .8; }
.btn:disabled,
.btn[disabled] { opacity: .35; cursor: not-allowed; transform: none; }

/* Filled — the committing action. One per view, ideally. */
.btn-primary {
  background: var(--tint-fill);
  color: var(--on-tint);
}
.btn-primary:hover  { background: var(--tint-dark); }
.btn-primary:active { background: var(--tint-dark); }

/* Tinted — secondary actions that are still the brand. */
.btn-secondary {
  background: var(--tint-wash);
  color: var(--tint);
}
.btn-secondary:hover { background: var(--tint-wash-hi); }

.btn-block { width: 100%; justify-content: center; margin-top: var(--space-4); }

/* Plain: text-only, for tertiary actions and sheet "Cancel". */
.btn-plain {
  background: none;
  color: var(--tint);
  padding-inline: var(--space-2);
}
.btn-plain:hover { background: var(--fill-4); }

/* ── Upload screen ─────────────────────────────────────────────────────────── */
#uploadScreen {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 2.5rem 1rem;
  min-height: calc(100vh - 56px);
}

.upload-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  width: 100%;
  max-width: 760px;
  box-shadow: var(--shadow);
}

.upload-card h2 {
  margin-bottom: 0.4rem;
  font-size: 1.15rem;
}

.upload-hint {
  color: var(--muted);
  font-size: 0.85rem;
  margin-bottom: 1.25rem;
}

/* .form-label is defined once, in the form-controls section further down. The
   uppercase micro-caps version that used to sit here was superseded rather
   than left to lose silently on source order — that pattern of two live
   definitions is exactly what made this file hard to reason about. */

.form-row {
  margin-bottom: 1rem;
}

/* Used two ways: as a <span> inside a .form-label ("(one per line)"), and as a
   <p> beneath a field. The margin-left only makes sense for the first — as a
   block it just indents the paragraph out of alignment with its input. */
.form-hint {
  font-size: 0.75rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.45;
  color: var(--muted);
  margin-left: 0.4rem;
}

p.form-hint {
  display: block;
  margin-left: 0;
  margin-top: 0.35rem;
}

input[type="date"] {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 0.85rem;
  padding: 0.45rem 0.7rem;
  outline: none;
  transition: border-color .15s;
  color-scheme: light;
}
input[type="date"]:focus { border-color: var(--blue); }

textarea {
  width: 100%;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: 'Cascadia Code', 'Consolas', monospace;
  font-size: 0.82rem;
  padding: 0.7rem;
  resize: vertical;
  outline: none;
  transition: border-color .15s;
}
textarea:focus { border-color: var(--blue); }

input[type="file"] {
  font-size: 0.82rem;
  color: var(--muted);
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.4rem 0.6rem;
  width: 100%;
  cursor: pointer;
}

.upload-file-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-top: 1rem;
}

.file-group { display: flex; flex-direction: column; }

.upload-error {
  background: #ffefed;
  border: 1px solid var(--red);
  border-radius: var(--radius);
  color: #b52f2b;
  font-size: 0.85rem;
  padding: 0.6rem 0.9rem;
  margin-top: 0.75rem;
}

.upload-success {
  background: #effaf3;
  border: 1px solid #22c55e;
  border-radius: var(--radius);
  color: #166534;
  font-size: 0.85rem;
  padding: 0.6rem 0.9rem;
  margin-top: 0.75rem;
}

/* ── Spinner ───────────────────────────────────────────────────────────────── */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255,255,255,.45);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Tab nav ───────────────────────────────────────────────────────────────── */
.tab-nav {
  display: flex;
  gap: 0;
  border-bottom: 2px solid rgba(var(--tint-light-rgb), .22);
  background: rgba(255, 255, 255, .90);
  padding: 0 2rem;
  position: sticky;
  top: 66px;
  z-index: 90;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 4px 14px rgba(var(--tint-rgb), .07);
}

.tab-btn {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 500;
  padding: 1rem 1.3rem;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
  letter-spacing: .15px;
}
.tab-btn:hover { color: var(--logo-blue); background: rgba(var(--tint-light-rgb),.06); }
.tab-btn.active {
  color: var(--logo-blue);
  border-bottom-color: var(--logo-cyan);
  font-weight: 700;
  background: linear-gradient(180deg, rgba(var(--tint-light-rgb),.13), rgba(var(--tint-light-rgb),0));
  text-shadow: 0 1px 10px rgba(var(--tint-light-rgb),.30);
}

/* ── Tab panels ────────────────────────────────────────────────────────────── */
.tab-panel { padding: 2rem; }

/* ── Loading spinner ────────────────────────────────────────────────────────── */
.loading-overlay {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 200px;
  color: var(--muted);
  font-size: 0.875rem;
}

.loading-spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--border);
  border-top-color: var(--logo-cyan);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex-shrink: 0;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ── Stat cards ────────────────────────────────────────────────────────────── */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}

/* A grouped card: white on the grey canvas, generous radius, hairline shadow.
   No border — the background change is the edge, which is how iOS separates
   a card from its page. */
.stat-card {
  background: var(--surface);
  border: none;
  border-radius: var(--radius-card);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-fast) var(--ease-spring);
  position: relative;
  overflow: hidden;
}

/* The value leads. Labels are secondary text above it, in the iOS footnote
   size at normal case — the previous uppercase + letter-spacing treatment
   made the label compete with the number it describes. */
.stat-card .stat-label {
  order: -1;
  font-size: var(--text-footnote);
  font-weight: 400;
  text-transform: none;
  letter-spacing: var(--track-body);
  color: var(--muted);
}

/* Rounded numerals and tabular figures: iOS uses SF Rounded for prominent
   metrics, and tabular spacing stops digits jittering as values update. */
.stat-card .stat-value {
  font-family: var(--font-rounded);
  font-size: clamp(26px, 2vw + 14px, 34px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: var(--track-large);
  font-variant-numeric: tabular-nums;
  overflow-wrap: break-word;
}

.stat-card .stat-delta {
  font-size: var(--text-footnote);
  color: var(--muted);
}

/* One accent affordance, not four. Each variant used to set a left border AND
   a top strip AND a bespoke tinted shadow AND a gradient background, which put
   accented and plain cards at visibly different elevations. Now the value
   takes the status colour and a hairline rule sits under it — the card itself
   stays a card. */
.stat-card.accent-red    .stat-value { color: var(--red-text); }
.stat-card.accent-amber  .stat-value,
.stat-card.accent-orange .stat-value { color: var(--orange-text); }
.stat-card.accent-green  .stat-value { color: var(--green-text); }
.stat-card.accent-blue   .stat-value { color: var(--tint); }

/* No accent bar. The coloured value already says "this one needs attention",
   and a 3px stripe pinned to the card's bottom edge said it a second time
   while reading as a crude underline — especially in dark, where a saturated
   bar on a dark card is the brightest thing on the page for no good reason.
   The --accent property is kept because other components key off it. */
.stat-card.accent-red    { --accent: var(--red); }
.stat-card.accent-amber,
.stat-card.accent-orange { --accent: var(--orange); }
.stat-card.accent-green  { --accent: var(--green); }
.stat-card.accent-blue   { --accent: var(--tint); }
.stat-card.accent-purple { --accent: var(--indigo); }

/* ── Segmented control ──────────────────────────────────────────────────────
   iOS's UISegmentedControl: one recessed grey track holding the whole set,
   with the selected segment raised as a white pill. Selection reads as a
   physical position rather than as a colour change, which is why it survives
   being hovered — the old chips shared one rule for :hover and .active, so
   while the pointer was over any chip you could not tell which filter applied.

   The track is the container, so .filter-chips stops being a plain flex gap
   and becomes the control itself. */
.filter-chips {
  display: inline-flex;
  gap: 2px;
  flex-wrap: wrap;
  padding: 2px;
  margin-bottom: var(--space-4);
  background: var(--fill-3);
  border-radius: var(--radius-control);
  max-width: 100%;
}

.chip {
  flex: 0 1 auto;
  min-height: 30px;
  padding: 5px 14px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-footnote);
  font-weight: 510;
  line-height: 1.35;
  letter-spacing: var(--track-body);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background var(--dur-fast) var(--ease),
              color      var(--dur-fast) var(--ease),
              transform  var(--dur-fast) var(--ease-spring);
}

.chip:hover  { background: var(--raise); }
.chip:active { transform: scale(0.96); }

/* The raised pill. A real background here rather than a ring, because the
   segmented control's whole idea is that the selection sits on top of the
   track. Status-specific chips on the vulns tab still override the fill with
   !important, and that continues to work — they simply supply their own pill
   colour instead of white. */
.chip.active {
  background: var(--surface);
  color: var(--text);
  font-weight: 590;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10), 0 0 0 0.5px rgba(0, 0, 0, .04);
}

/* ── Status pills ──────────────────────────────────────────────────────────── */
.status-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.7rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
  cursor: pointer;
  user-select: none;
  transition: all .2s ease;
}
.status-pill:hover { opacity: .8; }
.status-pill.open { background: var(--surface2); color: var(--muted); border: 1px solid var(--border); }
.status-pill.wip  { background: var(--amber-dim); color: var(--amber); border: 1px solid var(--amber); }
.status-pill.done { background: var(--green-dim); color: var(--green); border: 1px solid var(--green); }







/* ── Tables as inset grouped lists ──────────────────────────────────────────
   The iOS list is the pattern this app's tables should borrow: a white card on
   the grey canvas, rows separated by hairlines that are INSET from the leading
   edge so they align with the content rather than cutting the card in two, and
   a header that is quiet secondary text rather than a filled bar.

   What goes away: the blue gradient header, the 2px cyan underline, the
   full-bleed borders on every cell, and the zebra striping — iOS does not
   stripe, because inset separators already do the row-tracking job. */
.table-wrapper {
  overflow-x: auto;
  border-radius: var(--radius-card);
  border: none;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-subhead);
  letter-spacing: var(--track-body);
}

.data-table th {
  background: var(--surface);
  padding: var(--space-3) var(--space-4);
  text-align: left;
  font-size: var(--text-footnote);
  font-weight: 400;
  text-transform: none;
  letter-spacing: var(--track-body);
  color: var(--muted);
  border-bottom: 1px solid var(--separator);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 2;
}

.data-table th[data-sort],
.data-table th.sortable-col { cursor: pointer; user-select: none; }
.data-table th[data-sort]:hover,
.data-table th.sortable-col:hover { color: var(--tint); }
.data-table th[data-sort]::after {
  content: ' ⌃⌄';
  opacity: .35;
  font-size: 9px;
  letter-spacing: -2px;
}

.sort-arrow { font-style: normal; margin-left: 3px; font-size: var(--text-caption); }

.data-table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: none;
  vertical-align: middle;
}

/* The inset separator. Drawn on the row rather than per-cell so it can start
   after the leading padding — the detail that most distinguishes an iOS list
   from a web table. */
.data-table tbody tr {
  transition: background-color var(--dur-fast) var(--ease);
}
.data-table tbody tr:not(:last-child) > td { position: relative; }
.data-table tbody tr:not(:last-child) > td::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--separator);
}
.data-table tbody tr:not(:last-child) > td:first-child::after { left: var(--space-4); }
.data-table tbody tr:not(:last-child) > td:last-child::after  { right: var(--space-4); }

.data-table tbody tr:nth-child(odd) { background: transparent; }
.data-table tbody tr:hover { background: var(--fill-4); }

/* ── Coverage bar ──────────────────────────────────────────────────────────── */
.coverage-cell { display: flex; align-items: center; gap: 0.6rem; }

.coverage-bar-bg {
  flex: 1;
  min-width: 60px;
  height: 6px;
  background: #deedf5;
  border-radius: 999px;
  overflow: hidden;
}

.coverage-bar-fill {
  height: 100%;
  border-radius: 999px;
  transition: width .2s ease;
}

.coverage-bar-fill.cov-good   { background: var(--green); }
.coverage-bar-fill.cov-warn   { background: var(--amber); }
.coverage-bar-fill.cov-bad    { background: var(--red); }

.cov-good { color: var(--green); }
.cov-warn { color: var(--amber); }
.cov-bad  { color: var(--red);   }

/* ── IR Plan badge ─────────────────────────────────────────────────────────── */
.ir-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.18rem 0.55rem;
  border-radius: var(--radius);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .4px;
}
.ir-badge.yes { background: var(--green-dim); color: var(--green); }
.ir-badge.no  { background: var(--red-dim);   color: var(--red);   }




/* ── Escalation cell ───────────────────────────────────────────────────────── */
.esc-bad  { color: var(--red); font-weight: 600; }
.esc-warn { color: var(--amber); }

/* ── Charts ────────────────────────────────────────────────────────────────── */
.charts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 1.25rem;
  margin-bottom: 2rem;
}

.chart-card {
  background: var(--surface);
  border: 1px solid var(--card-edge, var(--separator));
  border-radius: var(--radius-card);
  padding: 1.3rem 1.4rem;
  transition: border-color .2s, transform .2s, box-shadow .2s;
  box-shadow: var(--shadow-sm);
}

.chart-card:hover {
  border-color: var(--tint);
  transform: translateY(-3px);
  box-shadow: 0 16px 34px rgba(var(--tint-rgb), .15);
}

.chart-title {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: #4d738b;
  margin-bottom: 0.75rem;
}

.chart-wrap {
  position: relative;
  height: 240px;
}

.chart-wrap canvas {
  width: 100% !important;
  display: block;
}

.chart-tooltip {
  position: absolute;
  background: rgba(255, 255, 255, .95);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.45rem 0.7rem;
  font-size: 0.78rem;
  pointer-events: none;
  white-space: nowrap;
  box-shadow: 0 12px 20px rgba(0, 119, 182, .18);
  z-index: 10;
  transform: translate(-50%, -110%);
}

/* ── Delta arrows ──────────────────────────────────────────────────────────── */
.delta-up-bad   { color: var(--red); }
.delta-down-bad { color: var(--green); }
.delta-up-good  { color: var(--green); }
.delta-down-good{ color: var(--red); }
.delta-neutral  { color: var(--muted); }

/* ── Section headings ──────────────────────────────────────────────────────── */
.section-heading {
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--text);
  margin: 2rem 0 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--separator);
  position: relative;
}
.section-heading::before {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 40px;
  height: 2px;
  background: linear-gradient(90deg, var(--logo-cyan), var(--logo-blue));
  border-radius: 1px;
}

/* Operations section containers */
/* Was a 50%-white gradient, which stays bright whatever sits behind it — the
   one panel that still glared in dark. A plain surface reads the same in both
   themes and lets the card edge do the separating. */
.operations-section {
  background: var(--surface);
  border: 1px solid var(--card-edge, var(--separator));
  border-radius: var(--radius-card);
  padding: var(--space-5);
  margin-bottom: var(--space-8);
  box-shadow: var(--shadow-sm);
}
.operations-section:first-of-type {
  margin-top: 0;
}

/* Simple entrance motion to give the UI energy without visual noise */
.upload-card,
.chart-card,
.stat-card {
  animation: fade-in-up .35s ease both;
}

/* The panel fades only — it must NOT be given a transform.
   `position: fixed` resolves against the nearest ancestor carrying a
   transform, filter, backdrop-filter or perspective, not against the viewport.
   .tab-panel used to share the fade-in-up animation above, and because that
   keyframe sets `transform` with fill-mode `both`, translateY(0) stayed
   applied for the life of the page — making every panel a containing block.
   Any .modal-overlay nested inside one was then positioned against the panel
   instead of the screen: measured at top:2312px, 3032px and 3448px rather
   than 0. Only 3 of 15 tabs reparent their modal to <body>, so the rest were
   affected. It stayed hidden while modals were centred boxes and became
   obvious the moment they became bottom-anchored sheets.
   Dropping the transform also stops a tab switch animating twice, once for
   the panel and again for every card inside it. */
.tab-panel {
  animation: fade-in .35s ease both;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── Vulnerability Management ───────────────────────────────────────────────── */

/* Risk severity badges */
.risk-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 9999px;
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .3px;
}
.risk-critical { background: #fee2e2; color: #991b1b; }
.risk-high      { background: #fef3c7; color: #92400e; }
.risk-medium    { background: #fff7ed; color: #c2410c; }
.risk-low       { background: #ecfdf5; color: #065f46; }
.risk-info      { background: #f1f5f9; color: #64748b; }

/* Additional status pill variants for vuln remediation */
.status-pill.accepted    { background: var(--green-dim);  color: var(--green-text);  border: 1px solid var(--green); }
.status-pill.in-progress { background: var(--blue-dim);   color: var(--blue-text);   border: 1px solid var(--blue); }
.status-pill.fixed       { background: var(--green-dim);  color: var(--green-text);  border: 1px solid var(--green); }
/* Neutral, for the same reason as the chip above. */
.status-pill.false-positive { background: var(--surface2); color: var(--muted); border: 1px solid var(--border); }

/* Remediation Tracker — source badges */
.badge-source-vuln     { background: var(--orange-dim); color: var(--orange-text); }
.badge-source-pentest  { background: var(--indigo-dim, rgba(125,122,255,.14)); color: var(--indigo); }
.badge-source-risk     { background: var(--blue-dim);   color: var(--blue-text); }
.badge-source-incident { background: var(--red-dim);    color: var(--red-text); }

/* Remediation Tracker view switch — the same segmented control as .filter-chips,
   since it is the same idea. Kept as its own selectors because the markup
   differs, but the metrics and the raised-pill treatment match exactly. */
.view-toggle {
  display: inline-flex;
  gap: 2px;
  background: var(--fill-3);
  border-radius: var(--radius-control);
  padding: 2px;
}
.view-toggle-btn {
  border: none;
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-footnote);
  font-weight: 510;
  letter-spacing: var(--track-body);
  min-height: 30px;
  padding: 5px 14px;
  border-radius: 8px;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.view-toggle-btn:hover { background: var(--raise); }
.view-toggle-btn.active {
  background: var(--surface);
  color: var(--text);
  font-weight: 590;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10), 0 0 0 0.5px rgba(0, 0, 0, .04);
}

/* Remediation Tracker — Gantt chart (weekly) */
.rt-gantt-wrap { overflow-x: auto; }
.rt-gantt { --rt-gantt-track-width: 600px; min-width: 820px; }
.rt-gantt-header {
  display: grid; grid-template-columns: 220px var(--rt-gantt-track-width); border-bottom: 1px solid var(--border);
  padding-bottom: 6px; margin-bottom: 6px;
}
.rt-gantt-header-weeks { position: relative; height: 20px; }
.rt-gantt-week-tick {
  position: absolute; top: 0; font-size: 0.7rem; color: var(--muted);
  border-left: 1px solid var(--border); padding-left: 4px; white-space: nowrap;
}
.rt-gantt-row { display: grid; grid-template-columns: 220px var(--rt-gantt-track-width); align-items: center; min-height: 34px; }
.rt-gantt-row:nth-child(odd) { background: var(--surface2); }
.rt-gantt-row-label {
  font-size: 0.82rem; padding: 0 0.75rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rt-gantt-track { position: relative; height: 26px; }
.rt-gantt-week-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--border); }
.rt-gantt-today-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--red, #dc2626); z-index: 1; }
.rt-gantt-bar {
  position: absolute; top: 4px; height: 18px; border-radius: 4px; cursor: default; z-index: 2;
  display: flex; align-items: center; padding: 0 6px; font-size: 0.68rem; color: #fff;
  white-space: nowrap; overflow: hidden; min-width: 6px;
}
.rt-gantt-bar.source-vuln     { background: #d97706; }
.rt-gantt-bar.source-pentest  { background: #7c3aed; }
.rt-gantt-bar.source-risk     { background: #2563eb; }
.rt-gantt-bar.source-incident { background: #dc2626; }
.rt-gantt-bar.overdue        { outline: 2px solid #dc2626; outline-offset: 1px; }
.rt-gantt-empty { padding: 1.5rem; text-align: center; color: var(--muted); }

/* Client risk acceptances awaiting review, above the tracker. */
.rt-accept-panel {
  border: 1px solid var(--border);
  border-left: 3px solid var(--amber, #d98e04);
  border-radius: var(--radius);
  background: var(--card-bg);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
}
.rt-accept-title { margin: 0; font-size: 1rem; font-weight: 600; display: flex; gap: 0.5rem; align-items: center; }
.rt-accept-lead { margin: 0.35rem 0 0.75rem; font-size: 0.85rem; color: var(--muted); }
.rt-accept-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, 300px);
  gap: 1rem 1.5rem;
  padding: 0.9rem 0;
  border-top: 1px solid var(--border);
}
.rt-accept-finding { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.rt-accept-sev { text-transform: capitalize; font-size: 0.8rem; color: var(--muted); }
.rt-accept-host { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.8rem; color: var(--muted); }
.rt-accept-meta { margin-top: 0.35rem; font-size: 0.85rem; color: var(--muted); }
.rt-accept-why {
  margin: 0.5rem 0 0;
  padding: 0.4rem 0.75rem;
  border-left: 2px solid var(--border);
  font-size: 0.875rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rt-accept-actions { display: flex; flex-direction: column; gap: 0.35rem; }
.rt-accept-note { resize: vertical; }
.rt-accept-buttons { display: flex; gap: 0.5rem; }
.rt-accept-error { color: var(--red); font-size: 0.8rem; }
@media (max-width: 760px) {
  .rt-accept-item { grid-template-columns: 1fr; }
}

/* Scrollable findings table.
   Sized to the viewport rather than a fixed 500px: this list runs to hundreds
   of rows, and 500px showed seven of them on a 1080p screen. The floor keeps it
   usable on a short window. */
.vuln-findings-wrap {
  max-height: max(60vh, 420px);
  overflow-y: auto;
}

/* Vuln name cell — truncate long names */
.vuln-name-cell {
  max-width: 280px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: default;
}

/* Solution cell — truncate in top-vulns table */
.solution-cell {
  max-width: 260px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: default;
  font-size: .82rem;
  color: var(--muted);
}

/* Empty state card on vulns tab */
/* ── Empty states ──────────────────────────────────────────────────────────── */
/* .empty-state is defined once, further down (centred italic note). It used to
   be declared here as well, as a 200px flex centring box — the later rule won,
   so this one had no effect and only made the component impossible to reason
   about from its first definition. Removed.
   .empty-state-container consequently has to centre itself, since it can no
   longer rely on a flex parent. */

.empty-state-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2rem;
  text-align: center;
  max-width: 420px;
  /* Centres itself rather than depending on a flex parent that no longer
     exists. Without this it renders left-aligned at 420px, with its text
     centred inside — which reads as a layout bug. */
  margin-inline: auto;
  padding: var(--space-8, 2rem) var(--space-4, 1rem);
}

.empty-state-icon {
  font-size: 3.5rem;
  line-height: 1;
  display: inline-block;
  opacity: 0.75;
  animation: float 3s ease-in-out infinite;
}

@keyframes float {
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-8px); }
}

.empty-state-title {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: .3px;
  margin: 0;
}

.empty-state-description {
  font-size: 0.9rem;
  color: var(--muted);
  line-height: 1.5;
  margin: 0;
}

.empty-state-cta {
  margin-top: 0.5rem;
}

.vuln-empty-card {
  text-align: center;
  padding: 3rem 2rem;
  background: radial-gradient(600px 220px at 50% 0%, var(--tint-wash), transparent), var(--surface2);
  border: 1px solid var(--separator-solid);
  border-radius: var(--radius);
  color: var(--muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}
.vuln-empty-card::before {
  content: '📊';
  font-size: 2.8rem;
  display: block;
  opacity: 0.7;
  animation: float 3s ease-in-out infinite;
}
.vuln-empty-card p {
  margin: 0;
  font-size: 0.95rem;
}
.vuln-empty-card p:first-of-type {
  font-weight: 600;
  color: var(--text);
}
.vuln-empty-card p:last-of-type {
  font-size: 0.85rem;
  color: var(--muted);
}

/* Vuln scan month selector bar */
.vuln-tenant-filter {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}
.vuln-scan-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.scan-select-label {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.scan-select {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 0.9rem;
  min-width: 160px;
  cursor: pointer;
}
.btn-delete-scan {
  padding: 0.35rem 0.75rem;
  border: 1px solid #fca5a5;
  border-radius: 6px;
  background: transparent;
  color: #dc2626;
  font-size: 0.82rem;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.btn-delete-scan:hover { background: #fef2f2; }
.btn-delete-scan:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Sparkline cells ───────────────────────────────────────────────────────── */
.sparkline-cell canvas { display: block; }

/* ── Status summary bar ────────────────────────────────────────────────────── */
.vuln-status-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.2rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 1rem;
  margin-bottom: 0.75rem;
  font-size: 0.88rem;
}
.status-count {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 500;
  white-space: nowrap;
}
.status-count strong { font-size: 1rem; }
.sc-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.status-count-open       .sc-dot { background: #4f748a; }
.status-count-inprogress .sc-dot { background: #f59e0b; }
.status-count-fixed      .sc-dot { background: #22c55e; }
.status-count-accepted   .sc-dot { background: #059669; }
.status-count-total { color: var(--muted); border-left: 1px solid var(--border); padding-left: 1rem; }
.status-count[data-status]:hover { opacity: 0.75; text-decoration: underline; }
.status-remediated-pct {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.82rem;
  color: var(--muted);
  white-space: nowrap;
}
.remediation-bar {
  width: 80px; height: 6px;
  background: var(--surface2);
  border-radius: 3px;
  overflow: hidden;
}
.remediation-fill {
  height: 100%;
  background: #22c55e;
  border-radius: 3px;
  transition: width 0.4s ease;
}

/* ── GRC & Insurability Tab ────────────────────────────────────────────────── */

.grc-wrapper { display: flex; flex-direction: column; gap: 1.5rem; padding: 1rem; }

#tab-grc .section-header .rpt-actions { margin-top: 0.9rem; }

/* Score panel */
.grc-score-panel {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem 2rem;
  box-shadow: var(--shadow-sm);
  flex-wrap: wrap;
}
.grc-score-left  { display: flex; align-items: center; gap: 1.5rem; flex-shrink: 0; }
.grc-gauge-container { flex-shrink: 0; }
.grc-score-title { font-size: 1.1rem; font-weight: 700; color: var(--text); }
.grc-score-date  { font-size: 0.85rem; color: var(--muted); margin-top: 0.25rem; }
.grc-progress-info { font-size: 0.8rem; color: var(--muted); margin-top: 0.5rem; }

/* Domain breakdown grid */
.grc-breakdown { flex: 1; min-width: 260px; }
.grc-domain-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; }
.grc-domain-card { background: var(--surface2); border-radius: var(--radius-sm); padding: 0.75rem; }
.grc-domain-name { font-size: 0.82rem; font-weight: 600; color: var(--text); margin-bottom: 0.4rem; }
.grc-domain-bar-wrap { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }
.grc-domain-bar { flex: 1; height: 7px; background: var(--border); border-radius: 4px; overflow: hidden; }
.grc-domain-bar-fill { height: 100%; border-radius: 4px; transition: width 0.7s cubic-bezier(0.4,0,0.2,1); }
.grc-domain-score { font-size: 0.8rem; font-weight: 700; min-width: 40px; text-align: right; }
.grc-domain-progress { font-size: 0.75rem; color: var(--muted); }

/* Framework alignment panel */
.grc-framework-panel {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem 2rem;
  box-shadow: var(--shadow-sm);
}
.grc-framework-title { font-size: 1rem; font-weight: 700; color: var(--text); margin-bottom: 0.75rem; }

/* GRC indicator card (on Secure Score tab) */
.grc-indicator-card {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem 1.25rem;
  min-width: 160px;
  text-align: center;
}
.grc-indicator-score { font-size: 2rem; font-weight: 700; line-height: 1; }
.grc-indicator-score span { font-size: 1rem; font-weight: 400; color: var(--muted); }
.grc-indicator-label { font-size: 0.85rem; font-weight: 600; color: var(--text); margin: 0.35rem 0 0.2rem; }
.grc-indicator-sub   { font-size: 0.78rem; color: var(--muted); }
.grc-indicator-link  { display: block; margin-top: 0.5rem; font-size: 0.8rem; color: var(--logo-cyan); text-decoration: none; }
.grc-indicator-link:hover { text-decoration: underline; }
.grc-indicator-empty .grc-indicator-label { color: var(--muted); }

/* Questionnaire */
.grc-questionnaire {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.grc-questionnaire-header { padding: 1.25rem 1.5rem 1rem; border-bottom: 1px solid var(--border); }
.grc-questionnaire-header h3 { margin: 0 0 0.4rem; font-size: 1rem; font-weight: 700; color: var(--text); }
.grc-questionnaire-subtitle { margin: 0; font-size: 0.85rem; color: var(--muted); line-height: 1.5; }

/* Section accordion */
.grc-section { border-bottom: 1px solid var(--border); }
.grc-section:last-child { border-bottom: none; }
.grc-section-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1.5rem;
  background: var(--surface2);
  border: none;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  font-weight: 600;
  font-size: 0.9rem;
  transition: background 0.15s;
}
.grc-section-toggle:hover { background: var(--border); }
.grc-section-title  { flex: 1; }
.grc-section-count  { font-size: 0.78rem; color: var(--muted); font-weight: 400; }
.grc-toggle-arrow   { font-size: 0.7rem; color: var(--muted); transition: transform 0.2s; }
.grc-section-body   { padding: 0.5rem 0; }

/* Individual question */
.grc-question {
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
  transition: background 0.15s;
}
.grc-question:last-child { border-bottom: none; }
.grc-question:hover { background: var(--surface2); }
.grc-unanswered { background: rgba(245,158,11,0.04); border-left: 3px solid var(--amber); }

.grc-question-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
.grc-question-weight { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; }
.grc-question-ref    { font-size: 0.72rem; color: var(--muted); background: var(--surface2); padding: 0.1rem 0.4rem; border-radius: 3px; }
.grc-question-ref-cis { color: var(--logo-cyan); background: color-mix(in srgb, var(--logo-cyan) 12%, var(--surface2)); }
.grc-question-text   { margin: 0 0 0.35rem; font-size: 0.92rem; color: var(--text); line-height: 1.5; }
.grc-question-help   { margin: 0 0 0.6rem; font-size: 0.8rem; color: var(--muted); line-height: 1.5; font-style: italic; }

/* Answer radio row */
.grc-answer-row { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.grc-radio-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text);
  transition: border-color 0.15s, background 0.15s;
}
.grc-radio-label input { display: none; }
.grc-radio-label:hover { border-color: var(--logo-cyan); }
.grc-radio-label.selected { border-color: var(--logo-cyan); background: rgba(var(--tint-light-rgb), 0.1); color: var(--logo-cyan); font-weight: 600; }

/* Notes */
.grc-notes-wrap { margin-top: 0.35rem; }
.grc-notes-input {
  width: 100%;
  max-width: 500px;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface2);
  color: var(--text);
  font-size: 0.82rem;
}
.grc-notes-input:focus { outline: none; border-color: var(--logo-cyan); }
.grc-notes-readonly { margin: 0.25rem 0 0; font-size: 0.82rem; color: var(--muted); font-style: italic; }

/* Save bar */
.grc-save-bar {
  position: sticky;
  bottom: 0;
  background: var(--card-bg);
  border-top: 1px solid var(--border);
  padding: 0.85rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  box-shadow: 0 -2px 8px rgba(0,0,0,0.06);
  z-index: 10;
}
.grc-save-status    { font-size: 0.85rem; color: var(--muted); }
.grc-save-ok        { color: var(--green); }
.grc-save-err       { color: var(--red); }

@media (max-width: 768px) {
  .grc-score-panel { flex-direction: column; gap: 1rem; }
  .grc-score-left  { flex-direction: column; align-items: flex-start; }
  .grc-domain-grid { grid-template-columns: 1fr 1fr; }
  .grc-question    { padding: 0.75rem 1rem; }
  .grc-section-toggle { padding: 0.75rem 1rem; }
}

/* ── Vuln quick-search ─────────────────────────────────────────────────────── */
.vuln-search-bar { margin-bottom: 0.75rem; }
.vuln-search-bar input {
  width: 100%;
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface2);
  color: var(--text);
  font-size: 0.9rem;
}
.vuln-search-bar input:focus {
  outline: none;
  border-color: var(--logo-cyan);
  box-shadow: 0 0 0 2px rgba(var(--tint-light-rgb), 0.12);
}
.vuln-search-bar input::placeholder { color: var(--muted); }

/* ── Status filter chips ───────────────────────────────────────────────────── */
.filter-chips-status { margin-top: 0.35rem; }
.chip-st-open        { border-color: #c3e6f4 !important; color: var(--muted) !important; }
.chip-st-open.active { background: #edf8fd !important; }
.chip-st-inprogress,
.chip-st-in-progress        { border-color: #efcc89 !important; color: #9f6f17 !important; }
.chip-st-inprogress.active,
.chip-st-in-progress.active { background: #fff4de !important; }
.chip-st-fixed        { border-color: #9ed9aa !important; color: #2c8740 !important; }
.chip-st-fixed.active { background: #e7f8ea !important; }
.chip-st-accepted        { border-color: #6ee7b7 !important; color: #059669 !important; }
.chip-st-accepted.active { background: #ecfdf5 !important; }
/* A false positive is not a remediation and not an acceptance — it is a
   correction to the data. Deliberately neutral rather than green: nothing
   about the estate improved, so it must not read like a win. */
.chip-st-false-positive        { border-color: #b8bfc9 !important; color: #5a6472 !important; }
.chip-st-false-positive.active { background: #eef1f5 !important; }

/* ── Notes column ─────────────────────────────────────────────────────────── */
.notes-cell { cursor: pointer; max-width: 200px; font-size: 0.82rem; color: var(--muted); }
.notes-cell:hover .notes-add, .notes-cell:hover .notes-preview { opacity: 0.7; }
.notes-preview { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notes-add { color: var(--logo-blue); font-style: italic; }

/* When modal is open: collapse backdrop-filter GPU layers on sticky elements
   so they don't paint above the fixed overlay in Chromium ─────────────────── */
body.modal-open .site-header,
body.modal-open .tab-nav {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  z-index: 0 !important;
}

/* ── Finding management modal ──────────────────────────────────────────────── */
/* ── Sheets ─────────────────────────────────────────────────────────────────
   iOS presents modal content as a sheet that rises from the bottom edge with
   a grabber at the top, not as a box floating in the middle of a dimmed page.
   On a wide screen it settles as a tall card anchored to the bottom, which is
   how iPadOS handles the same component.

   The scrim is lighter than the old 0.6 black: iOS dims to roughly 0.4 and
   lets the page stay legible behind, because the sheet is understood as
   sitting *on* the page rather than replacing it. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .4);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 9999;
  padding: 0;
  animation: sheet-scrim var(--dur-sheet) var(--ease-spring) both;
}
/* display:flex overrides the HTML hidden attribute — restore hide behaviour */
.modal-overlay[hidden] { display: none !important; }

@keyframes sheet-scrim { from { opacity: 0; } to { opacity: 1; } }
@keyframes sheet-rise  { from { transform: translateY(100%); } to { transform: translateY(0); } }

.modal-box {
  background: var(--surface);
  border: none;
  /* Top corners only — the sheet is attached to the bottom edge. */
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  width: 100%;
  max-width: 560px;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  max-height: 92vh;
  overflow: hidden;
  animation: sheet-rise var(--dur-sheet) var(--ease-spring) both;
}

/* The grabber. Purely an affordance cue — it tells you the surface is
   dismissible, which is the job the old header border could not do. */
.modal-box::before {
  content: '';
  flex-shrink: 0;
  width: 36px;
  height: 5px;
  margin: 8px auto 0;
  border-radius: var(--radius-capsule);
  background: var(--fill);
}

.modal-box > form {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-height: 0;
}
.modal-risk-strip {
  height: 4px;
  border-radius: var(--radius-capsule);
  background: var(--fill);
  flex-shrink: 0;
}

/* Sheet navigation bar: title centred, actions at the edges. */
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--separator);
}
.modal-title {
  font-size: var(--text-headline);
  font-weight: 590;
  margin: 0;
  letter-spacing: var(--track-body);
  flex: 1 1 auto;
  text-align: center;
}
/* .modal-close was never defined anywhere, yet nine modals use it — every one
   of them rendered a raw native button in its header next to styled chrome.

   iOS's sheet close is a filled grey circle with a thin glyph, sized to the
   44px touch floor. Both class names resolve to the same control. */
.modal-close,
.modal-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  background: var(--fill-3);
  border: none;
  font-size: 17px;
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
  border-radius: 50%;
  transition: background var(--dur-fast) var(--ease),
              color      var(--dur-fast) var(--ease),
              transform  var(--dur-fast) var(--ease-spring);
}
.modal-close:hover,
.modal-close-btn:hover { background: var(--fill-2); color: var(--text); }
.modal-close:active,
.modal-close-btn:active { transform: scale(0.92); }
/* Keeps the 44px target without a 44px visual. */
.modal-close::after,
.modal-close-btn::after {
  content: '';
  position: absolute;
  width: var(--target);
  height: var(--target);
}
.modal-header { position: relative; }
.modal-close, .modal-close-btn { position: relative; }

.modal-body { padding: 1.1rem 1.25rem 0.75rem; display: flex; flex-direction: column; gap: 1rem; overflow-y: auto; }

/* Finding info card */
.modal-finding-card {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.modal-finding-top { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.modal-cve {
  font-size: 0.8rem;
  color: var(--muted);
  font-family: monospace;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1rem 0.4rem;
}
.modal-vuln-name {
  font-weight: 600;
  font-size: 0.95rem;
  margin: 0;
  line-height: 1.4;
  color: var(--text);
}
.modal-meta {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.meta-icon { font-size: 0.9rem; }

.modal-divider { height: 1px; background: var(--border); margin: 0 -1.25rem; }

/* Form fields */
.modal-form-group { display: flex; flex-direction: column; gap: 0.35rem; }
.modal-label {
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.modal-status-select {
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
  width: 100%;
  transition: border-color 0.15s;
}
.modal-status-select:focus { outline: none; border-color: var(--logo-blue); box-shadow: 0 0 0 3px rgba(var(--tint-rgb),0.12); }
.modal-textarea {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 0.88rem;
  resize: vertical;
  box-sizing: border-box;
  font-family: inherit;
  line-height: 1.5;
  transition: border-color 0.15s;
  min-height: 90px;
}
.modal-textarea:focus { outline: none; border-color: var(--logo-blue); box-shadow: 0 0 0 3px rgba(var(--tint-rgb),0.12); }
.modal-char-count { font-size: 0.72rem; color: var(--muted); text-align: right; display: block; margin-top: 0.15rem; }
.modal-updated-at {
  font-size: 0.75rem;
  color: var(--muted);
  margin: -0.25rem 0 0;
  font-style: italic;
}

/* The sheet is anchored to the bottom edge, so the footer no longer needs a
   rounded corner or a tinted bar to sit on — it is the base of the surface.
   The safe-area inset keeps the buttons clear of a home indicator. */
.modal-footer {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: flex-end;
  padding: var(--space-3) var(--space-5);
  padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--separator);
  background: var(--surface);
  border-radius: 0;
}
/* .btn-secondary was defined a second time here, re-declaring padding, radius
   (7px, matching no token), font-size and weight. It won on source order and
   fought .btn — so Cancel sat at a different height and weight than the Save
   beside it in every modal footer, and `.btn-sm .btn-secondary` rendered at
   full size because this padding overrode .btn-sm's. Removed; the definition
   near .btn (~line 274) composes with .btn as intended. Every usage in the
   app pairs the two classes, so nothing loses its styling. */

/* Status pill as button */
.status-pill.status-edit-btn {
  cursor: pointer;
  border: none;
  font-size: 0.78rem;
  font-family: inherit;
  transition: opacity 0.15s, transform 0.1s;
}
.status-pill.status-edit-btn:hover { opacity: 0.8; transform: scale(1.04); }

/* ── Responsive ────────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
  .site-header { flex-direction: column; align-items: flex-start; }
  .header-right { width: 100%; }
  .upload-file-row { grid-template-columns: 1fr; }
  .charts-grid { grid-template-columns: 1fr; }
  .tab-btn { padding: 0.75rem 0.7rem; font-size: 0.82rem; }
  .tab-panel { padding: 1rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LOGIN PAGE
   ═══════════════════════════════════════════════════════════════════════════ */

body.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface2);
}

.login-card {
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 2.5rem 2rem;
  width: 100%;
  max-width: 400px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.10);
}

.login-logo-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 2rem;
}

.login-logo {
  height: 48px;
  object-fit: contain;
}

.login-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--logo-blue, #0066CC);
  letter-spacing: -0.01em;
}

.login-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 0.35rem;
}

.login-input {
  display: block;
  width: 100%;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.95rem;
  color: var(--text);
  background: var(--surface);
  outline: none;
  box-sizing: border-box;
  transition: border-color 0.15s;
}
.login-input:focus {
  border-color: var(--logo-blue, #0066CC);
  box-shadow: 0 0 0 2px rgba(var(--tint-rgb),0.12);
}

.login-error {
  color: #dc2626;
  font-size: 0.85rem;
  margin: 0.5rem 0;
}

.login-btn {
  width: 100%;
  margin-top: 1.25rem;
  padding: 0.65rem;
  font-size: 1rem;
}

/* ── MFA login steps ─────────────────────────────────────────────────────── */
.login-mfa-title {
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
  color: var(--text);
}

.login-mfa-hint {
  font-size: 0.85rem;
  color: var(--muted);
  margin-bottom: 0.75rem;
  line-height: 1.4;
}

.login-btn-back {
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.5rem;
  font-size: 0.9rem;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.login-btn-back:hover {
  background: var(--surface2);
}

.login-divider {
  display: flex;
  align-items: center;
  text-align: center;
  margin: 1rem 0 0.5rem;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.login-divider::before,
.login-divider::after {
  content: '';
  flex: 1;
  border-bottom: 1px solid var(--border);
}
.login-divider span {
  padding: 0 0.75rem;
}

.enroll-qr {
  display: block;
  margin: 0.75rem auto;
  max-width: 180px;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* ── Carousel ──────────────────────────────────────────────────────────────── */
.carousel-wrapper {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.carousel-container {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.carousel-slides {
  display: flex;
  width: 100%;
  height: auto;
  transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.carousel-slide {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  width: 100%;
  min-height: 200px;
  padding: 1rem;
}

.carousel-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.carousel-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: all 0.2s ease;
  flex-shrink: 0;
  font-weight: 300;
}

.carousel-btn:hover {
  background: var(--surface2);
  border-color: var(--logo-cyan);
  color: var(--logo-cyan);
  box-shadow: 0 2px 8px rgba(var(--tint-light-rgb), 0.2);
}

.carousel-btn:active {
  transform: scale(0.93);
}

.carousel-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
  pointer-events: none;
}

.carousel-dots {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.carousel-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--border);
  border: none;
  cursor: pointer;
  transition: all 0.25s ease;
  padding: 0;
  flex-shrink: 0;
}

.carousel-dot:hover {
  background: rgba(var(--tint-light-rgb), 0.5);
  transform: scale(1.2);
}

.carousel-dot.active {
  background: var(--logo-cyan);
  transform: scale(1.4);
}

.carousel-slide-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  min-width: 140px;
  text-align: center;
  letter-spacing: 0.01em;
}

.enroll-secret {
  display: block;
  font-family: monospace;
  font-size: 0.8rem;
  word-break: break-all;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  color: var(--text);
  margin-bottom: 0.5rem;
}

/* ── MFA banner (grace-period superadmin reminder) ──────────────────────── */
.mfa-banner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 1.25rem;
  background: var(--orange-dim);
  color: var(--orange-text);
  border-bottom: 1px solid rgba(var(--tint-light-rgb), .35);
  font-size: 0.85rem;
  font-weight: 500;
  position: sticky;
  top: 0;
  z-index: 200;
}

.mfa-banner-link {
  color: inherit;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
  white-space: nowrap;
}

/* ── MFA admin card status text ──────────────────────────────────────────── */
.mfa-status-text {
  font-size: 0.9rem;
  margin-bottom: 0.75rem;
  color: var(--text);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SECURITY AWARENESS TAB
   ═══════════════════════════════════════════════════════════════════════════ */

.awareness-meta-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
  min-height: 2rem;
}

.awareness-uploaded-at {
  font-size: 0.82rem;
  color: var(--muted);
}

.awareness-chart-card,
.awareness-table-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
}

.awareness-card-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 1rem;
  color: var(--text);
}

.awareness-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2rem;
  padding: 3rem 2rem;
  color: var(--muted);
  text-align: center;
  background: var(--fill-4);
  border: 1px solid var(--card-edge, var(--separator));
  border-radius: var(--radius);
  margin: 1.5rem 0;
}
.awareness-empty::before {
  content: '🎓';
  font-size: 2.8rem;
  display: block;
  opacity: 0.7;
  animation: float 3s ease-in-out infinite;
}
.awareness-empty p {
  margin: 0;
  font-size: 0.95rem;
}
.awareness-empty p:first-of-type {
  font-weight: 600;
  color: var(--text);
}

/* Manual awareness figures — for clients running their own programme. */
.awareness-manual-card {
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
  background: var(--fill-4);
  border: 1px solid var(--card-edge, var(--separator));
  border-radius: var(--radius);
}
.awareness-manual-hint {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
  max-width: 70ch;
}
.awareness-manual-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1rem;
  align-items: end;
}
#awarenessManualContent .awareness-manual-hint { margin-top: 1rem; }

.table-scroll {
  max-height: 320px;
  overflow-y: auto;
  overflow-x: auto;
}

/* purple stat card accent (for Awareness) */
.stat-card.accent-purple {
  border-top-color: #7c3aed;
}

/* ── Awareness: missing training expand/collapse ─────────────────────────── */
.awareness-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.awareness-card-header .awareness-card-title { margin: 0; }

.table-search-input {
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 0.85rem;
  min-width: 200px;
  transition: border-color 0.15s;
}
.table-search-input:focus {
  outline: none;
  border-color: var(--logo-blue, #0066cc);
  box-shadow: 0 0 0 3px rgba(var(--tint-rgb),0.1);
}
.btn-link {
  background: none;
  border: none;
  color: var(--logo-blue, #0066cc);
  cursor: pointer;
  font-size: 0.82rem;
  padding: 0;
  font-family: inherit;
}
.btn-link:hover { text-decoration: underline; }

.awareness-missing-detail td { border-top: none; }

.awareness-missing-list {
  margin: 0;
  padding: 0 0 0 1rem;
  list-style: disc;
  font-size: 0.82rem;
  line-height: 1.9;
}

/* Capsule, not a square tag. iOS status pills are rounded, sit on a tinted
   wash of their own hue, and use medium weight rather than bold — the colour
   is the signal, so the weight does not also have to shout. */
.badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--text-caption-2);
  font-weight: 590;
  padding: 3px 8px;
  border-radius: var(--radius-capsule);
  vertical-align: middle;
  letter-spacing: 0;
  line-height: 1.35;
  white-space: nowrap;
}
/* .badge-blue / .badge-amber were declared here and again further down; the
   later pair won, so these never applied. Single definition now lives with the
   rest of the badge variants. */

/* ── Hamburger button ──────────────────────────────────────────────────────── */
.hamburger-btn {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  flex-shrink: 0;
  transition: background .18s, border-color .18s, box-shadow .18s;
}
.hamburger-btn:hover {
  background: var(--bg-soft);
  border-color: var(--logo-cyan);
  box-shadow: 0 0 0 3px rgba(var(--tint-light-rgb),.14);
}
.hamburger-btn:focus-visible {
  outline: 2px solid var(--logo-cyan);
  outline-offset: 2px;
}
.hamburger-btn span {
  display: block;
  width: 18px;
  height: 2px;
  background: var(--text);
  border-radius: 2px;
  transition: background .18s;
}
.hamburger-btn:hover span { background: var(--logo-blue); }

/* ── Side overlay ──────────────────────────────────────────────────────────── */
.side-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 20, 50, .40);
  z-index: 199;
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s ease;
}
.side-overlay.open {
  opacity: 1;
  pointer-events: auto;
}

/* ── Side menu (permanent sidebar) ────────────────────────────────────────── */
.side-menu {
  position: fixed;
  top: var(--header-h);
  left: 0;
  width: var(--side-w);
  height: calc(100vh - var(--header-h));
  /* A sidebar is chrome, so it takes the same translucent material as the nav
     bar rather than its own blue gradient. The 2px cyan edge and the 20px blue
     glow both go — a hairline is the whole separation iOS uses. */
  background: var(--material-thick);
  backdrop-filter: var(--material-blur);
  -webkit-backdrop-filter: var(--material-blur);
  border-right: 1px solid var(--separator);
  box-shadow: none;
  z-index: 90;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* overflow-y:auto implies overflow-x:auto — without this, a 280px label
     inside a 64px rail throws a horizontal scrollbar mid-transition. */
  overflow-x: hidden;
  overscroll-behavior: contain;
  transition: width var(--nav-anim) ease, transform var(--nav-anim) ease;
}

.side-menu-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.2rem;
  /* Was a hardcoded near-white with a cyan rule, which stayed a bright island
     at the top of the sidebar once the rest of the chrome went dark. */
  border-bottom: 1px solid var(--separator);
  background: transparent;
  flex-shrink: 0;
}

.side-menu-brand {
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.1;
}
.side-menu-brand .brand-secops {
  font-size: 1.1rem;
  font-weight: 800;
  color: var(--logo-blue);
  letter-spacing: 2px;
  text-transform: uppercase;
  font-family: var(--font-display);
}
.side-menu-sub {
  font-size: 0.7rem;
  color: var(--muted);
  font-weight: 500;
  letter-spacing: .3px;
  text-transform: uppercase;
}

.side-menu-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
  flex-shrink: 0;
}
.side-menu-close:hover {
  background: var(--red-dim);
  border-color: var(--red);
  color: var(--red);
}
.side-menu-close:focus-visible {
  outline: 2px solid var(--logo-cyan);
  outline-offset: 2px;
}

/* ── Side nav items ────────────────────────────────────────────────────────── */
.side-nav {
  display: flex;
  flex-direction: column;
  padding: 1rem 0.6rem;
  gap: 0.4rem;
  flex: 1;
}

.side-nav-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.85rem 1rem;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: all .2s ease;
  letter-spacing: .1px;
  position: relative;
}
/* The iOS sidebar selection is a filled rounded pill, not a left border plus a
   gradient plus two inset glows. Hover no longer shifts padding-left either —
   text that moves under the cursor is a fidget, and it made the whole list
   jitter as the pointer travelled down it. */
.side-nav-item:hover {
  background: var(--fill-4);
  color: var(--text);
}
.side-nav-item.active {
  background: var(--tint-fill);
  color: var(--on-tint);
  font-weight: 590;
  border-left: none;
  box-shadow: none;
}
.side-nav-item.active .side-nav-icon { stroke: var(--on-tint); }
.side-nav-item:focus-visible {
  outline: 2px solid var(--logo-cyan);
  outline-offset: 1px;
}

.side-nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke: currentColor;
}
/* .badge-red was declared here too; superseded further down. Removed. */
/* ═══════════════════════════════════════════════════════════════════════════
   HEADER USER INFO (logout button, username, role badge)
   ═══════════════════════════════════════════════════════════════════════════ */

.header-user-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.header-username {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header-role-badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.role-superadmin { background: #ede9fe; color: #6d28d9; }
.role-admin      { background: #dbeafe; color: #1d4ed8; }
.role-manager    { background: #dcfce7; color: #15803d; }
.role-sales      { background: #fef3c7; color: #92400e; }
.role-readonly   { background: #f3f4f6; color: #6b7280; }

/* ═══════════════════════════════════════════════════════════════════════════
   ADMIN TAB — User management
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Admin sub-tabs ────────────────────────────────────────────────────────
   Integrations / User Management / Client Profile. Matches the Firewall tab's
   sub-tab bar so the two read as the same control rather than two inventions. */

/* Shared by the Admin and Pricing sub-tab bars, so the two read as one pattern. */
.admin-subtabs,
.pricing-subtabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1.5rem;
  border-bottom: 1px solid var(--border);
}

.admin-subtab,
.pricing-subtab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 0.55rem 0.9rem; font: inherit; font-size: 0.88rem;
  color: var(--text-muted, #8C8C8C); cursor: pointer;
}
.admin-subtab:hover,
.pricing-subtab:hover { color: var(--text, inherit); }
.admin-subtab.is-on,
.pricing-subtab.is-on {
  color: var(--text, inherit);
  border-bottom-color: var(--accent, var(--tint));
  font-weight: 600;
}
/* Keyboard users need to see where they are; the browser default outline is
   clipped by the bar's overflow on narrow viewports. */
.admin-subtab:focus-visible,
.pricing-subtab:focus-visible {
  outline: 2px solid var(--accent, var(--tint));
  outline-offset: -2px;
  border-radius: 4px;
}

/* ── Pricing page ──────────────────────────────────────────────────────────── */
.pricing-pane-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.pricing-pane-actions > .btn { margin-left: auto; }

/* vISO: the tier is the price, so the tiers are the first thing on the pane. */
.visop-tiers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.visop-tier {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
}
.visop-tier:has(input:checked) {
  border-color: var(--accent, var(--tint));
  box-shadow: 0 0 0 2px rgba(var(--tint-rgb), .18);
}
.visop-tier input { align-self: flex-start; margin: 0 0 0.2rem; }
.visop-tier-name { font-weight: 600; }
.visop-tier-fits { font-size: 0.8rem; color: var(--muted); }
.visop-tier-price { font-size: 1.05rem; font-weight: 700; margin-top: 0.2rem; }

.visop-includes { margin-bottom: 1.25rem; font-size: 0.85rem; }
.visop-includes ul { margin: 0.25rem 0 0; padding-left: 1.1rem; }

.visop-services {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem 0.4rem;
  margin: 0 0 1rem;
}
.visop-service {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
  padding: 0.15rem 0;
}
.visop-included { font-size: 0.72rem; color: var(--muted); font-style: italic; }
.visop-sub { font-size: 0.75rem; color: var(--muted); margin-top: 0.3rem; line-height: 1.4; }
.visop-h { font-size: 1rem; font-weight: 600; margin: 0.5rem 0 0.6rem; }

/* A quote under the margin floor is marked on the card that measures it, and
   explained in a banner that names what would fix it. */
.stat-card.visop-warn { border-color: #dc2626; box-shadow: inset 3px 0 0 #dc2626; }
.visop-alert {
  grid-column: 1 / -1;
  padding: 0.6rem 0.8rem;
  border: 1px solid rgba(220,38,38,.35);
  background: rgba(220,38,38,.07);
  color: #b91c1c;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
}

.admin-card {
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
}

.admin-card-title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 0.35rem 0;
}

.admin-card-subtitle {
  font-size: 0.83rem;
  color: var(--muted);
  margin: 0 0 1.25rem;
}

/* ── Integration cards ─────────────────────────────────────────────────────── */
.integration-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 1rem;
  overflow: hidden;
}

.integration-card-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1.1rem;
  background: var(--surface2);
  border-bottom: 1px solid var(--border);
}

.integration-icon { font-size: 1.4rem; flex-shrink: 0; }

.integration-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.integration-info strong { font-size: 0.9rem; color: var(--text); }
.integration-desc         { font-size: 0.78rem; color: var(--muted); }

/* Toggle switch */
.integration-toggle { position: relative; display: inline-block; width: 40px; height: 22px; flex-shrink: 0; }
.integration-toggle input { opacity: 0; width: 0; height: 0; }
.int-toggle-slider {
  position: absolute; inset: 0;
  background: var(--border);
  border-radius: 22px;
  cursor: pointer;
  transition: background 0.2s;
}
.int-toggle-slider::before {
  content: '';
  position: absolute;
  width: 16px; height: 16px;
  left: 3px; bottom: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
}
.integration-toggle input:checked + .int-toggle-slider { background: var(--logo-cyan); }
.integration-toggle input:checked + .int-toggle-slider::before { transform: translateX(18px); }

.integration-form {
  padding: 1rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.int-key-row { display: flex; gap: 0.4rem; align-items: center; }
.int-key-row .form-input { flex: 1; }
.int-clear-key { flex-shrink: 0; padding: 0.25rem 0.55rem; font-size: 0.78rem; }

.integration-sync-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.8rem;
  align-items: center;
  flex-wrap: wrap;
}
.int-sync-status { font-weight: 600; }
.int-sync-ok    { color: var(--green); }
.int-sync-error { color: var(--red); }
.int-sync-date  { color: var(--muted); }

.integration-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }

.int-feedback {
  margin: 0;
  font-size: 0.82rem;
  min-height: 1.1em;
}

.admin-add-form .admin-form-row {
  display: grid;
  grid-template-columns: 1fr 1fr 160px auto;
  gap: 0.75rem;
  align-items: end;
}

.admin-form-submit {
  display: flex;
  flex-direction: column;
}

/* Explanatory copy under an admin card title. The estate form needs it: the
   blank-versus-zero distinction is not guessable from the inputs alone. */
.admin-card-hint {
  font-size: 0.82rem;
  color: var(--muted);
  line-height: 1.55;
  margin: 0 0 1rem;
  max-width: 78ch;
}
/* Per-field note under an estate input, where the field's behaviour is not
   obvious from its label (e.g. blank means "follow the scan"). */
.admin-field-hint {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--muted);
}

.admin-card-hint strong { color: var(--text); font-weight: 590; }
.admin-card-hint em { font-style: normal; font-weight: 590; color: var(--text); }

/* The estate form has five fields plus a full-width notes row, so it cannot use
   the 4-column grid the other admin forms share. */
#estateForm .admin-form-row {
  grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
}
#estateForm .admin-form-row + .admin-form-row {
  grid-template-columns: 1fr;
  margin-top: 0.75rem;
}

@media (max-width: 900px) {
  #estateForm .admin-form-row { grid-template-columns: 1fr 1fr; }
}

.admin-form-error {
  color: #dc2626;
  font-size: 0.85rem;
  margin: 0.25rem 0;
}

.admin-form-success {
  color: #16a34a;
  font-size: 0.85rem;
  margin: 0.25rem 0;
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

.admin-table th {
  text-align: left;
  padding: 0.55rem 0.75rem;
  background: var(--surface2);
  color: var(--muted, #4A7A96);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--border);
}

.admin-table td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  color: var(--text);
}

.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tbody tr:hover td { background: var(--surface2); }

.admin-table-empty {
  text-align: center;
  color: var(--muted);
  padding: 2rem !important;
  font-size: 0.9rem;
  font-style: normal;
  font-weight: 500;
  background: var(--surface2);
}
.admin-table-empty::before {
  content: '⏳ ';
  margin-right: 0.4rem;
}

.admin-actions {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.admin-self-label {
  font-size: 0.75rem;
  color: var(--muted);
  font-style: italic;
}

/* Role badges in admin table */
.role-badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* btn-sm helper */
.btn-sm {
  min-height: 28px;
  padding: 4px 12px;
  font-size: var(--text-footnote);
}

/* Danger button */
/* Destructive. iOS almost never fills a destructive button — it tints the
   label red and leaves the fill neutral, so "delete" does not shout from the
   corner of every row. The filled form is reserved for a sheet's final
   confirmation. */
.btn-danger {
  background: var(--fill-3);
  color: var(--red-text);
}
.btn-danger:hover { background: rgba(255, 59, 48, .12); }
.btn-danger-filled {
  background: var(--red-solid);
  color: var(--on-red);
}
.btn-danger-filled:hover { background: var(--red-text); }

/* Edit modal — optional label */
.modal-optional {
  font-size: 0.75rem;
  color: var(--muted);
  font-weight: 400;
}

/* ── Per-page access editor (Admin tab) ─────────────────────────────────── */

.page-access-details {
  margin-top: 1rem;
  border-top: 1px solid var(--border);
  padding-top: 0.75rem;
}

.page-access-summary {
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.page-access-hint {
  font-size: 0.75rem;
  color: var(--muted);
  margin: 0.4rem 0 0.75rem;
}

.page-access-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.5rem 1rem;
}

.page-access-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.page-access-label {
  font-size: 0.85rem;
}

.page-access-select {
  flex: 0 0 auto;
  max-width: 11rem;
  padding: 0.25rem 0.4rem;
  font-size: 0.8rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card-bg);
  color: inherit;
}

@media (max-width: 480px) {
  .page-access-row { flex-direction: column; align-items: stretch; }
  .page-access-select { max-width: none; }
}

@media (max-width: 768px) {
  .admin-add-form .admin-form-row { grid-template-columns: 1fr; }
  .header-user-info { gap: 0.3rem; }
  .header-username { max-width: 80px; }
}

/* ── Uploaded-at timestamp ─────────────────────────────────────────────────────── */
.vuln-uploaded-at {
  font-size: 0.78rem;
  color: var(--muted);
  margin-left: 0.5rem;
}

/* ── Findings heading row (title + Export CSV btn) ─────────────────────── */
.findings-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

/* ── Armed (confirm) delete button ────────────────────────────────────── */
.btn-delete-armed {
  background: #dc2626 !important;
  color: #fff !important;
  border-color: #dc2626 !important;
}

/* ── Modal Esc hint ───────────────────────────────────────────────────────── */
.modal-esc-hint {
  font-size: 0.72rem;
  color: var(--muted);
  margin-right: auto;
}

/* ── Print: findings-only view ───────────────────────────────────────────── */
@media print {
  .app-header, .tab-nav,
  #vulnTenantFilterWrap, .vuln-scan-header,
  #vulns-stat-cards, .charts-grid,
  .filter-chips, .findings-heading-row .btn,
  .vuln-status-summary,
  .col-check, #vulnBulkBar { display: none !important; }

  /* Deliberately does NOT force panel visibility. This used to read
     `.tab-panel { display: block !important }` followed by hiding everything
     except #tab-vulns, while secure-score.css ran the mirror image for its own
     panel — and since it loads second, it won, so printing from ANY tab
     printed the Secure Score page. Inactive panels already carry the `hidden`
     attribute, and `[hidden] { display: none !important }` at the top of this
     file handles them, so whichever tab is open is the one that prints. */

  .vuln-findings-wrap {
    max-height: none !important;
    overflow: visible !important;
  }

  body { background: #fff; }
}

/* ── Bulk action bar ─────────────────────────────────────────────────────── */
.vuln-bulk-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.9rem;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 0.6rem;
}
.bulk-count {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
  margin-right: 0.2rem;
}
.bulk-status-select {
  font-size: 0.82rem;
  padding: 0.25rem 0.5rem;
}
.col-check {
  width: 2rem;
  text-align: center;
}

/* ── Age / SLA cells ─────────────────────────────────────────────────────── */
.age-cell { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.age-overdue { color: var(--red); font-weight: 700; }
.age-new { color: var(--muted); font-style: italic; }
.age-unknown { color: var(--muted); }

/* Due date = first detected + severity SLA (Critical 1w / High 2w / Med 1m / Low 2m) */
.due-cell { font-size: 0.8rem; white-space: nowrap; }
.due-cell em { font-style: normal; font-size: 0.72rem; opacity: 0.85; }
.due-ok { color: var(--muted); }
.due-soon { color: var(--amber); font-weight: 600; }
.due-overdue { color: var(--red); font-weight: 700; }
.due-closed { color: var(--muted); text-decoration: line-through; opacity: 0.7; }

/* ── Sidebar layout ────────────────────────────────────────────────────────────── */
/* Content follows the sidebar through the single --side-w token, so the two can
   never desynchronise. See the "Sidebar states" block for where it is set. */
#dashboard {
  margin-left: var(--side-w);
  transition: margin-left var(--nav-anim) ease;
}

/* ── Header center tab label ─────────────────────────────────────────────────── */
.header-center {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  min-width: 0;
}
.current-tab-label {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Side nav upload footer ─────────────────────────────────────────────────── */
.side-nav-footer {
  margin-top: auto;
  padding: 0.75rem 0.6rem 1rem;
  border-top: 1.5px solid rgba(var(--tint-light-rgb),.18);
  flex-shrink: 0;
}
.side-upload-btn {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.72rem 1rem;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--logo-blue) 0%, var(--logo-cyan) 100%);
  color: #fff;
  font-family: var(--font);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
  transition: opacity .18s, box-shadow .18s;
  letter-spacing: .2px;
  box-sizing: border-box;
}
.side-upload-btn:hover {
  opacity: 0.88;
  box-shadow: 0 4px 16px rgba(var(--tint-rgb),.30);
}
.side-upload-btn[hidden] { display: none; }

/* ── Side menu user info ───────────────────────────────────────────────────── */
.side-user-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.85rem 1rem;
  border-bottom: 1.5px solid rgba(var(--tint-light-rgb),.18);
  flex-shrink: 0;
  background: rgba(var(--tint-rgb),.04);
}
.side-user-details {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.side-username {
  font-family: var(--font);
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.side-logout-btn {
  flex-shrink: 0;
  font-size: 0.78rem !important;
  padding: 0.3rem 0.7rem !important;
}

/* ── Red Team Operations ─────────────────────────────────────────────────── */

.section-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.redteam-cal-wrap {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
}

.cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cal-title {
  font-weight: 600;
  font-size: 0.95rem;
  min-width: 180px;
  text-align: center;
}

.cal-view-toggle {
  display: flex;
  gap: 0.4rem;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}

.cal-day-header {
  font-size: 0.72rem;
  font-weight: 600;
  text-align: center;
  color: var(--muted);
  padding: 4px 0;
  text-transform: uppercase;
}

.cal-day {
  min-height: 72px;
  background: var(--surface2);
  border-radius: 4px;
  padding: 4px;
  position: relative;
  vertical-align: top;
}

.cal-day--empty {
  background: transparent;
}

.cal-day--today {
  outline: 2px solid var(--tint);
  outline-offset: -2px;
}

.cal-day-num {
  font-size: 0.72rem;
  color: var(--muted);
  display: block;
  margin-bottom: 2px;
}

.cal-event-bar {
  font-size: 0.68rem;
  color: #fff;
  border-radius: 3px;
  padding: 1px 4px;
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  line-height: 1.4;
}

.cal-event-bar:hover {
  filter: brightness(1.15);
}

.cal-event-more {
  font-size: 0.65rem;
  color: var(--muted);
  padding: 0 2px;
}

.cal-week-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.cal-week-col {
  background: var(--surface2);
  border-radius: 4px;
  padding: 6px;
  min-height: 120px;
}

.row-selected {
  background: rgba(59, 130, 246, 0.12) !important;
}

.overdue-date {
  color: #ef4444;
  font-weight: 600;
}

.empty-state {
  text-align: center;
  color: var(--muted);
  font-style: italic;
  padding: 1.5rem 0;
}

/* The only definition of these variants — earlier duplicates were removed.
   Each is an iOS system hue at 15% for the fill, paired with the darkened
   -text companion for the label, because every system colour fails AA as text
   at this size (systemRed 3.6:1, systemOrange 2.2:1, systemGreen 2.2:1). The
   fill carries the hue; the text carries the legibility. */
/* Fills are the OPAQUE -dim tokens rather than a translucent wash of the hue.
   Two reasons: they are themed, so dark mode swaps them automatically; and an
   opaque fill has one known contrast value instead of a different one on every
   background it lands on — the translucent version had to be verified against
   the card, the row and the hover state separately.
   Measured: light 4.8-5.6:1, dark 6.9-7.8:1. */
.badge-blue   { background: var(--blue-dim);   color: var(--blue-text); }
.badge-green  { background: var(--green-dim);  color: var(--green-text); }
.badge-amber  { background: var(--orange-dim); color: var(--orange-text); }
.badge-red    { background: var(--red-dim);    color: var(--red-text); }
.badge-muted  { background: var(--fill-3);     color: var(--muted); }

/* Risk Register kanban board */
.rr-board {
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;
}
.rr-column {
  flex: 0 0 260px;
  display: flex;
  flex-direction: column;
  background: var(--fill-4);
  border: none;
  border-radius: var(--radius-card);
  max-height: 75vh;
}
.rr-column.drag-over {
  background: rgba(var(--tint-rgb),.08);
  border-color: var(--tint);
}
.rr-column-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.75rem;
  font-weight: 600;
  font-size: 0.85rem;
  border-bottom: 1px solid var(--border);
}
.rr-column-count {
  background: rgba(148,163,184,.2);
  color: var(--muted);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  font-size: 0.75rem;
}
.rr-column-body {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.rr-card {
  background: var(--surface);
  border: none;
  border-radius: var(--radius-control);
  padding: var(--space-3);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-fast) var(--ease-spring),
              box-shadow var(--dur-fast) var(--ease);
}
.rr-card:active { transform: scale(0.98); }
.rr-card[draggable="true"] { cursor: grab; -webkit-user-drag: element; user-select: none; }
.rr-card.dragging { opacity: 0.5; }
.rr-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  font-weight: 600;
  font-size: 0.85rem;
}
.rr-card-title { flex: 1; }
.rr-card-meta {
  display: flex;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.75rem;
  margin-top: 0.35rem;
  flex-wrap: wrap;
}
.rr-card-due {
  color: var(--muted);
  font-size: 0.75rem;
  margin-top: 0.25rem;
}

/* Incident Response playbook track — ordered sequence of step tiles */
.ir-playbook-progress-label {
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 500;
}
.ir-playbook-progress-bar {
  height: 6px;
  border-radius: 999px;
  background: rgba(148,163,184,.2);
  margin-bottom: 0.85rem;
  overflow: hidden;
}
.ir-playbook-progress-fill {
  height: 100%;
  background: var(--tint);
  border-radius: 999px;
  transition: width .3s ease;
}
.ir-playbook-track {
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;
  align-items: stretch;
}
.ir-playbook-tile {
  flex: 0 0 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.75rem;
  box-shadow: 0 1px 2px rgba(0,0,0,.04);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.ir-playbook-tile-phase-tag {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--muted);
}
.ir-playbook-tile-step {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted);
}
.ir-playbook-tile-entry { font-size: 0.85rem; }
.ir-playbook-tile-meta {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  color: var(--muted);
  font-size: 0.75rem;
  flex-wrap: wrap;
}
.ir-playbook-tile.is-done {
  border-color: #22c55e;
  background: rgba(34,197,94,.06);
}
.ir-playbook-tile.is-current {
  border-color: var(--tint);
  box-shadow: 0 0 0 2px rgba(var(--tint-rgb),.25);
}
/* Upcoming steps are faded — but not their comments. Opacity on the tile itself
   would also fade the thread and the comment box, and a note left against a
   future step ("client has asked us not to reset passwords until Monday") is
   exactly the one that must be readable. So the fade is applied to everything
   in the tile EXCEPT the comments block. */
.ir-playbook-tile.is-upcoming > :not(.ir-step-comments) {
  opacity: 0.55;
}

/* ── Step comments ─────────────────────────────────────────────────────────── */
.ir-step-comments {
  border-top: 1px dashed var(--border);
  margin-top: 0.25rem;
  padding-top: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.ir-step-comments-toggle {
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.ir-step-comments-toggle:hover,
.ir-step-comments-toggle:focus-visible { color: var(--tint); outline: none; }
/* A step that has something written against it is worth noticing at a glance,
   without opening every thread to find the one that matters. */
.ir-step-comments-toggle.has-comments { color: #0891b2; }

.ir-step-thread {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-height: 220px;
  overflow-y: auto;
}
.ir-step-comment {
  background: rgba(148,163,184,.08);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.5rem;
  font-size: 0.78rem;
}
.ir-step-comment-head {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.7rem;
  margin-bottom: 0.15rem;
}
.ir-step-comment-author { font-weight: 600; }
/* white-space: pre-wrap so a pasted list of recipients keeps its line breaks.
   The body is escaped, never rendered as HTML, so this is presentation only. */
.ir-step-comment-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.ir-step-comment-empty { font-size: 0.75rem; color: var(--muted); }
.ir-step-comment-form { display: flex; flex-direction: column; gap: 0.3rem; }
.ir-step-comment-input {
  width: 100%;
  min-height: 3.2rem;
  resize: vertical;
  font: inherit;
  font-size: 0.78rem;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: inherit;
  box-sizing: border-box;
}
.ir-step-comment-error { font-size: 0.72rem; color: #dc2626; }
.rr-score-display {
  display: inline-block;
  min-width: 2.5rem;
  text-align: center;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-sm);
  background: rgba(148,163,184,.15);
  font-weight: 700;
}

/* iOS fields are a filled well, not an outlined box: no border, a light grey
   fill, and the focus state adds the tint ring. Body size (17px) because these
   are the one place a person actually types. */
.form-input,
.form-select,
.form-textarea {
  width: 100%;
  min-height: var(--target);
  padding: 11px 14px;
  border: none;
  border-radius: var(--radius-control);
  background: var(--fill-4);
  color: var(--text);
  /* Explicit, and not optional. Form controls do not inherit the page font,
     so without this they render in the UA default — and .form-textarea in
     particular fell through to the bare `textarea` rule's Cascadia Code, so
     every textarea in the app was monospace. */
  font-family: var(--font);
  font-size: var(--text-callout);
  letter-spacing: var(--track-body);
  line-height: 1.35;
  box-sizing: border-box;
  transition: background var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.form-input::placeholder,
.form-textarea::placeholder { color: var(--text-tertiary); }

.form-select {
  /* Native arrow removed for a chevron that matches the platform. */
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%238A8A8E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
}

.form-textarea { resize: vertical; min-height: 78px; }

/* --primary is not a token in this file, so the focus ring was falling back to
   a generic #3b82f6 that appears nowhere else in the product. */
.form-input:focus-visible,
.form-select:focus-visible,
.form-textarea:focus-visible,
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
  outline: none;
  background: var(--surface);
  box-shadow: var(--focus-ring), inset 0 0 0 1px var(--tint);
}

/* Read-only users get every control disabled; there was no styling for that
   state, so they saw raw UA grey and no cue as to why nothing responded. */
.form-input:disabled,
.form-select:disabled,
.form-textarea:disabled {
  background: var(--fill-4);
  color: var(--text-tertiary);
  cursor: not-allowed;
  opacity: 1;               /* override the UA's own dimming */
  -webkit-text-fill-color: var(--text-tertiary);  /* Safari ignores color when disabled */
}

/* ── Labels ────────────────────────────────────────────────────────────────
   iOS form labels are plain secondary text, not uppercase micro-caps. The
   uppercase + letter-spacing treatment reads as a section header, which made
   every field look like the start of a new group. */
.form-label {
  display: block;
  font-size: var(--text-footnote);
  font-weight: 400;
  text-transform: none;
  letter-spacing: var(--track-body);
  color: var(--muted);
  margin-bottom: 6px;
}

/* ── Switch ────────────────────────────────────────────────────────────────
   The iOS toggle. A checkbox is a form primitive; a switch is a setting that
   takes effect — which is what these actually are. Built on a real
   <input type="checkbox"> so it stays keyboard- and screen-reader-native, with
   the box itself hidden behind the track. */
.ios-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  width: 51px;
  height: 31px;
  cursor: pointer;
}
.ios-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.ios-switch .switch-track {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-capsule);
  background: var(--fill);
  transition: background var(--dur) var(--ease);
  pointer-events: none;
}
.ios-switch .switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #FFFFFF;
  box-shadow: 0 3px 8px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .16);
  /* The spring is what makes the thumb feel like it snaps rather than slides. */
  transition: transform var(--dur) var(--ease-spring);
  pointer-events: none;
}
.ios-switch input:checked ~ .switch-track { background: var(--green); }
.ios-switch input:checked ~ .switch-thumb { transform: translateX(20px); }
.ios-switch input:focus-visible ~ .switch-track { box-shadow: var(--focus-ring); }
.ios-switch input:disabled ~ .switch-track { opacity: .5; }
.ios-switch input:disabled { cursor: not-allowed; }

/* A switch belongs on a row with its label, label leading, control trailing —
   the settings-row shape. */
.form-switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--target);
}
.form-switch-row .form-switch-label {
  font-size: var(--text-callout);
  color: var(--text);
  letter-spacing: var(--track-body);
}


/* ── Reports tab ──────────────────────────────────────────────────────────── */

.rpt-actions { display: flex; gap: 0.5rem; }

.rpt-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.2rem;
}

.rpt-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 200px;
}
.rpt-field > span {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.rpt-field select,
.rpt-field input {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
}
.rpt-field select:disabled { opacity: 0.75; cursor: not-allowed; }

.rpt-sub {
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--text);
  margin: 1.6rem 0 0.4rem;
}

.rpt-help {
  font-size: 0.82rem;
  color: var(--muted);
  margin: 0 0 0.75rem;
  max-width: 62ch;
}

.rpt-notice {
  background: var(--surface3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--amber);
  border-radius: 8px;
  padding: 0.6rem 0.85rem;
  font-size: 0.84rem;
  color: var(--text);
  margin: 0 0 1rem;
}
.rpt-notice.error { border-left-color: var(--red); }

.rpt-section-list {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
}
.rpt-group {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  min-width: 230px;
}
.rpt-group-label {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.6rem;
}
.rpt-section-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.3rem 0;
  cursor: pointer;
}
.rpt-section-name { font-size: 0.9rem; color: var(--text); }

/* A section switched off because the client does not buy the service it
   reports on. Dimmed rather than hidden: the analyst must be able to see what
   was decided for them, and tick it back on. */
.rpt-section-na .rpt-section-name { opacity: 0.62; }
.rpt-section-tag {
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--separator);
  border-radius: 999px;
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  color: var(--text-muted, #6b7a89);
  white-space: nowrap;
  cursor: help;
}
.rpt-section-hint {
  display: block;
  font-size: 0.74rem;
  color: var(--text-muted, #6b7a89);
  margin-top: 0.1rem;
}
.rpt-sections-note {
  margin: 0.6rem 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted, #6b7a89);
}

.rpt-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0.9rem;
}
.rpt-tile {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.8rem 0.9rem;
}
.rpt-tile-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.rpt-tile-label { font-size: 0.88rem; font-weight: 600; color: var(--text); }
.rpt-source-badge {
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.15rem 0.4rem;
  border-radius: 4px;
  background: var(--surface3);
  color: var(--muted);
  white-space: nowrap;
}
.rpt-source-badge.warn {
  background: rgba(245, 158, 11, 0.16);
  color: #8a5a00;
}
.rpt-tile-derived {
  font-size: 0.76rem;
  color: var(--muted);
  margin-bottom: 0.45rem;
}
/* Names WHICH figure was derived, where more than one is defensible — the
   Secure Score is reported in scope of the services a client buys, and a
   number shown without that qualifier invites a hand-typed "correction". */
.rpt-tile-qual {
  opacity: 0.8;
  font-style: italic;
}
.rpt-tile-override {
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
}

.rpt-narrative {
  width: 100%;
  max-width: 900px;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  line-height: 1.55;
  resize: vertical;
  margin-bottom: 0.7rem;
}
.rpt-tile-actions {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
}

/* ── Managed EDR tab ───────────────────────────────────────────────────────── */
.edr-toolbar {
  display: flex;
  align-items: flex-end;
  gap: 0.8rem;
  flex-wrap: wrap;
  margin-bottom: 1.1rem;
}
.edr-toolbar-spacer { flex: 1 1 auto; }
.edr-field { display: flex; flex-direction: column; gap: 0.3rem; }
.edr-field > span {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.edr-field .form-input { min-width: 170px; }

.edr-sync-meta {
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
  padding-bottom: 0.4rem;
}

.edr-empty {
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 2.2rem 1.6rem;
  text-align: center;
  color: var(--muted);
}
.edr-empty p + p { margin-top: 0.5rem; font-size: 0.9rem; }
.edr-empty a { color: var(--logo-blue); font-weight: 600; }

.edr-grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 1.1rem;
  margin-top: 1.1rem;
}
.edr-grid-2 > .admin-card { margin: 0; }

.edr-chart-wrap { position: relative; height: 260px; margin-top: 0.6rem; }

.edr-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.edr-filters { display: flex; gap: 0.6rem; }
.edr-filters .form-input { min-width: 150px; font-size: 0.85rem; }

.edr-table-scroll { max-height: 460px; overflow-y: auto; margin-top: 0.7rem; }

.edr-fleet { margin-top: 0.6rem; }
.edr-fleet-coverage {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.6rem 0 1rem;
}
.edr-fleet-coverage-value {
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-weight: 700;
  color: var(--logo-blue);
  line-height: 1;
}
.edr-fleet-coverage-label { font-size: 0.85rem; color: var(--muted); }
.edr-fleet-table td:last-child { text-align: right; font-weight: 700; }

.edr-tone-red   { color: var(--red); }
.edr-tone-amber { color: var(--amber); }
.edr-tone-green { color: var(--green); }
.edr-muted      { color: var(--muted); font-style: italic; }
.edr-mono {
  font-family: 'Cascadia Mono', Consolas, monospace;
  font-size: 0.82rem;
  word-break: break-word;
}

.int-optional { font-weight: 400; color: var(--muted); text-transform: none; }

/* ── Rollup-backed tabs (Managed NDR, Managed Identity, AI Visibility) ──────
   These screens reuse the .edr-* toolbar, grid, chart and table classes. What
   is new here is the vocabulary for partial data: a panel whose log source is
   not ingesting renders a reasoned placeholder in place of its chart or table,
   sized to keep the card roughly its normal height so the grid does not reflow
   as sources come and go. */

.wz-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 180px;
  margin-top: 0.6rem;
  padding: 1.2rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-alt, var(--surface));
}
.wz-placeholder-text {
  color: var(--muted);
  font-weight: 600;
  font-size: 0.9rem;
}
.wz-placeholder-hint {
  margin-top: 0.4rem;
  color: var(--muted);
  font-size: 0.8rem;
  max-width: 34ch;
  line-height: 1.45;
}

/* Ingestion has stopped on a source that IS configured — distinct from a
   source that was never set up, which the placeholders already explain. */
.wz-banner {
  margin-bottom: 1.1rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border-left: 3px solid var(--amber, #F59E0B);
  background: rgba(245, 158, 11, .1);
  color: var(--text);
  font-size: 0.86rem;
}

/* Which store answered — live indexer or stored rollups. The two differ
   slightly (approximate cardinality, top-N truncation), so the reader is told
   rather than left to file a bug. */
.wz-source-note {
  margin-top: 1.1rem;
  color: var(--muted);
  font-size: 0.78rem;
  font-style: italic;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SIDEBAR STATES — expanded / icon rail / hidden
   ═══════════════════════════════════════════════════════════════════════════

   Layout is driven by ONE custom property, --side-w. Both .side-menu (width)
   and #dashboard (margin-left) read it, so they cannot drift apart, and any
   element added later gets the same offset for free. The alternative — a
   margin-left rule per state per element — is N×M rules and a guaranteed bug
   the first time a fourth state appears.

   The state class lives on <html> and is stamped by the inline bootstrap in
   index.html BEFORE first paint. Without that, rail users see the sidebar
   render at 280px and snap to 64px on every single page load. */

:root {
  --header-h:        66px;
  --side-w-expanded: 280px;
  --side-w-rail:     64px;
  --side-w:          var(--side-w-expanded);
  --nav-anim:        180ms;
}

.nav-expanded { --side-w: var(--side-w-expanded); }
.nav-rail     { --side-w: var(--side-w-rail); }
.nav-hidden   { --side-w: 0px; }

/* width:0 alone still leaves every control tabbable. visibility removes the
   subtree from the tab order, delayed on the way out so the collapse animates. */
.nav-hidden .side-menu {
  visibility: hidden;
  transition: width var(--nav-anim) ease, visibility 0s linear var(--nav-anim);
}

/* First paint only — cleared by sidenav.js in a double rAF. */
.nav-boot .side-menu,
.nav-boot #dashboard { transition: none !important; }

@media (prefers-reduced-motion: reduce) {
  .side-menu, #dashboard, .side-nav-caret { transition: none; }
}

/* ── Header hamburger ─────────────────────────────────────────────────────── */
.nav-burger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.nav-burger svg { width: 18px; height: 18px; }
.nav-burger:hover {
  background: rgba(var(--tint-light-rgb),.12);
  border-color: var(--logo-cyan);
  color: var(--logo-blue);
}
.nav-burger:focus-visible { outline: 2px solid var(--logo-cyan); outline-offset: 2px; }

/* Current tab name in the header. Revives .header-center / .current-tab-label,
   which had no markup. It earns its place in rail and hidden modes, where the
   sidebar no longer tells you where you are — so it is redundant, and hidden,
   only while the full-width sidebar is showing. */
.nav-expanded:not(.nav-mobile) .header-center { visibility: hidden; }

/* ── Category headers + rows ──────────────────────────────────────────────── */
/* A consistent row height keeps the rail from jittering against the expanded
   sidebar, and keeps the flyout's anchor rect predictable. */
.side-nav-item,
.side-nav-group-btn,
.side-upload-btn,
.side-dock-btn {
  min-height: 42px;
  white-space: nowrap;
  outline-offset: -2px;   /* focus ring must not be clipped by overflow-y:auto */
}

.side-nav-group { display: block; }
.side-nav-group-h { margin: 0; font-size: inherit; font-weight: inherit; }

.side-nav-group-btn {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.7rem 1rem;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font-family: var(--font);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: left;
  transition: background .15s, color .15s;
}
.side-nav-group-btn:hover { background: rgba(var(--tint-light-rgb),.10); color: var(--logo-blue); }
.side-nav-group-btn:focus-visible { outline: 2px solid var(--logo-cyan); outline-offset: 1px; }

.side-nav-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }

.side-nav-caret {
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  stroke: currentColor;
  transition: transform 150ms ease;
}
.side-nav-group.is-open > .side-nav-group-h .side-nav-caret { transform: rotate(90deg); }
.side-nav-group.is-open > .side-nav-group-h .side-nav-group-btn { color: var(--logo-blue); }

/* Child items sit indented under their category. */
.side-nav-group-items { display: block; }
.side-nav-group-items .side-nav-item { padding-left: 2.4rem; font-size: 0.87rem; }
.side-nav-group-items .side-nav-item:hover { padding-left: 2.55rem; }
.side-nav-group-items .side-nav-item.active { padding-left: calc(2.4rem - 4px); }

/* ── Footer: pinned Admin / Upload / dock ─────────────────────────────────── */
.side-nav-footer { display: flex; flex-direction: column; gap: 0.4rem; }

.side-dock-btn {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.6rem 1rem;
  border: 1.5px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  transition: background .15s, color .15s, border-color .15s;
}
.side-dock-btn:hover {
  background: rgba(var(--tint-light-rgb),.10);
  border-color: rgba(var(--tint-light-rgb),.30);
  color: var(--logo-blue);
}
.side-dock-btn:focus-visible { outline: 2px solid var(--logo-cyan); outline-offset: 1px; }
.side-dock-btn .side-nav-icon { transition: transform 180ms ease; }
.nav-rail .side-dock-btn .side-nav-icon { transform: rotate(180deg); }

/* ═══════════════════════════════════════════════════════════════════════════
   RAIL MODE
   ═══════════════════════════════════════════════════════════════════════════ */

/* Labels go visually away but MUST keep their accessible name. display:none or
   visibility:hidden here would leave a screen reader announcing "button"
   sixteen times over — the classic icon-rail accessibility failure. */
.nav-rail .side-nav-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* These carry no accessible name, so they can go entirely. */
.nav-rail .side-user-info,
.nav-rail .side-nav-caret,
.nav-rail .side-nav-group-items { display: none !important; }

.nav-rail .side-nav-item,
.nav-rail .side-nav-group-btn,
.nav-rail .side-upload-btn,
.nav-rail .side-dock-btn {
  justify-content: center;
  padding: 0.7rem 0;
  gap: 0;
}
.nav-rail .side-nav-item:hover { padding-left: 0; }
.nav-rail .side-nav-item.active {
  border-left: 3px solid var(--logo-cyan);
  padding-left: 0;
}
.nav-rail .side-nav { gap: 0.5rem; }
.nav-rail .side-nav-group-btn { border: 1px solid transparent; }
.nav-rail .side-nav-group-btn[aria-expanded="true"] {
  background: rgba(var(--tint-light-rgb),.14);
  border-color: rgba(var(--tint-light-rgb),.35);
  color: var(--logo-blue);
}

/* ── Rail flyout ──────────────────────────────────────────────────────────── */
/* position:fixed on a body-level host, because .side-menu is overflow-y:auto
   (which would clip an absolutely-positioned child) and sits at z-index 90
   inside its own stacking context (so a child could never paint above the
   z-index 100 header). Stack: sidebar 90 < header 100 < flyout 150 <
   .side-overlay 199 < mobile drawer 201 < modals 9999. */
.nav-flyout {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 150;
  display: flex;
  /* Transparent hit corridor: the flyout sits flush against the rail, so a
     mouse crossing the boundary is never over dead space. */
  padding-left: 8px;
  max-height: calc(100vh - 16px);
}
.nav-flyout-inner {
  min-width: 220px;
  max-width: 300px;
  overflow-y: auto;
  padding: 0.4rem 0.35rem 0.5rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--card-edge, var(--separator));
  box-shadow: var(--shadow);
}
.nav-flyout-title {
  padding: 0.4rem 0.9rem 0.5rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Undo the rail sr-only treatment on the cloned labels. */
.nav-flyout .side-nav-text {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  clip-path: none;
  overflow: hidden;
}
.nav-flyout .side-nav-item {
  justify-content: flex-start;
  gap: 0.75rem;
  padding: 0.6rem 0.9rem;
  font-size: 0.87rem;
}
.nav-flyout .side-nav-item:hover { padding-left: 1.05rem; }

/* The flyout only exists in rail mode. */
:not(.nav-rail) .nav-flyout { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE — drawer over content, declared last so it wins on ties
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Content is always full width; the sidebar overlays rather than displaces. */
  .nav-mobile { --side-w: 0px; }

  .nav-mobile .side-menu {
    width: min(280px, 84vw);
    transform: translateX(-100%);
    visibility: hidden;
    z-index: 201;
    transition: transform var(--nav-anim) ease, visibility 0s linear var(--nav-anim);
  }
  .nav-mobile.nav-expanded .side-menu {
    transform: none;
    visibility: visible;
    transition: transform var(--nav-anim) ease, visibility 0s;
  }

  /* A 64px rail on a phone is a worse drawer, not a better one. */
  .nav-mobile .side-dock-btn { display: none; }
}

/* Backdrop reuses the existing .side-overlay / .side-overlay.open rules. */
.side-overlay { top: var(--header-h); }

/* ── No accessible pages at all ───────────────────────────────────────────── */
/* Derived state, never persisted. auth.js already renders the explanatory
   message into #dashboard; this just removes the empty nav chrome around it. */
.nav-empty .side-menu,
.nav-empty .nav-burger { display: none !important; }
.nav-empty #dashboard  { margin-left: 0; }

/* Reports tab — attested metric entry */
.rpt-manual { display: flex; flex-direction: column; gap: 0.9rem; margin-bottom: 0.4rem; }
.rpt-mgroup { background: var(--surface2); border: 1px solid var(--border); border-radius: 10px; padding: 0.8rem 1rem 1rem; }
.rpt-mgh { font-weight: 700; color: var(--logo-blue); font-size: 0.86rem; margin-bottom: 0.6rem; }
.rpt-mitems { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.7rem 1rem; }
.rpt-mfield { display: flex; flex-direction: column; gap: 0.25rem; }
.rpt-mlabel { font-size: 0.78rem; color: var(--muted); }
.rpt-mfield input { width: 100%; padding: 0.45rem 0.6rem; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; font-size: 0.86rem; }
.rpt-mfield input:focus { outline: none; border-color: var(--logo-cyan); }

/* Reports tab — per-section commentary */
.rpt-comments { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 0.9rem; margin-bottom: 0.4rem; }
.rpt-cfield { display: flex; flex-direction: column; gap: 0.3rem; }
.rpt-clabel { font-size: 0.8rem; font-weight: 600; color: var(--logo-blue); }
.rpt-cfield textarea { width: 100%; padding: 0.55rem 0.7rem; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; font-size: 0.86rem; line-height: 1.45; resize: vertical; }
.rpt-cfield textarea:focus { outline: none; border-color: var(--logo-cyan); }

/* Reports tab — collapsible section commentary */
.rpt-comments-acc { border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); padding: 0.5rem 0.9rem; margin: 0.6rem 0 1rem; }
.rpt-comments-acc > summary { cursor: pointer; font-weight: 600; color: var(--logo-blue); font-size: 0.9rem; padding: 0.35rem 0; list-style: revert; }
.rpt-comments-acc > summary:focus-visible { outline: 2px solid var(--logo-cyan); outline-offset: 2px; }
.rpt-comments-acc[open] > summary { margin-bottom: 0.3rem; }

/* ═══════════════════════════════════════════════════════════════════════════
   INTERACTION FOUNDATIONS
   Appended last so the focus rules win over the component definitions above
   without needing !important. Everything here is cross-cutting: focus, motion
   and the small typographic corrections that follow from body{line-height}.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Focus ────────────────────────────────────────────────────────────────────
   The app had six :focus-visible rules, all on sidebar chrome. Buttons, chips,
   close buttons, sortable headers and table row actions had none at all, so a
   keyboard user navigating a tab fell back to the UA outline — which is close
   to invisible against this blue palette. One treatment, applied once.
   :focus-visible (not :focus) so a mouse click does not leave a ring behind. */
.btn:focus-visible,
.chip:focus-visible,
.modal-close:focus-visible,
.modal-close-btn:focus-visible,
.data-table th[data-sort]:focus-visible,
.data-table th.sortable-col:focus-visible,
.tpr-name-btn:focus-visible,
.rr-card:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--logo-cyan);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ── Motion ───────────────────────────────────────────────────────────────────
   `transition: all` makes the compositor watch every animatable property,
   including ones that change for unrelated reasons. Name what actually moves. */
.btn {
  transition: transform  var(--dur-fast) var(--ease),
              box-shadow var(--dur)      var(--ease),
              background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.stat-card {
  transition: transform  var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

/* The existing reduced-motion block covers only the sidebar. These are the
   parts that actually move on a dashboard: the infinite float on an empty-
   state icon, the entrance animation applied to every panel AND every card
   inside it (so a tab switch animates twice), and the hover lifts. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .empty-state-icon { animation: none !important; }
  .btn:hover,
  .stat-card:hover { transform: none !important; }
}

/* ── Typographic corrections ─────────────────────────────────────────────────
   body now sets line-height: 1.5, which is right for prose and too loose for
   headings and dense tabular data. Pull those back deliberately rather than
   leaving the whole app at the UA default to avoid the problem. */
h1, h2, h3, h4,
.section-title,
.modal-title,
.stat-value,
.empty-state-title { line-height: 1.25; }

.data-table       { line-height: 1.45; }
.badge            { line-height: 1.5; }

/* border-collapse: collapse paints cell backgrounds over the wrapper's rounded
   corners, so the table reads as a square block inside a rounded container.
   Clipping the wrapper restores the radius.

   `auto`, NOT the `overflow: hidden` shorthand this used to be. The shorthand
   sets BOTH axes, and this rule sits ~3000 lines below
   `.vuln-findings-wrap { max-height: 500px; overflow-y: auto }` at equal
   specificity — so it won, silently clamped overflow-y to hidden, and left the
   Vulnerabilities table showing its first seven rows of 257 with no way to
   reach the rest. The sticky `thead` a few hundred lines up is there precisely
   because that table is meant to scroll.

   `auto` clips to the border radius exactly as `hidden` did, so the reason this
   rule exists is unaffected, and a wrapper that sets its own height can scroll
   again. */
.table-wrapper { overflow: auto; }

/* The zebra stripe is gone rather than tuned. iOS lists do not stripe — inset
   separators already give the eye a row to track, and a stripe on top of them
   is two solutions to one problem. See the .data-table rules above. */

/* ═══════════════════════════════════════════════════════════════════════════
   THIRD-PARTY RISK TAB
   ═══════════════════════════════════════════════════════════════════════════ */

/* Numeric and date columns align right so digits stack and the eye can compare
   down the column. Generic enough for any .data-table to use. */
.data-table th.num,
.data-table td.num { text-align: right; white-space: nowrap; }

/* The vendor name is the row's control. Styled as text rather than as a button
   so the table stays a table — the affordance is the hover underline and the
   pointer, and the real <button> underneath gives keyboard and screen-reader
   users the same access the mouse always had. */
.tpr-name-btn {
  display: inline-block;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: inherit;
  font-weight: 700;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.tpr-name-btn:hover { color: var(--logo-blue); text-decoration: underline; }

/* Secondary line: present but recessive. Sized and coloured to sit clearly
   below the name in the hierarchy without becoming unreadable. */
.tpr-sub {
  margin-top: 0.15rem;
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--muted);
}
.tpr-status { font-weight: 700; color: var(--orange-text); }

.tpr-table td { vertical-align: top; }
.tpr-table td.num,
.tpr-table td:not(:first-child) { vertical-align: middle; }

/* Two badges can land in one cell (assurance + its expiry). Give them room
   rather than letting them collide. */
.tpr-table td .badge + .badge { margin-left: 0.3rem; }

.tpr-load-error {
  color: var(--muted);
  font-style: italic;
  padding: 1rem 0;
}

/* The live score readout in the modal. It was permanently grey while the same
   number wore a colour in the table two inches away — so the one place the
   user is actively changing the score gave the least feedback about it. */
.tpr-score {
  display: inline-block;
  min-width: 2.5rem;
  text-align: center;
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tpr-score.is-low    { background: var(--green-dim);  color: var(--green-text); }
.tpr-score.is-medium { background: var(--orange-dim); color: var(--orange-text); }
.tpr-score.is-high   { background: var(--red-dim);    color: var(--red-text); }

/* Paired date fields — four stacked full-width date pickers was a lot of
   vertical travel for four related values. */
.tpr-field-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
@media (max-width: 480px) {
  .tpr-field-pair { grid-template-columns: 1fr; }
}

/* Inline modal errors, replacing alert(). */
.tpr-modal-msg {
  flex: 1 1 auto;
  align-self: center;
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--red-text);
  text-align: left;
}
.tpr-modal-msg:empty { display: none; }

/* The network-access control is now an .ios-switch (see the switch rules in
   the controls section). A checkbox states a fact about a form; a switch
   states a setting that is on — which is what this is. */
.form-switch-row .form-switch-label { cursor: pointer; }

/* Five tiles. The shared .stat-grid uses auto-fit/minmax(180px), which at
   common desktop widths fits four and orphans the fifth on its own row — a
   layout that reads as broken rather than as five equal metrics. Explicit
   column counts, stepping down cleanly instead. */
.tpr-stats { grid-template-columns: repeat(5, minmax(0, 1fr)); }

@media (max-width: 1199px) {
  .tpr-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .tpr-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .tpr-stats { grid-template-columns: 1fr; }
}

/* Labels wrap to two lines at narrow tile widths while others stay on one,
   which leaves the numbers sitting at different heights across the row.
   Reserving the second line keeps the value baselines aligned. */
.tpr-stats .stat-label { min-height: 2.2em; }

/* ── Appearance toggle ─────────────────────────────────────────────────────── */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-capsule);
  background: var(--fill-4);
  color: var(--muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color      var(--dur-fast) var(--ease),
              transform  var(--dur-fast) var(--ease-spring);
}
.theme-toggle:hover  { background: var(--fill-3); color: var(--text); }
.theme-toggle:active { transform: scale(0.92); }
.theme-toggle:focus-visible {
  outline: 2px solid var(--tint);
  outline-offset: 2px;
}
.theme-icon { width: 18px; height: 18px; }

/* ── Dark-mode corrections ─────────────────────────────────────────────────────
   Rules whose LIGHT value is correct but which need a different value in dark,
   and which are not expressible as a single token. Kept together so the dark
   theme is one thing to read rather than scattered conditionals.

   This block is deliberately short. Everything else in the app themes itself
   through the tokens — if this section grows, that is the signal that a
   component is hardcoding colour and should be tokenised instead. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-secops,
  :root:not([data-theme="light"]) .side-menu-brand .brand-secops { color: var(--tint); }

  /* The logo is a dark-ink PNG/SVG on a light ground; on black it disappears.
     Lifting it slightly keeps the mark visible without a second asset. */
  :root:not([data-theme="light"]) .header-logo,
  :root:not([data-theme="light"]) .brand-logo { filter: brightness(1.6); }
}
:root[data-theme="dark"] .brand-secops,
:root[data-theme="dark"] .side-menu-brand .brand-secops { color: var(--tint); }
:root[data-theme="dark"] .header-logo,
:root[data-theme="dark"] .brand-logo { filter: brightness(1.6); }

/* ── Card definition in dark ────────────────────────────────────────────────
   The single most important dark-mode rule in this file.

   In light, a white card on a grey canvas separates on background alone. In
   dark, a #212429 card on a #17191D canvas is a ~1.13:1 step — technically a
   difference, perceptually almost nothing, because the eye discriminates
   poorly at the dark end and ambient light on an LCD washes out what is left.
   Drop shadows do not help either; a shadow needs something lighter to fall
   against.

   macOS solves it with a hairline edge, and so does this. --card-edge is only
   defined in the dark themes, so in light these rules resolve to `transparent`
   and change nothing. */
.stat-card,
.table-wrapper,
.modal-box,
.rr-card,
.chart-card,
.upload-card,
.operations-section,
.grc-domain-card,
.empty-state-container {
  border: 1px solid var(--card-edge, transparent);
}

/* Recessed surfaces get the inverse treatment — a slightly darker inset line,
   so a well reads as pressed in rather than raised. */
.form-input,
.form-select,
.form-textarea,
.filter-chips,
.view-toggle,
.rr-column {
  border: 1px solid var(--well-edge, transparent);
}

/* The selected pill in a segmented control needs its own edge in dark: it sits
   on a track that is already light-on-dark, so the value step alone is too
   subtle to read as "raised". */
.chip.active,
.view-toggle-btn.active {
  border: 1px solid var(--card-edge, transparent);
}

/* ── Secure Score: unmeasured components ────────────────────────────────────
   A component with no data scores 0 by design. These styles exist so that
   zero is never mistaken for a failed assessment — the card is visibly a
   "nothing here" state rather than a bad result. */
.score-unmeasured-note {
  max-width: 62ch;
  margin: var(--space-3) auto 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-card);
  background: var(--orange-dim);
  color: var(--orange-text);
  border: 1px solid var(--card-edge, transparent);
  font-size: var(--text-footnote);
  line-height: 1.5;
  text-align: left;
}
.score-unmeasured-note strong { font-weight: 590; }

/* The estate the vulnerability score was sized against. Informational by
   default — the denominator is doing real work now, so it has to be visible
   and challengeable — and amber only when nothing has been recorded. */
.score-estate-note {
  max-width: 62ch;
  margin: var(--space-3) auto 0;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-card);
  background: var(--surface2);
  color: var(--muted);
  border: 1px solid var(--card-edge, transparent);
  font-size: var(--text-footnote);
  line-height: 1.5;
  text-align: left;
}
.score-estate-note strong { font-weight: 590; color: var(--text); }
.score-estate-note.warn {
  background: var(--orange-dim);
  color: var(--orange-text);
}
.score-estate-note.warn strong { color: inherit; }
/* "(detected)" marks a figure the system derived rather than one somebody
   typed — the difference matters when a number looks wrong. */
.score-estate-note .estate-src {
  font-size: .92em;
  opacity: .75;
}
/* A caveat on the estate the score was computed from — an ageing declaration,
   or one the telemetry disagrees with. Set apart so it is not read as part of
   the workings, but deliberately NOT alarming: the score above used the
   recorded figures exactly as they stand and nothing has been discounted. */
.score-estate-note .score-estate-stale {
  display: block;
  margin-top: var(--space-2);
  padding-left: var(--space-2);
  border-left: 2px solid var(--orange-text, #f39c12);
}

/* Dashed edge reads as "absent", not "failed". */
.component-card.component-unmeasured {
  border: 1px dashed var(--separator-solid);
  background: var(--fill-4);
}
.component-card.component-unmeasured .component-score-text { color: var(--muted); }

.component-nodata-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: var(--radius-capsule);
  background: var(--orange-dim);
  color: var(--orange-text);
  font-size: var(--text-caption-2);
  font-weight: 590;
  vertical-align: middle;
}

/* ── Reports: the published-archive panel ─────────────────────────────────
   What the client can currently see in their portal, shown on the Reports tab
   so staff never have to guess what was sent. */
.rpt-published {
  margin: 0 0 1.5rem;
  border: 1px solid var(--card-edge, var(--separator));
  border-radius: var(--radius, 10px);
  background: var(--card-bg, var(--surface));
}
.rpt-published > summary {
  cursor: pointer; padding: 0.75rem 1rem;
  font-size: 0.9rem; font-weight: 600;
}
.rpt-published > summary:focus-visible {
  outline: 2px solid var(--logo-cyan, var(--tint)); outline-offset: -2px;
}
.rpt-published > div { padding: 0 1rem 1rem; }
.rpt-pub-empty { color: var(--muted); font-size: 0.85rem; margin: 0.5rem 0; }
.rpt-pub-table { width: 100%; font-size: 0.85rem; }
.rpt-pub-actions { text-align: right; white-space: nowrap; }
/* A withdrawn report is still in the record but is no longer being shown to
   the client, and must not read as current. */
.rpt-pub-withdrawn { opacity: 0.55; }
.rpt-pub-tag {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--orange, var(--amber)); margin-left: 0.4rem;
}

/* ── Admin: account state ─────────────────────────────────────────────────
   A client is an EXTERNAL account and must never read as an internal one —
   before this it fell through the badge chain to "Read-only", which is a
   staff role with read access to every tab. */
.role-badge.role-client {
  background: rgba(109, 40, 217, .14);
  color: #6d28d9;
  border: 1px solid rgba(109, 40, 217, .3);
}

/* Suspended is the only one that changes what the account can do, so it is
   the only one coloured as a warning. */
.role-badge.state-suspended {
  background: rgba(220, 38, 38, .14);
  color: #b91c1c;
  border: 1px solid rgba(220, 38, 38, .3);
}
.role-badge.state-pending {
  background: rgba(202, 138, 4, .16);
  color: #a16207;
  border: 1px solid rgba(202, 138, 4, .3);
}
.role-badge.state-ok {
  background: rgba(22, 163, 74, .14);
  color: #15803d;
  border: 1px solid rgba(22, 163, 74, .3);
}

/* Dimmed rather than hidden: a suspended account is still part of the record
   of who had access, and hiding it would make it easy to forget. */
.admin-row-suspended > td { opacity: 0.6; }
.admin-row-suspended > td:first-child { opacity: 1; }
.admin-muted { color: var(--muted); }

:root[data-theme="dark"] .role-badge.role-client     { color: #c4b5fd; }
:root[data-theme="dark"] .role-badge.state-suspended { color: #fca5a5; }
:root[data-theme="dark"] .role-badge.state-pending   { color: #fcd34d; }
:root[data-theme="dark"] .role-badge.state-ok        { color: #86efac; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .role-badge.role-client     { color: #c4b5fd; }
  :root:not([data-theme="light"]) .role-badge.state-suspended { color: #fca5a5; }
  :root:not([data-theme="light"]) .role-badge.state-pending   { color: #fcd34d; }
  :root:not([data-theme="light"]) .role-badge.state-ok        { color: #86efac; }
}


/* ── Admin › Branding (js/tab-branding.js) ─────────────────────────────── */
.branding-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0 1rem;
}
.branding-preset {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem 0.4rem 0.4rem;
  border: 1.5px solid var(--separator-solid);
  border-radius: var(--radius-capsule);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.branding-preset:hover { background: var(--surface2); }
.branding-preset.is-on { border-color: var(--tint); box-shadow: var(--focus-ring); }
.branding-preset-swatch {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  flex-shrink: 0;
}
.branding-custom { display: flex; flex-wrap: wrap; gap: 1rem; }
.branding-pick { display: flex; align-items: center; gap: 0.5rem; }
.branding-pick input[type="color"] {
  width: 40px;
  height: 36px;
  padding: 2px;
  border: 1px solid var(--separator-solid);
  border-radius: var(--radius-tight);
  background: var(--surface);
  cursor: pointer;
}
.branding-pick input[type="text"] { width: 7.5rem; font-family: var(--font-mono); }
.branding-optional { color: var(--muted); font-weight: 400; }
.branding-result { margin: 0.75rem 0; }
.branding-chips { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.branding-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.branding-chip-swatch {
  width: 18px;
  height: 18px;
  border-radius: 5px;
  border: 1px solid var(--separator);
}
.branding-note { margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--muted); }
.branding-logo-row { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; }
.branding-logo-preview {
  display: grid;
  place-items: center;
  width: 180px;
  height: 120px;
  padding: 0.75rem;
  border: 1px dashed var(--separator-solid);
  border-radius: var(--radius-card);
  background: var(--surface2);
}
.branding-logo-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
.branding-logo-meta { margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--muted); }
.branding-upload-btn { cursor: pointer; }

/* ═══════════════════════════════════════════════════════════════════════════
   enc0ded CI LAYER
   ═══════════════════════════════════════════════════════════════════════════

   The colour is all in the token blocks at the top; this layer carries the
   parts of the identity a token cannot: the wordmark, the copper gradient on
   committing actions, the faint copper glow behind the canvas, and the
   console voice — JetBrains Mono, uppercase and tracked — on every label that
   names a thing rather than says something. Kept last so it wins on order
   without raising specificity anywhere. */

/* ── Wordmark: one cut per scheme ─────────────────────────────────────────
   Mint wordmark on dark, ink on light. Both <img>s are in the markup and the
   theme decides which renders, so a theme switch needs no script and there is
   no flash of the wrong cut. An uploaded logo replaces both URLs server-side. */
.brand-logo-dark  { display: none !important; }
.brand-logo-light { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo-dark  { display: block !important; }
  :root:not([data-theme="light"]) .brand-logo-light { display: none !important; }
}
:root[data-theme="dark"] .brand-logo-dark  { display: block !important; }
:root[data-theme="dark"] .brand-logo-light { display: none !important; }

.header-logo-svg {
  height: 22px;
  filter: none;
}
.header-brand { align-items: center; }
.header-brand .brand-secops,
.side-menu-brand .brand-secops {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 1.5px;
  color: var(--muted);
  text-shadow: none;
  padding-left: 0.6rem;
  border-left: 1px solid var(--separator-solid);
}
.side-menu-brand .brand-secops { border-left: none; padding-left: 0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-secops,
  :root:not([data-theme="light"]) .side-menu-brand .brand-secops { color: var(--muted); }
}
:root[data-theme="dark"] .brand-secops,
:root[data-theme="dark"] .side-menu-brand .brand-secops { color: var(--muted); }

/* ── Canvas ───────────────────────────────────────────────────────────────
   Two soft copper glows off the top corners, as on every enc0ded surface.
   Dark only: on the pale-mint canvas they read as a stain, not light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body {
    background-image:
      radial-gradient(820px 420px at 82% -12%, rgba(192, 138, 78, .09), transparent),
      radial-gradient(620px 320px at 0% 0%, rgba(138, 92, 36, .07), transparent);
    background-attachment: fixed;
  }
}
:root[data-theme="dark"] body {
  background-image:
    radial-gradient(820px 420px at 82% -12%, rgba(192, 138, 78, .09), transparent),
    radial-gradient(620px 320px at 0% 0%, rgba(138, 92, 36, .07), transparent);
  background-attachment: fixed;
}

/* ── Console voice ────────────────────────────────────────────────────────
   Labels, headings that name a section, table heads and the sidebar group
   headers. Body copy, values and controls stay in Inter. */
.section-heading,
.chart-title,
.nav-flyout-title,
.stat-card .stat-label,
.data-table th,
.admin-table th,
.side-nav-group-btn,
.side-menu-sub,
.rpt-group-label,
.cal-day-header,
.header-role-badge,
.current-tab-label {
  font-family: var(--font-mono);
  letter-spacing: .6px;
}
.stat-card .stat-label {
  text-transform: uppercase;
  font-size: 11px;
  color: var(--muted);
}
.section-heading { font-size: 0.82rem; letter-spacing: 1.2px; color: var(--muted); }
.side-nav-group-btn { font-size: 0.74rem; letter-spacing: 1.2px; }
.data-table th, .admin-table th { font-size: 11px; text-transform: uppercase; }

/* Numbers read as instrument readouts: tabular, so columns and counters
   do not shimmy as values change. */
.stat-card .stat-value,
.data-table td.num,
.data-table th.num { font-variant-numeric: tabular-nums; }

/* The underline under a section heading is the copper ramp, bronze → gold. */
.section-heading::before {
  background: linear-gradient(90deg, var(--logo-teal, #8A5C24), var(--tint-light));
}

/* ── Committing actions ───────────────────────────────────────────────────
   The enc0ded button is a short vertical gold→copper gradient with ink type,
   squarer than the iOS capsule. In light the fill is solid bronze with white
   type — the gradient's top stop would fail as a fill behind white. */
.btn { border-radius: var(--radius-tight); }
.side-upload-btn {
  background: var(--tint-fill);
  color: var(--on-tint);
  border-radius: var(--radius-tight);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn-primary,
  :root:not([data-theme="light"]) .side-upload-btn {
    background: linear-gradient(180deg, var(--tint-light), var(--tint));
    color: var(--on-tint);
  }
  :root:not([data-theme="light"]) .btn-primary:hover,
  :root:not([data-theme="light"]) .side-upload-btn:hover { filter: brightness(1.07); }
}
:root[data-theme="dark"] .btn-primary,
:root[data-theme="dark"] .side-upload-btn {
  background: linear-gradient(180deg, var(--tint-light), var(--tint));
  color: var(--on-tint);
}
:root[data-theme="dark"] .btn-primary:hover,
:root[data-theme="dark"] .side-upload-btn:hover { filter: brightness(1.07); }

/* ── Sidebar selection ────────────────────────────────────────────────────
   A copper wash with a copper rule on the leading edge, rather than a solid
   filled pill — on the green-black rail a full copper block shouts. */
.side-nav-item.active {
  background: var(--tint-wash);
  color: var(--tint);
  box-shadow: inset 2px 0 0 var(--tint);
}
.side-nav-item.active .side-nav-icon { stroke: var(--tint); }
.side-nav-item:focus-visible { outline-color: var(--tint); }

/* ── Login ──────────────────────────────────────────────────────────────── */
.login-card {
  border-radius: 14px;
  border-color: var(--separator-solid);
  box-shadow: var(--shadow);
}
.login-logo-wrap { align-items: flex-start; gap: 0.35rem; margin-bottom: 1.75rem; }
.login-logo { height: 28px; width: auto; margin-bottom: 1rem; }
.login-title { color: var(--text); font-size: 1.1rem; font-weight: 700; }
.login-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
  letter-spacing: .4px;
}
.login-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--muted);
}
.login-input { background: var(--bg); border-radius: var(--radius-tight); font-family: var(--font-mono); font-size: 14px; }
.login-input:focus { border-color: var(--tint); box-shadow: 0 0 0 3px var(--tint-wash); }
body.login-page { background-color: var(--bg); }
.login-page .form-group { margin-bottom: 0.9rem; }
