/* ──────────────────────────────────────────────────────────────
   page.css — composed on top of tokens.css.
   Covers Saved Tests + Reports + Custom Reports builder + every
   modal/popover/tooltip the prototype renders.

   ─────────── TABLE OF CONTENTS ───────────
   (search by section comment to jump — line numbers shift)

   • Tooltip system          [data-tip] base + align variants
   • Layout shell            .app, .shell, .main, viewport scroll
   • Row actions             kebab menu, action button hover
   • Profile popover         peek card on student-name hover
   • Top nav                 .topnav-* + density toggle + bell
   • Client rail             left overlay drawer for institutes
   • Main column             page-canvas tone + padding
   • Filter bar              base + reports + templates variants
   • Time filter dropdown    presets list + calendar pane
   • Range band              calendar grid hover/select states
   • Data table              base, density-cozy, density-compact
   • Page header             title + sub + tabs slot + actions
   • Tabs                    underline-style, scrollable strip
   • Breadcrumbs
   • KPI strip + .kpi-sub    value / sub / icon-seg
   • Pass-rate cell + fail bar  + .pr-cell-tip floating card
   • Severity pill           Minor / Major
   • Status pill (unified)   active/paused/inactive/draft tones
   • Callout banner          info/warn/etc strip
   • Report doc-head         title row + level chain row
   • Level chain             drill-down pills + nav hint
   • Drill-down picker       popover from chain tail
   • Empty state             shared illustration + copy
   • Stepper                 7-step wizard nav (Custom Reports)
   • Reports page            tab body, doc, split panes,
                             builder, builder filters, custom
                             reports list, schedules list
   • Unified in-table tooltip  applied to action buttons + pills
   • Branding panel          (legacy class prefix `.tweaks-*`)
   • Responsive              breakpoints: 1279, 1079, 899, 599
   • Generate-report modal
   • Create-schedule modal
   • Save-template confirm modal
   • Schedule history modal
   ──────────────────────────────────────── */

/* Fast CSS tooltip — any element with data-tip shows an instant tooltip. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: oklch(0.22 0 0);
  color: oklch(0.97 0 0);
  padding: 5px 9px;
  border-radius: 6px;
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 90ms ease;
  z-index: 200;
  box-shadow: 0 4px 10px -4px rgba(0,0,0,0.25);
  max-width: min(320px, 90vw);
  overflow: hidden;
  text-overflow: ellipsis;
}
[data-tip]:hover::after { opacity: 1; }
/* Topnav tooltips render below the bar so they aren't clipped by the
   viewport's top edge (e.g. the notifications bell). */
.topnav [data-tip]::after {
  bottom: auto;
  top: calc(100% + 6px);
}
/* Elements near the right edge anchor their tooltip to the right so
   it never overflows the viewport (e.g. density toggle, avatar). */
.topnav-tools > :last-child[data-tip]::after,
.filter-actions [data-tip]::after,
[data-tip-align="right"]::after {
  left: auto;
  right: auto;
  inset-inline-end: 0;
  transform: none;
}
/* Bottom-anchored tooltip variant — used by row-icon-btn instances
   inside table cells where the parent table-wrap has overflow-clip
   for border-radius rounding, which would otherwise hide the
   tooltip rendered above the button. Pointing the tooltip downward
   keeps it inside the row and visible. */
[data-tip-align="bottom"]::after {
  bottom: auto;
  top: calc(100% + 6px);
}

/* ─────────── Layout shell ─────────── */
/* Force the vertical scrollbar to always be present so the page never
   shifts horizontally when content height changes (filtering a table down,
   switching to a shorter tab, etc.).
   - macOS Chrome/Safari/Firefox with overlay scrollbars: stays overlay,
     visible only while scrolling — no visible track, no shift.
   - Windows / Linux / macOS with "Always show scrollbars": permanent track,
     no shift, no awkward "ghost gap" that scrollbar-gutter would leave.
   This avoids the Safari quirk where `scrollbar-gutter: stable` reserves
   visible empty space without filling it with a scrollbar. */
html { overflow-y: scroll; }
/* Login page is the exception — its content is fixed-height (sign-in
   card on a centered canvas, no dynamic-height tables or tabs) so the
   "always-on scrollbar" rule above just leaves an unused gutter on
   the trailing edge. Override to `auto` so the scrollbar only appears
   if the viewport is shorter than the card, and disappears otherwise. */
html:has(body.auth-body) { overflow-y: auto; }
.app {
  min-height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--bg);
  overflow-x: clip;
}
/* Dashboard ambient brand bloom — dark mode only.
   Same composition as the login-page corner bloom but at roughly half
   the intensity (18% / 12% vs. the login's 38% / 28%) and flipped to
   the *opposite* diagonal: top-leading + bottom-trailing instead of
   top-trailing + bottom-leading. The reason is anchoring — the bloom
   reads as if it's emanating downward from the tenant logo (top of
   the leading edge of the topnav) and bracketing the page H1 below
   it. The bottom-trailing bloom balances the diagonal so the canvas
   doesn't feel one-sidedly lit.

   The login is allowed to be cinematic because it's the doorway with
   one card on a mostly empty canvas. The dashboard is the workplace,
   with dense data surfaces — too much atmospheric color competes with
   chart axes and status pills. At these percentages the bloom only
   registers in page margins and between cards, never bleeding into
   table contrast.

   `background-attachment: fixed` anchors the gradient to the viewport
   so the blooms stay at the viewport corners as the user scrolls,
   rather than sliding off the top with `.app`'s content height.

   RTL flip: in Arabic the tenant logo and page H1 mirror to the right
   side of the topnav, so the blooms must mirror with them. CSS
   percentage positions in radial-gradient aren't writing-direction
   aware (they're absolute on the canvas), so we explicitly swap the
   stops under `[dir="rtl"]`.

   Light mode is intentionally unscoped — the existing light bg-sunken
   plus brand-tinted topnav/KPIs already carries enough brand presence. */
[data-theme="dark"] .app {
  background-image:
    /* top-leading bloom — sits under the tenant logo and page H1 */
    radial-gradient(1200px 600px at -10% -10%, color-mix(in oklab, var(--brand-500) 18%, transparent), transparent 60%),
    /* bottom-trailing bloom — diagonal balance */
    radial-gradient(900px 600px at 110% 110%, color-mix(in oklab, var(--brand-500) 12%, transparent), transparent 60%);
  background-attachment: fixed;
}
[data-theme="dark"][dir="rtl"] .app {
  background-image:
    /* top-leading in RTL = top-right (where the logo + H1 now live) */
    radial-gradient(1200px 600px at 110% -10%, color-mix(in oklab, var(--brand-500) 18%, transparent), transparent 60%),
    /* bottom-trailing in RTL = bottom-left */
    radial-gradient(900px 600px at -10% 110%, color-mix(in oklab, var(--brand-500) 12%, transparent), transparent 60%);
}

.shell {
  display: grid;
  grid-template-columns: 1fr;        /* main content always takes the full width */
  gap: 0;
  min-height: calc(100vh - 64px);
  position: relative;
  overflow-x: clip;
}

/* Driving-school rail overlay positioning — see the consolidated block at
   "Client Rail (left)" further down for the visuals. These rules just
   make the rail an overlay so the table always gets full width. */
.shell.rail-collapsed .client-rail {
  transform: translateX(-100%);
  box-shadow: none;
  pointer-events: none;
}
[dir="rtl"] .shell.rail-collapsed .client-rail {
  transform: translateX(100%);
}

/* Dimmed backdrop while the rail is open */
.rail-backdrop {
  position: fixed;
  top: 64px;
  left: 0; right: 0; bottom: 0;
  background: rgba(15, 23, 42, 0.35);
  z-index: 35;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast);
}
[data-theme="dark"] .rail-backdrop { background: rgba(0, 0, 0, 0.55); }
.shell:not(.rail-collapsed) .rail-backdrop {
  opacity: 1;
  pointer-events: auto;
}

/* School switcher — clickable page-title that opens the rail.
   Sizes to content so the institute name sits flush next to the logo
   instead of floating in a wide pill. */
.school-switcher {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 4px 14px 4px 5px;
  max-width: min(560px, 100%);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  align-self: flex-start;
  margin-bottom: 4px;
  transition: background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
  -webkit-tap-highlight-color: transparent;
}
[dir="rtl"] .school-switcher { padding: 4px 5px 4px 14px; }
.school-switcher:hover {
  background: var(--bg-raised);
  border-color: var(--line);
  box-shadow: var(--sh-xs);
}
.school-switcher[aria-expanded="true"] {
  background: var(--brand-tint);
  border-color: color-mix(in oklch, var(--brand-500) 30%, var(--line));
}
.school-switcher .page-title {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
[dir="rtl"] .school-switcher .page-title { font-size: 15px; }
.school-switcher-mark {
  width: 28px; height: 28px;
  flex-shrink: 0;
  border-radius: 8px;
  display: grid; place-items: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  color: white;
  position: relative; overflow: hidden;
}
.school-switcher-mark::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,0.18), rgba(0,0,0,0.08));
}
.school-switcher-mark > span { position: relative; z-index: 1; }
.school-switcher-mark[data-tone="A"] { background: oklch(0.55 0.16 240); }
.school-switcher-mark[data-tone="B"] { background: oklch(0.60 0.17 18); }
.school-switcher-mark[data-tone="C"] { background: oklch(0.55 0.13 160); }
.school-switcher-mark[data-tone="D"] { background: oklch(0.50 0.16 285); }
.school-switcher-mark[data-tone="E"] { background: oklch(0.55 0.14 205); }
.school-switcher-mark[data-tone="F"] { background: oklch(0.65 0.14 45); }
.school-switcher-mark[data-tone="G"] { background: oklch(0.58 0.14 325); }
.school-switcher-mark[data-tone="H"] { background: oklch(0.55 0.12 185); }
.school-switcher-mark[data-tone="I"] { background: oklch(0.55 0.12 120); }
.school-switcher-mark[data-tone="J"] { background: oklch(0.55 0.16 255); }
/* Subtle "switch institute" hint sitting after the institute name.
   Dimmed by default so the institute name stays the visual anchor;
   brightens to full strength when the user hovers the whole CTA. */
.school-switcher-hint {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
  white-space: nowrap;
  flex-shrink: 0;
  opacity: 0.85;
  transition: color var(--t-fast), opacity var(--t-fast);
}
[dir="rtl"] .school-switcher-hint { font-size: 12.5px; }
.school-switcher:hover .school-switcher-hint {
  color: var(--ink-2);
  opacity: 1;
}
.school-switcher[aria-expanded="true"] .school-switcher-hint {
  color: var(--brand-700);
  opacity: 1;
}
.school-switcher-chev {
  color: inherit;
  transition: transform var(--t-fast);
  flex-shrink: 0;
  opacity: 0.85;
}
.school-switcher[aria-expanded="true"] .school-switcher-chev {
  transform: rotate(180deg);
}

/* ── Row actions (per-row kebab menu) ──
   Keep the inline-end gap equal to the inline-start gap of the first
   (Traffic File) cell — they're both 10px via the .data-table td:first/last-child
   rules, so we just align the kebab to the trailing edge of the cell. */
.cell-actions { text-align: end; padding-inline-start: 4px; }
.row-actions { position: relative; display: inline-flex; }
.row-actions-btn {
  width: 28px; height: 28px;
  display: inline-grid; place-items: center;
  background: transparent;
  border: 0;
  border-radius: 8px;
  color: var(--ink-3);
  cursor: pointer;
  padding: 0;
  opacity: 0;
  transition: opacity var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.data-table tbody tr:hover .row-actions-btn,
.row-actions-btn.is-open,
.row-actions-btn:focus-visible { opacity: 1; }
.row-actions-btn:hover { background: var(--brand-tint); color: var(--brand-700); }
.row-actions-btn.is-open {
  background: var(--brand-tint-2);
  color: var(--brand-700);
}

.row-actions-menu {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  min-width: 220px;
  padding: 4px;
  display: flex; flex-direction: column;
  gap: 1px;
  animation: dropdown-in 120ms ease-out;
}
.row-actions-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  white-space: nowrap;
}
[dir="rtl"] .row-actions-item { font-size: 13.5px; }
.row-actions-item:hover { background: var(--brand-tint); color: var(--brand-700); }
.row-actions-item svg { color: var(--brand-600); flex-shrink: 0; }

/* ── Profile popover (peek card) ── */
.profile-popover {
  width: 320px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  /* Cap height to whatever fits between the trigger and the nearest
     viewport edge, minus a small margin. JS anchors by the closest
     edge (top when below, bottom when above), so this max keeps the
     popover within the viewport even on short screens — the body
     becomes scrollable rather than overflowing off-page. */
  max-height: calc(100vh - 24px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: dropdown-in 120ms ease-out;
}
.profile-popover .profile-info {
  overflow-y: auto;
  /* Keep scrollbar inside the rounded card so it doesn't sit on top
     of the bottom-radius. */
  scrollbar-gutter: stable;
}
.profile-header {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 14px 12px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--brand-500) 4%, var(--bg-raised));
}
[data-theme="dark"] .profile-header { background: color-mix(in srgb, var(--brand-500) 8%, var(--bg-raised)); }
.profile-photo {
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--bg-sunken);
  border: 2px solid var(--bg-raised);
  box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 0 0 1px var(--line);
}
.profile-names { min-width: 0; flex: 1; }
.profile-name {
  font-size: 14px; font-weight: 600; color: var(--ink);
  line-height: 1.25;
  white-space: nowrap; overflow: hidden;
  /* No text-overflow:ellipsis — the marquee uses a fade-gradient mask
     and slides on hover instead. */
}
[dir="rtl"] .profile-name { font-size: 15px; }
/* Secondary-language name — always aligned to the page's start side
   (left in EN, right in AR). Inherits page direction explicitly so the
   bidi algorithm doesn't infer it from the Arabic content. */
.profile-name-sub {
  font-size: 11.5px; color: var(--ink-3);
  line-height: 1.3; margin-top: 1px;
  white-space: nowrap; overflow: hidden;
  /* No text-overflow:ellipsis — same marquee treatment as the primary. */
  font-family: var(--font-ar);
  direction: inherit;
  text-align: start;
}
.profile-meta {
  font-size: 11.5px; color: var(--ink-3);
  margin-top: 4px;
  line-height: 1.4;
  /* Allow up to 2 lines so longer "Institute - Branch" text reads cleanly. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
.profile-meta-label { color: var(--ink-4); }
[dir="rtl"] .profile-meta { font-size: 12.5px; }

/* Key-value info list */
.profile-info {
  margin: 0;
  padding: 6px 14px 4px;
  display: flex; flex-direction: column;
}
.profile-info-row {
  display: grid;
  grid-template-columns: 96px 1fr;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}
.profile-info-row:last-child { border-bottom: 0; }
.profile-info-key {
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-4);
  margin: 0;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
[dir="rtl"] .profile-info-key { font-size: 12px; letter-spacing: 0; text-transform: none; }
.profile-info-val {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  margin: 0;
  min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .profile-info-val { font-size: 13px; }
.profile-attempt-num {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--ink);
}
.profile-attempt-of { color: var(--ink-3); font-weight: 500; }

/* Inline value with copy button */
.profile-info-copy {
  display: flex; align-items: center; gap: 6px;
}
.profile-info-truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Inline "leading icon + label" pattern — used in the L4 student/
   examiner grid AND the saved-tests profile popover to put a small
   visual cue (gender silhouette, country flag) in front of the value
   text. The value cell becomes inline-flex so the icon and label sit
   on the same baseline. The leading-icon wrapper inherits the muted
   ink so SVG glyphs match the surrounding type colour; the leading-
   flag wrapper bumps font-size slightly so the emoji's visual cap
   matches the text's. */
.info-with-leading {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Override the parent cell's nowrap/ellipsis so the icon doesn't
     get clipped on narrow popovers — the label can still ellipsis
     via the inner span if needed. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
.info-leading-icon {
  display: inline-flex;
  align-items: center;
  color: var(--ink-3);
  flex-shrink: 0;
}
.info-leading-icon svg { display: block; }
.info-leading-flag {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  font-size: 14px;
  line-height: 1;
}
[dir="rtl"] .info-leading-flag { font-size: 15px; }
/* Phone numbers, emails, IDs — Latin/numeric content stays LTR even in
   Arabic mode so the leading "+" and spaces don't get re-ordered by bidi. */
.profile-info-val [dir="ltr"] { unicode-bidi: isolate; }
.profile-copy {
  width: 22px; height: 22px;
  display: inline-grid; place-items: center;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink-3);
  cursor: pointer;
  padding: 0;
  transition: all var(--t-fast);
}
.profile-copy:hover {
  background: var(--brand-tint);
  color: var(--brand-700);
  border-color: color-mix(in oklch, var(--brand-500) 30%, var(--line));
}
.profile-copy.is-copied {
  background: color-mix(in oklab, var(--ok, oklch(0.55 0.13 155)) 14%, transparent);
  border-color: color-mix(in oklab, var(--ok, oklch(0.55 0.13 155)) 35%, var(--line));
  color: var(--ok, oklch(0.55 0.13 155));
}

.profile-footer {
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line);
  display: flex;
}
.profile-cta {
  width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}

[dir="rtl"] .shell { direction: rtl; }

/* ─────────── Top nav ─────────── */
.topnav {
  height: 64px;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--line);
  display: flex;
  justify-content: center;
  position: sticky;
  top: 0;
  z-index: 50;
}
/* Inner wrapper caps the nav content at 1600 px (matches the page-content
   cap on `.main > *`) while the .topnav background continues edge-to-edge
   so wide viewports still feel anchored. Below 1600 px the cap has no
   effect — the content just fills the available width. */
.topnav-inner {
  width: 100%;
  max-width: 1600px;
  height: 100%;
  display: grid;
  grid-template-columns: 128px 1fr auto;
  align-items: center;
  padding-inline: 16px;
  gap: 16px;
}

.hamburger-btn {
  width: 36px; height: 36px;
  display: none;
  place-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ink);
  cursor: pointer;
  margin-inline-end: 4px;
}
.hamburger-btn:hover { background: var(--brand-tint); }

.topnav-brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
.tenant-icon {
  width: 128px;
  height: 32px;
  background-image: url('assets/PL-logo-light.svg?v=1777589280');
  background-repeat: no-repeat;
  background-size: contain;
  background-position: left center;
}
/* In Arabic the logo's frame sits on the right of the bar, so anchor the
   image to the inside (right) edge of its frame instead of the outer (left)
   edge — otherwise the logo image visually drifts away from the hamburger. */
[dir="rtl"] .tenant-icon { background-position: right center; }
[data-theme="dark"] .tenant-icon {
  background-image: url('assets/PL-logo-dark.svg?v=1777589280');
}
.brand-mark {
  width: 30px; height: 30px;
  border-radius: 9px;
  background: var(--brand-600);
  color: var(--brand-ink);
  display: grid; place-items: center;
  box-shadow: var(--sh-brand);
}
.brand-text {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.brand-name {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.brand-dot {
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--ink-4);
}
.brand-client {
  display: inline-flex; align-items: center; gap: 4px;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 500;
  transition: background var(--t-fast);
  min-width: 0;
}
.brand-client:hover { background: var(--brand-tint); color: var(--ink); }
.brand-client > span:first-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 140px;
}

.topnav-items {
  display: flex; align-items: center; gap: 2px;
  justify-content: center;
  flex-wrap: nowrap;
  overflow: hidden;
}
.nav-item {
  display: inline-flex; align-items: center; gap: 7px;
  background: transparent;
  border: 0;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  white-space: nowrap;
  text-decoration: none;          /* anchors render as nav buttons */
  font-family: inherit;
}
[dir="rtl"] .nav-item { font-size: 13.5px; }
.nav-item:hover { background: var(--brand-tint); color: var(--ink); }
.nav-item.is-active {
  background: var(--brand-tint-2);
  color: var(--brand-700);
}

/* Asset-based nav icons inherit currentColor via mask so they retint
   in both themes and follow active/hover states. */
.nav-icon-asset {
  width: 16px;
  height: 16px;
  display: inline-block;
  background-color: currentColor;
  flex-shrink: 0;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: 80%;
          mask-size: 80%;
  opacity: 0.95;
}
.nav-icon--dashboard { -webkit-mask-image: url('assets/nav_dashboard.svg?v=1777589280'); mask-image: url('assets/nav_dashboard.svg?v=1777589280'); }
.nav-icon--live_test { -webkit-mask-image: url('assets/nav_live_test.svg?v=1777589280'); mask-image: url('assets/nav_live_test.svg?v=1777589280'); }
.nav-icon--saved_tests { -webkit-mask-image: url('assets/nav_saved_tests.svg?v=1777589280'); mask-image: url('assets/nav_saved_tests.svg?v=1777589280'); }
.nav-icon--training { -webkit-mask-image: url('assets/nav_training.svg?v=1777589280'); mask-image: url('assets/nav_training.svg?v=1777589280'); }
.nav-icon--assessment { -webkit-mask-image: url('assets/nav_assessment.svg?v=1777589280'); mask-image: url('assets/nav_assessment.svg?v=1777589280'); }
.nav-icon--requests { -webkit-mask-image: url('assets/nav_requests.svg?v=1777589280'); mask-image: url('assets/nav_requests.svg?v=1777589280'); }
.nav-icon--reports { -webkit-mask-image: url('assets/nav_reports.svg?v=1777589280'); mask-image: url('assets/nav_reports.svg?v=1777589280'); }
.nav-icon--setting { -webkit-mask-image: url('assets/nav_setting.svg?v=1777589280'); mask-image: url('assets/nav_setting.svg?v=1777589280'); }
[data-theme="dark"] .nav-item.is-active { color: var(--brand-700); }
.nav-item svg { opacity: 0.85; }

.topnav-tools {
  display: flex; align-items: center; gap: 6px;
}

.icon-btn {
  width: 32px; height: 32px;
  display: inline-grid; place-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  cursor: pointer;
  position: relative;
  transition: background var(--t-fast), color var(--t-fast);
}
.icon-btn:hover { background: var(--brand-tint); color: var(--ink); }

.dot-indicator {
  position: absolute; top: 6px; right: 7px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--err);
  border: 2px solid var(--bg-raised);
}

.avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--brand-200);
  color: var(--brand-900);
  display: inline-grid; place-items: center;
  font-size: 11px; font-weight: 700;
  letter-spacing: .03em;
  cursor: pointer;
  margin-inline-start: 4px;
  border: 0;
  font-family: inherit;
  transition: box-shadow var(--t-fast);
}
.avatar:hover { box-shadow: 0 0 0 3px var(--brand-tint); }

/* Notifications */
.notif-wrap { position: relative; }
.notif-panel {
  position: absolute;
  top: calc(100% + 10px);
  inset-inline-end: -8px;
  width: 440px;
  max-width: calc(100vw - 24px);
  max-height: 560px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
  z-index: 150;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: dropdown-in 140ms ease-out;
}
.notif-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 14px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.notif-head-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}
.notif-head-counts {
  font-size: 11.5px;
  color: var(--ink-3);
  margin-top: 2px;
}
.notif-sep { color: var(--ink-4); padding: 0 2px; }
.notif-head-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
.notif-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
  padding: 4px 4px 4px 10px;
  border-radius: 999px;
  transition: background var(--t-fast);
}
[dir="rtl"] .notif-toggle { padding: 4px 10px 4px 4px; }
.notif-toggle:hover { background: var(--brand-tint); color: var(--ink); }
.notif-toggle input { display: none; }
.notif-switch {
  width: 32px; height: 18px;
  border-radius: 999px;
  background: var(--line-strong);
  position: relative;
  transition: background var(--t-fast);
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 10%, transparent);
}
.notif-switch::after {
  content: '';
  position: absolute;
  top: 2px; inset-inline-start: 2px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--bg-raised);
  box-shadow: 0 1px 2px rgba(0,0,0,0.18);
  transition: inset-inline-start 160ms cubic-bezier(.4,0,.2,1);
}
.notif-toggle input:checked + .notif-switch {
  background: var(--brand-500);
  box-shadow: inset 0 0 0 1px var(--brand-600);
}
.notif-toggle input:checked + .notif-switch::after { inset-inline-start: 16px; }

.notif-menu-wrap { position: relative; }
.notif-menu-btn { width: 28px; height: 28px; }
.notif-actions-menu {
  position: absolute;
  top: calc(100% + 4px);
  inset-inline-end: 0;
  min-width: 220px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  padding: 4px;
  z-index: 160;
  animation: dropdown-in 120ms ease-out;
}
.notif-actions-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  white-space: nowrap;
}
.notif-actions-item:hover { background: var(--brand-tint); }
.notif-actions-item svg { color: var(--ink-3); }

.notif-list {
  overflow-y: auto;
  overflow-x: hidden;
  flex: 1;
  scrollbar-gutter: stable;
}
.notif-empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--ink-3);
  font-size: 12.5px;
}
.notif-item {
  position: relative;
  padding: 12px 40px 12px 14px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  cursor: pointer;
  transition: background var(--t-fast);
  overflow: hidden;
}
[dir="rtl"] .notif-item { padding: 12px 14px 12px 40px; }
.notif-item:hover { background: var(--brand-tint); }
.notif-item.is-new { background: color-mix(in srgb, var(--brand-500) 8%, var(--bg-raised)); }
.notif-item.is-new:hover { background: color-mix(in srgb, var(--brand-500) 14%, var(--bg-raised)); }

.notif-type {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.notif-type svg { color: var(--ink-3); flex-shrink: 0; }
.notif-type > span:first-of-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notif-new-pill {
  margin-inline-start: 4px;
  background: var(--brand-600);
  color: var(--brand-ink);
  font-size: 10px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  letter-spacing: 0.02em;
  flex-shrink: 0;
}
.notif-body {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 6px;
}
.notif-avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  margin-top: 1px;
}
.notif-text {
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.notif-sender { font-weight: 600; }
.notif-text b { font-weight: 600; }
.notif-time {
  margin-top: 6px;
  font-size: 11px;
  color: var(--ink-3);
}

/* Small unread dot, top-right. Hidden for read items unless hovered. */
.notif-dot {
  position: absolute;
  top: 12px;
  inset-inline-end: 12px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: grid;
  place-items: center;
  opacity: 0;
  transition: opacity 120ms ease;
}
.notif-dot::before {
  content: '';
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid var(--line-strong);
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.notif-item:hover .notif-dot { opacity: 1; }
.notif-dot.is-unread { opacity: 1; }
.notif-dot.is-unread::before {
  background: var(--brand-500);
  border-color: var(--brand-500);
}
.notif-dot:hover::before { transform: scale(1.2); border-color: var(--brand-500); }

/* Profile dropdown */
.profile-wrap { position: relative; }
.profile-menu {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  min-width: 280px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-lg);
  padding: 12px 6px 6px;
  z-index: 120;
  animation: dropdown-in 120ms ease-out;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.profile-menu-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
}
.profile-menu-item:hover { background: var(--brand-tint); }
.profile-menu-item svg { color: var(--ink-3); }
.profile-menu-item.is-destructive { color: var(--err); }
.profile-menu-item.is-destructive svg { color: var(--err); }
.profile-menu-item.is-destructive:hover { background: color-mix(in oklab, var(--err) 10%, var(--bg-raised)); }
.profile-divider {
  height: 1px;
  background: var(--line);
  margin: 4px 2px;
}
.profile-section { padding: 6px 6px 4px; }
.lang-icon {
  width: 16px;
  height: 14px;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  opacity: 0.85;
}
.lang-icon--en {
  -webkit-mask-image: url('assets/lang_english.svg?v=1777589280');
          mask-image: url('assets/lang_english.svg?v=1777589280');
}
.lang-icon--ar {
  -webkit-mask-image: url('assets/lang_arabic.svg?v=1777589280');
          mask-image: url('assets/lang_arabic.svg?v=1777589280');
}
.profile-section-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin-bottom: 6px;
  padding-inline-start: 4px;
}

/* ─────────── Client Rail (left) — floating overlay ─────────── */
.client-rail {
  position: fixed;
  top: 64px;
  bottom: 0;
  inset-inline-start: 0;
  width: 296px;
  max-width: 92vw;
  z-index: 40;
  background: var(--bg-raised);
  border-right: 1px solid var(--line);
  box-shadow: var(--sh-md);
  padding: 16px 12px;
  overflow-y: auto;
  overflow-x: hidden;
  transform: translateX(0);
  transition: transform var(--t-med), box-shadow var(--t-fast);
  pointer-events: auto;
}
[dir="rtl"] .client-rail { border-right: 0; border-left: 1px solid var(--line); }

.rail-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px 4px 12px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.rail-title {
  font-size: 13px; font-weight: 600; color: var(--ink);
}
[dir="rtl"] .rail-title { font-size: 14px; }
.rail-close {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  background: transparent; border: 0;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  cursor: pointer;
  padding: 0;
}
.rail-close:hover { background: var(--bg-sunken); color: var(--ink); }

.rail-head { padding: 4px 8px 12px; }
.rail-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--ink-4);
  font-weight: 600;
}
.rail-list { display: flex; flex-direction: column; gap: 4px; }

.client-card {
  position: relative;
  width: 100%;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  cursor: pointer;
  text-align: start;
  transition: all var(--t-fast);
}
.client-card:hover {
  background: var(--brand-tint);
}
.client-card.is-active {
  background: var(--brand-tint-2);
  border-color: color-mix(in oklch, var(--brand-500) 35%, var(--line));
  box-shadow: var(--sh-sm), inset 0 0 0 1px color-mix(in oklch, var(--brand-500) 20%, transparent);
}
.client-card.is-active .client-name { color: var(--brand-700); }
[data-theme="dark"] .client-card.is-active .client-name { color: var(--brand-700); }
.client-card.is-active .client-sub { color: var(--brand-700); opacity: 0.85; }
[data-theme="dark"] .client-card.is-active .client-sub { color: var(--brand-700); }

/* Favorite star — hidden by default, visible on hover or when pinned */
.client-fav {
  position: absolute;
  top: 5px;
  inset-inline-end: 5px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--ink-4);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
  padding: 0;
  z-index: 2;
}
.client-card:hover .client-fav,
.client-card:focus-within .client-fav { opacity: 1; }
.client-fav:hover { background: var(--bg-sunken); color: oklch(0.72 0.18 82); }
.client-fav.is-on {
  opacity: 1;
  color: oklch(0.72 0.18 82);
}
.client-fav.is-on:hover { color: oklch(0.66 0.19 82); }
[data-theme="dark"] .client-fav.is-on { color: oklch(0.82 0.17 85); }

.client-mark {
  width: 40px; height: 40px;
  flex-shrink: 0;
  border-radius: 11px;
  display: grid; place-items: center;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .02em;
  color: white;
  position: relative;
  overflow: hidden;
}
.client-mark::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,0.18), rgba(0,0,0,0.08));
}
.client-mark > span { position: relative; z-index: 1; }

/* Tonal mark backgrounds — derived from each client's brand for variety */
.client-mark[data-tone="A"] { background: oklch(0.55 0.16 240); }
.client-mark[data-tone="B"] { background: oklch(0.60 0.17 18); }
.client-mark[data-tone="C"] { background: oklch(0.55 0.13 160); }
.client-mark[data-tone="D"] { background: oklch(0.50 0.16 285); }
.client-mark[data-tone="E"] { background: oklch(0.55 0.14 205); }
.client-mark[data-tone="F"] { background: oklch(0.65 0.14 45); }
.client-mark[data-tone="G"] { background: oklch(0.58 0.14 325); }
.client-mark[data-tone="H"] { background: oklch(0.55 0.12 185); }
.client-mark[data-tone="I"] { background: oklch(0.55 0.11 120); }
.client-mark[data-tone="J"] { background: oklch(0.55 0.16 255); }

.client-body {
  min-width: 0;
  flex: 1;
  display: flex; flex-direction: column;
  gap: 1px;
}
.client-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.3;
}
.client-sub {
  font-size: 11px;
  letter-spacing: 0;
  color: var(--ink-3);
  font-weight: 500;
  margin-top: 2px;
}
.client-active-bar {
  position: absolute;
  inset-inline-start: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 22px;
  border-radius: 0 4px 4px 0;
  background: var(--brand-600);
}
[dir="rtl"] .client-active-bar { border-radius: 4px 0 0 4px; }

/* ─────────── Main column ─────────── */
.main {
  padding: 16px 16px 40px;
  display: flex; flex-direction: column;
  gap: 12px;
  min-width: 0;
  /* Page canvas: a neutral sunken surface with a whisper of brand so the
     cards on top (topnav, table, filter-bar — all on --bg-raised) read as
     elevated. Keeps the brand color reserved for interactive states. */
  background: color-mix(in srgb, var(--brand-500) 1%, var(--bg-sunken));
}
[data-theme="dark"] .main {
  background: color-mix(in srgb, var(--brand-500) 2%, var(--bg-sunken));
}
/* Cap content children at a sensible max-width on ultra-wide monitors so
   tables/cards don't stretch awkwardly. The .main background still spans
   edge-to-edge; only the content children are centered + capped. Below
   1600px the cap has no effect — content fills the available width. */
.main > * {
  width: 100%;
  max-width: 1600px;
  align-self: center;
}
.page-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1.2;
}

/* ─────────── FILTER BAR ─────────── */
.filter-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-xs);
}

.filter-search {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-sunken);
  border-radius: var(--r-md);
  padding: 7px 10px;
  /* Trimmed 24px (was 280) so the pills get more horizontal room
     on the same row before they have to wrap or shrink labels. */
  flex: 0 0 256px;
  color: var(--ink-3);
  transition: all var(--t-fast);
}
.filter-search:focus-within {
  background: var(--bg-raised);
  box-shadow: 0 0 0 1px var(--line-strong), 0 0 0 4px var(--ring);
}
.filter-search input {
  border: 0; background: transparent; outline: 0;
  flex: 1; min-width: 0;
  font-size: 13px;
  color: var(--ink);
  font-family: inherit;
}
.filter-search input::placeholder { color: var(--ink-3); }
.kbd {
  font-size: 10px;
  padding: 2px 5px;
  border-radius: 4px;
  background: var(--bg-raised);
  color: var(--ink-3);
  border: 1px solid var(--line);
  letter-spacing: 0;
}

.filter-pills {
  display: flex; align-items: center; gap: 6px;
  flex: 1; min-width: 0;
  flex-wrap: wrap;
}
.filter-pill-wrap { position: relative; flex-shrink: 0; }
.filter-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 11px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: all var(--t-fast);
  white-space: nowrap;
  flex-shrink: 0;
  font-family: inherit;
  max-width: 220px;
}
.filter-pill:hover {
  background: var(--bg-sunken);
  border-color: var(--line-strong);
  color: var(--ink);
}
.filter-pill.is-active {
  background: var(--brand-tint-2);
  border-color: color-mix(in oklch, var(--brand-500) 30%, var(--line));
  color: var(--brand-700);
}
[data-theme="dark"] .filter-pill.is-active { color: var(--brand-700); }
.filter-pill.is-open {
  background: var(--bg-sunken);
  border-color: var(--line-strong);
}
.filter-pill.is-active.is-open { background: var(--brand-tint-2); }
.filter-pill svg { opacity: 0.7; flex-shrink: 0; }
.filter-pill-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Optional axis label that prefixes the pill value (e.g. "Last run"
   in front of "This week"). Sits between the leading icon and the
   value, separated by an em-dot, in muted ink so the value still
   reads as the dominant piece. */
.filter-pill-prefix {
  color: var(--ink-3);
  white-space: nowrap;
  flex-shrink: 0;
}
.filter-pill-prefix::after {
  content: ' · ';
  color: var(--ink-4);
}
/* Time filter pill — when a custom range is shown the label is much longer
   (e.g. "1 Mar 2026 → 24 Apr 2026" / Arabic month names), so allow it to
   grow to fit the full text rather than truncate. */
.filter-pill.filter-pill--wide { max-width: none; }
.filter-pill.filter-pill--wide .filter-pill-label { overflow: visible; text-overflow: clip; }

/* Standard filter Clear button — used on every page that has a
   filter bar (Saved Tests, Reports, Custom Reports). Inline-text
   style, no border, subtle underline. Picked because it doesn't
   compete with the actual filter pills (which carry the heavier
   visual weight) — Clear is a destructive secondary action and the
   light treatment keeps it discoverable without making it loud. */
.filter-reset {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  flex-shrink: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklab, var(--ink-3) 50%, transparent);
  transition: color var(--t-fast), text-decoration-color var(--t-fast);
}
.filter-reset:hover {
  color: var(--err);
  text-decoration-color: var(--err);
}
/* `.filter-reset--inline` kept as a no-op alias — older call sites
   pass it explicitly. The base class is now the inline style. */
.filter-reset--inline { /* no-op; merged into .filter-reset */ }

.filter-search-clear {
  background: transparent;
  border: 0;
  padding: 2px;
  cursor: pointer;
  color: var(--ink-3);
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
}
.filter-search-clear:hover { color: var(--ink); background: var(--bg-raised); }
.filter-search-clear.is-hidden { visibility: hidden; pointer-events: none; }

/* Filter dropdown menu */
.filter-menu {
  position: absolute;
  top: calc(100% + 6px);
  inset-inline-start: 0;
  z-index: 40;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  min-width: 200px;
  max-width: 280px;
  max-height: 320px;
  overflow-y: auto;
  padding: 4px;
  animation: dropdown-in 120ms ease-out;
}
@keyframes dropdown-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
.filter-menu-item {
  display: flex; align-items: center; justify-content: flex-start; gap: 8px;
  width: 100%;
  padding: 7px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  white-space: nowrap;
}
.filter-menu-item > span:not(.multi-check):not(.examiner-opt) { flex: 1; }
.filter-menu-item:hover { background: var(--brand-tint); }
.filter-menu-item.is-on {
  color: var(--brand-700);
  background: var(--brand-tint);
}
[data-theme="dark"] .filter-menu-item.is-on { color: var(--brand-700); }
.filter-menu-item svg { color: var(--brand-600); opacity: 1; }

/* Multi-select rows: checkbox replaces brand-tint background */
.filter-menu-item.is-multi { color: var(--ink); background: transparent; }
.filter-menu-item.is-multi:hover { background: var(--brand-tint); }
.filter-menu-item.is-multi.is-on { color: var(--ink); background: transparent; }
.filter-menu-item.is-multi.is-on:hover { background: var(--brand-tint); }
.multi-check {
  width: 16px; height: 16px;
  border-radius: 4px;
  border: 1px solid var(--line-strong);
  background: var(--bg-raised);
  display: inline-grid; place-items: center;
  flex-shrink: 0;
  color: var(--brand-ink);
  transition: all var(--t-fast);
}
.multi-check.is-on {
  background: var(--brand-600);
  border-color: var(--brand-600);
}
.multi-check svg { color: inherit; opacity: 1; }
.multi-check.is-on svg { color: var(--brand-ink); }

.filter-menu-search {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  margin: 2px 2px 6px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink-3);
}
.filter-menu-search:focus-within {
  border-color: var(--line-strong);
  box-shadow: 0 0 0 3px var(--ring);
}
.filter-menu-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink);
}
.filter-menu-search input::placeholder { color: var(--ink-3); }
.filter-menu-empty {
  padding: 14px 10px;
  text-align: center;
  font-size: 12px;
  color: var(--ink-3);
}

.filter-menu-clear {
  width: 100%;
  padding: 6px 10px;
  background: transparent;
  border: 0;
  text-align: start;
  font-family: inherit;
  font-size: 11.5px;
  color: var(--ink-3);
  cursor: pointer;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
  border-radius: 0;
}
.filter-menu-clear:hover { color: var(--err); background: var(--bg-sunken); }

/* Examiner option with photo */
.examiner-opt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.examiner-opt-photo {
  width: 22px; height: 22px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  flex-shrink: 0;
}

/* RecipientsPicker rows — 22px circular icon-mark + name + role/count
   subtitle. The mark is brand-tinted for roles (groups) and neutral
   for individual users, so the user can scan kind at a glance
   without reading every label. */
.recipient-opt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.recipient-opt-mark {
  width: 22px; height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border: 1px solid var(--line);
}
.recipient-opt-mark.is-user {
  background: var(--bg-sunken);
  color: var(--ink-3);
}
.recipient-opt-mark.is-role {
  background: var(--brand-tint);
  border-color: color-mix(in oklab, var(--brand-500) 30%, var(--line));
  color: var(--brand-700);
}
.recipient-opt-text {
  display: flex;
  flex-direction: column;
  gap: 0;
  min-width: 0;
}
.recipient-opt-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.recipient-opt-sub {
  font-size: 11px;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Time filter menu — presets + custom range */
.time-menu { min-width: 260px; }
/* Calendar footer hint + error (still used by the calendar's footer) */
.custom-range-error { font-size: 11px; color: var(--err); }
.custom-range-hint { font-size: 12px; color: var(--ink-3); }
[dir="rtl"] .custom-range-hint { font-size: 13px; }
.cal-actions .custom-range-hint { font-size: 12.5px; }
[dir="rtl"] .cal-actions .custom-range-hint { font-size: 13px; }

/* ── Time filter dropdown: presets list (default) + optional calendar pane ──
   The presets rail keeps the SAME size in both list mode (default) and combo
   mode (when the calendar is showing), so the sidebar doesn't visibly
   resize when the user opens the calendar. */
.time-menu .combo-presets {
  display: flex; flex-direction: column;
  gap: 2px;
}
.combo-preset-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
  width: 100%;
  padding: 7px 9px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-size: 12px;
  color: var(--ink);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  white-space: nowrap;
}
.combo-preset-item:hover { background: var(--brand-tint); }
.combo-preset-item.is-on { background: var(--brand-tint); color: var(--brand-700); font-weight: 600; }
[data-theme="dark"] .combo-preset-item.is-on { color: var(--brand-700); }
.combo-preset-item svg { color: var(--brand-600); }
.combo-preset-item.is-custom > svg:last-child { color: var(--ink-3); opacity: 0.7; }
[dir="rtl"] .combo-preset-item { font-size: 13px; }

/* List mode (no calendar) — narrow dropdown matching the combo sidebar width */
.time-menu:not(.time-menu--combo) {
  width: 168px;
  min-width: 168px;
  max-width: 168px;
  max-height: none;
  padding: 6px;
  overflow: visible;
}

/* Combo mode (calendar visible) — split layout */
.time-menu--combo {
  width: 600px;
  min-width: 600px;
  max-width: none;
  max-height: none;
  padding: 0;
  display: grid;
  grid-template-columns: 168px 1fr;
  overflow: visible;
}
.time-menu--combo .combo-presets {
  padding: 6px;
  border-inline-end: 1px solid var(--line);
  background: transparent;
}

.combo-calendar {
  display: flex; flex-direction: column;
  background: var(--bg-raised);
}
.cal-nav-btn {
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink-2); cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.cal-nav-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--ink); border-color: var(--line-strong); }
.cal-nav-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.cal-nav-spacer { width: 24px; height: 24px; flex-shrink: 0; }

.cal-body {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0;
}
.cal-month { padding: 8px 10px 10px; }
.cal-month + .cal-month { border-inline-start: 1px solid var(--line); }
.cal-month-title {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
  margin-bottom: 6px;
}
.cal-month-label {
  flex: 1;
  text-align: center;
  font-size: 12px; font-weight: 600; color: var(--ink);
  letter-spacing: 0.01em;
}
[dir="rtl"] .cal-month-label { font-size: 13px; letter-spacing: 0; }

.cal-weekdays {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 0;
  margin-bottom: 2px;
}
.cal-weekday {
  text-align: center;
  font-size: 9.5px; font-weight: 600;
  color: var(--ink-4);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 0;
}
[dir="rtl"] .cal-weekday { font-size: 11px; letter-spacing: 0; }

.cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 0;
}
.cal-grid .cal-day {
  position: relative;
  height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0;
  font-family: inherit;
  font-size: 11.5px;
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  border-radius: 0;
  overflow: visible;
}
.cal-grid .cal-day > span {
  position: relative; z-index: 2;
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  transition: background-color 80ms ease, color 80ms ease;
}
.cal-grid .cal-day.is-blank { pointer-events: none; cursor: default; }
.cal-grid .cal-day.is-disabled { color: var(--ink-4); opacity: 0.45; cursor: not-allowed; }

/* Hover for non-selected days — show a soft pill on the inner span */
.cal-grid .cal-day:not(.is-disabled):not(.is-blank):not(.in-band):hover > span {
  background: var(--brand-tint);
  color: var(--ink);
}

/* Today indicator — brand-colored ring on the inner pill (when not selected endpoint) */
.cal-grid .cal-day.is-today:not(.is-start):not(.is-end) > span {
  box-shadow: inset 0 0 0 1.5px var(--brand);
  color: var(--brand-700);
  font-weight: 600;
}

/* ── Range band ──
   Painted via a ::before that fills the cell horizontally. We toggle width
   per cell-class so the band visually flows across the row, with rounded
   ends only at the actual range edges or row boundaries. */
.cal-grid .cal-day.in-band::before {
  content: '';
  position: absolute;
  top: 2px; bottom: 2px;
  left: 0; right: 0;
  background: var(--brand-tint);
  z-index: 1;
  pointer-events: none;
}
[data-theme="dark"] .cal-grid .cal-day.in-band::before {
  background: color-mix(in srgb, var(--brand) 22%, transparent);
}
/* Half-bands for endpoints when there is a range */
.cal-grid .cal-day.band-trail-half::before { left: 50%; right: 0; }
[dir="rtl"] .cal-grid .cal-day.band-trail-half::before { left: 0; right: 50%; }
.cal-grid .cal-day.band-lead-half::before { left: 0; right: 50%; }
[dir="rtl"] .cal-grid .cal-day.band-lead-half::before { left: 50%; right: 0; }
/* Single-point selection has no band */
.cal-grid .cal-day.is-single-point::before { display: none; }

/* Row-edge rounding on the band */
.cal-grid .cal-day.round-lead::before {
  border-start-start-radius: 14px;
  border-end-start-radius: 14px;
}
.cal-grid .cal-day.round-trail::before {
  border-start-end-radius: 14px;
  border-end-end-radius: 14px;
}

/* Endpoints — full-cell CTA-style pill on top of the band */
.cal-grid .cal-day.is-start > span,
.cal-grid .cal-day.is-end > span {
  position: absolute;
  inset: 2px 3px;
  width: auto; height: auto;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-600);
  color: var(--brand-ink);
  font-weight: 700;
  font-size: 11.5px;
  border-radius: 11px;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--brand-600) 28%, transparent);
  z-index: 3;
}
.cal-grid .cal-day.is-start:hover > span,
.cal-grid .cal-day.is-end:hover > span {
  background: var(--brand-700, var(--brand-600));
}

/* In-range mids: keep base text dark/legible on tint */
.cal-grid .cal-day.is-mid { color: var(--ink); }

/* Calendar footer */
.cal-footer {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 8px 12px;
  border-top: 1px solid var(--line);
  background: var(--bg-sunken);
}
.cal-summary {
  font-size: 12px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; gap: 2px;
}
.cal-summary-hint { color: var(--ink-3); font-style: italic; font-size: 11.5px; }
.cal-summary-strong { color: var(--ink); font-weight: 600; }
[dir="rtl"] .cal-summary { font-size: 12.5px; }
[dir="rtl"] .cal-summary-hint { font-style: normal; font-size: 12.5px; }
.cal-actions { display: flex; align-items: center; gap: 8px; }

/* Combo responsive
   - under 660: presets become a horizontal pill bar above the calendar (still two months side-by-side if width allows)
   - under 520: single-month calendar
   - under 420: tighten footer to two rows */
@media (max-width: 659px) {
  .time-menu--combo {
    width: calc(100vw - 24px);
    min-width: 0;
    max-width: 560px;
    grid-template-columns: 1fr;
  }
  .time-menu--combo .combo-presets {
    flex-direction: row; flex-wrap: wrap;
    border-inline-end: 0;
    border-bottom: 1px solid var(--line);
    padding: 6px;
    gap: 4px;
  }
  .time-menu--combo .combo-preset-item {
    flex: 1 1 auto; justify-content: center;
    padding: 6px 10px;
  }
  .time-menu--combo .combo-preset-item.is-custom > svg:last-child { display: none; }
}
@media (max-width: 519px) {
  .cal-body { grid-template-columns: 1fr; }
  .cal-month + .cal-month { border-inline-start: 0; border-top: 1px solid var(--line); }
}
@media (max-width: 419px) {
  .cal-footer {
    flex-wrap: wrap;
    align-items: stretch;
  }
  .cal-actions {
    width: 100%;
    justify-content: space-between;
  }

  /* Bottom-sheet style on tiny viewports (e.g. 320–375 phones).
     The JS positioning otherwise anchors the popover to the pill's
     bottom-edge, which on a narrow phone pushes the calendar +
     footer past the viewport bottom and hides the Apply button.
     Anchoring the menu to the viewport bottom and letting the
     calendar body scroll internally keeps Apply always visible
     and reachable. `!important` is needed to override the inline
     `top/left` set by the React popover-positioning effect. */
  .time-menu--combo {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    max-height: 92vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    display: flex;
    flex-direction: column;
    grid-template-columns: none;
    overflow: hidden;
  }
  .time-menu--combo .combo-presets { flex-shrink: 0; }
  .time-menu--combo .combo-calendar {
    flex: 1;
    min-height: 0;
  }
  /* Calendar body becomes the internal scroll container so the
     footer (Apply + summary) stays pinned to the sheet's bottom
     edge regardless of how tall the month grid is. */
  .time-menu--combo .cal-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
  .time-menu--combo .cal-footer {
    flex-shrink: 0;
    box-shadow: 0 -1px 0 var(--line);
  }
}

.filter-actions {
  display: flex; align-items: center; gap: 6px;
}

/* Density icon toggle — compact one-click switch between comfort/dense */
.density-icon-btn {
  width: 32px; height: 32px;
  display: inline-grid; place-items: center;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  cursor: pointer;
  padding: 0;
  transition: all var(--t-fast);
  flex-shrink: 0;
}
.density-icon-btn:hover {
  background: var(--bg-sunken);
  border-color: var(--line-strong);
  color: var(--ink);
}

/* Buttons */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: all var(--t-fast);
  white-space: nowrap;
}
.btn-ghost { background: transparent; color: var(--ink-2); }
.btn-ghost:hover { background: var(--brand-tint); color: var(--ink); }
.btn-primary {
  background: var(--brand-600);
  color: var(--brand-ink);
  box-shadow: var(--sh-xs);
}
.btn-primary:hover { background: var(--brand-700); }
.btn-sm { padding: 5px 9px; font-size: 12px; }

/* ─────────── DATA TABLE ─────────── */
.table-card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  /* Clip horizontally so rounded corners crop side overflow.
     `overflow-y: visible` is intended to let per-cell tooltips rise
     above row 1, but per CSS spec, mixing clip + visible computes
     both axes to clip — so without `overflow-clip-margin` the row-1
     edit/activate/duplicate/schedule tooltips were being clipped at
     the card's top edge. Margin of 32px gives descendants room to
     paint that far outside the clip box. */
  overflow-x: clip;
  overflow-y: visible;
  overflow-clip-margin: 32px;
  box-shadow: var(--sh-xs);
}

.table-scroll {
  overflow-x: clip;
  overflow-y: visible;
  /* Same trick as `.rpt-table-wrap`: per CSS spec, mixing clip + visible
     computes both axes to clip, which means tooltips on action buttons
     in the FIRST row of the table get clipped at the wrapper's top edge.
     `overflow-clip-margin` lets descendants paint up to 32px outside
     the clip box — enough headroom for the row-1 edit / activate /
     duplicate / schedule tooltips to show in full. */
  overflow-clip-margin: 32px;
}

.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 12.5px;
  font-family: var(--font-ui);
  table-layout: fixed;
}
.data-table td,
.data-table .mono,
.data-table .cell-id,
.data-table .cell-muted,
.data-table .student-name .cell-link,
.data-table .examiner-cell .cell-link,
.data-table .vehicle-tag,
.data-table .vehicle-tag .mono {
  font-family: var(--font-ui);
  font-size: 12.5px;
}
/* Arabic UI needs slightly larger text to feel balanced against Latin sizing. */
[dir="rtl"] .th-btn { font-size: 11.5px; letter-spacing: 0.02em; }
[dir="rtl"] .table-count {
  font-size: 12.5px;
  letter-spacing: 0;
  font-family: var(--font-ui);
  color: var(--ink-3);
}
[dir="rtl"] .page-size { font-size: 12.5px; }
[dir="rtl"] .page-btn { font-size: 13px; }
[dir="rtl"] .filter-menu-item { font-size: 13px; }
[dir="rtl"] .filter-pill { font-size: 13px; }
[dir="rtl"] .page-size select { font-size: 13px; }
.data-table .mono { font-variant-numeric: tabular-nums; letter-spacing: 0; }
.data-table td { font-weight: 400; }
.data-table .cell-id,
.data-table .student-name .cell-link { font-weight: 500; }

.data-table thead th {
  position: sticky; top: 64px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--line);
  text-align: start;
  padding: 0;
  font-weight: 500;
  z-index: 10;
  height: 40px;
  transition: box-shadow 120ms ease;
}
.density-cozy .data-table thead th,
.density-compact .data-table thead th { height: 40px; }
.data-table thead.is-stuck th {
  box-shadow: 0 4px 8px -6px color-mix(in oklab, var(--ink) 28%, transparent);
  height: 34px;
}
.data-table thead.is-stuck .th-btn {
  padding: 4px 8px;
  font-size: 10px;
}
.th-btn {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 10px 6px;
  background: transparent;
  border: 0;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--ink-3);
  cursor: pointer;
  font-family: inherit;
  width: 100%;
  white-space: nowrap;
}
.th-btn:hover { color: var(--ink); }
/* Direct-child SVGs only (legacy headers without an icon slot). The new
   .th-icon slot manages its own SVG opacities — see the .th-icon rules
   below. */
.th-btn > svg { opacity: 0.6; }
.th-btn.is-sorted { color: var(--ink); }
.th-btn.is-sorted > svg { opacity: 1; color: var(--brand-600); }
/* When the active-sort arrow is showing, give it the brand color (the slot
   logic already handles its visibility). */
.th-btn.is-sorted .th-icon-active { color: var(--brand-600); }

[style*="text-align: center"] .th-btn { justify-content: center; }

.data-table tbody {
  --row-line: color-mix(in srgb, var(--ink) 10%, transparent);
}
.data-table tbody tr {
  transition: background var(--t-fast);
}
/* Use box-shadow on the cell for reliable row lines under border-collapse: separate */
.data-table tbody td { box-shadow: inset 0 -1px 0 0 var(--row-line); }
.data-table tbody tr:first-child td { box-shadow: inset 0 1px 0 0 var(--row-line), inset 0 -1px 0 0 var(--row-line); }
.data-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--ink) 2.5%, transparent); }
.data-table tbody tr:hover,
.data-table tbody tr:nth-child(even):hover {
  background: var(--brand-tint);
}

.data-table td {
  padding: 6px 6px;
  vertical-align: middle;
  color: var(--ink);
  white-space: nowrap;
  box-sizing: border-box;
  overflow: hidden;
}
.data-table td:first-child { padding-left: 14px; }
.data-table td:last-child { padding-right: 14px; }
.data-table thead th:first-child .th-btn { padding-left: 14px; }
.data-table thead th:last-child .th-btn { padding-right: 14px; }
.density-compact .data-table tbody td,
.density-compact .data-table tbody tr { height: 32px; }
.density-cozy .data-table tbody td,
.density-cozy .data-table tbody tr { height: 60px; }
/* Tighter cell + header padding in compact density. The default 6px
   vertical / 14px horizontal padding wastes row height on a 32px row
   that no longer carries an avatar. */
.density-compact .data-table tbody td { padding: 4px 8px; }
.density-compact .data-table tbody td:first-child { padding-left: 12px; }
.density-compact .data-table tbody td:last-child  { padding-right: 12px; }
.density-compact .data-table thead th { height: 34px; }
.density-compact .data-table thead .th-btn { padding: 6px 8px; font-size: 10px; }
.density-compact .data-table thead th:first-child .th-btn { padding-left: 12px; }
.density-compact .data-table thead th:last-child  .th-btn { padding-right: 12px; }
/* Avatar isn't rendered in compact mode (per DataTable JSX), but
   collapse the gap on the cell wrappers anyway so a future avatar
   reintroduction doesn't accidentally widen the row. */
.density-compact .student-cell,
.density-compact .examiner-cell { gap: 0; }
.density-compact .data-table tbody td,
.density-compact .data-table .student-name .cell-link,
.density-compact .data-table .examiner-cell .cell-link { font-size: 12px; }

.cell-id {
  font-size: 12px;
  color: var(--ink);
  font-weight: 500;
}
.cell-muted { color: var(--ink-3); }
.cell-empty { color: var(--ink-4); }

/* Student cell */
.student-cell {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.student-ava {
  width: 26px; height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
}
.density-cozy .student-ava { width: 34px; height: 34px; }
.student-name { min-width: 0; }
.student-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* Cell link — a button that looks like the previous <a>. Used for student
   and examiner names so they stay clickable but don't act as phantom links. */
.cell-link {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: start;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cell-link:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 2px; }
.student-cell .student-ava,
.examiner-cell .examiner-ava { cursor: pointer; transition: box-shadow var(--t-fast); }
.student-cell .student-ava:hover,
.examiner-cell .examiner-ava:hover { box-shadow: 0 0 0 2px var(--brand-tint); }
.student-name .cell-link {
  color: var(--ink);
  text-decoration: none;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  /* No text-overflow:ellipsis here — the new marquee uses a fade-gradient
     mask instead so the inner span can transform-slide past the edge. */
  display: block;
  max-width: 150px;
}
.student-name .cell-link { max-width: 240px; }
.density-compact .student-name .cell-link { max-width: 220px; }

/* Slide-on-hover for truncated names is driven by JS (it measures
   the exact overflow and sets text-indent inline) so the animation
   stops at the last letter and only runs when the name doesn't fit. */
.student-name .cell-link,
.examiner-cell .cell-link,
.student-name-sub {
  text-indent: 0;
}
.student-name .cell-link:hover { color: var(--brand-700); }
[data-theme="dark"] .student-name .cell-link:hover { color: var(--brand-700); }
.student-name-sub {
  display: block;
  font-size: 10.5px;
  color: var(--ink-3);
  font-weight: 400;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  /* No text-overflow:ellipsis — the new marquee uses a fade-gradient mask. */
  max-width: 150px;
  font-family: var(--font-ar);
}
.student-name-sub { max-width: 240px; }
[dir="ltr"] .student-name-sub { text-align: left; }

/* Examiner cell */
.examiner-cell { display: flex; align-items: center; gap: 8px; }
.examiner-ava {
  width: 26px; height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
}
.density-cozy .examiner-ava { width: 34px; height: 34px; }
.examiner-cell .cell-link {
  color: var(--ink);
  text-decoration: none;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  /* No text-overflow:ellipsis — the marquee uses a fade-gradient mask. */
  max-width: 210px;
}
/* Name stack inside the examiner cell — same layout as .student-name so the
   secondary (Arabic / English) line renders directly under the headline name
   in cozy density. */
.examiner-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.examiner-name-sub {
  display: block;
  font-size: 10.5px;
  color: var(--ink-3);
  font-weight: 400;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  max-width: 210px;
  font-family: var(--font-ar);
}
[dir="ltr"] .examiner-name-sub { text-align: left; }
.examiner-cell { gap: 5px; }
.examiner-cell .cell-link:hover { color: var(--brand-700); }

/* Vehicle tag — minimal: a colored category dot + plate text. The dot's
   hue communicates Manual vs Automatic at a glance; the column header's
   car icon already says "this column = vehicle", so no per-row icon. */
.vehicle-tag {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px;
  color: var(--ink);
  white-space: nowrap;
}
/* The data-table cells use overflow:hidden so other columns can ellipsize.
   The vehicle cell needs to escape that so its tooltip isn't clipped.
   Positioning + z-index for the tooltip itself live in the unified
   `.data-table tbody [data-tip]::after` rule below, so every in-table
   tooltip behaves identically. */
.data-table td.cell-vehicle { overflow: visible; }
.vehicle-tag::before {
  content: '';
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 12%, transparent);
}
/* Manual — warm amber */
.vehicle-tag.is-manual::before { background: oklch(0.66 0.16 55); color: oklch(0.66 0.16 55); }
/* Automatic — calm cyan */
.vehicle-tag.is-auto::before   { background: oklch(0.60 0.13 215); color: oklch(0.60 0.13 215); }
[data-theme="dark"] .vehicle-tag.is-manual::before { background: oklch(0.74 0.16 55); color: oklch(0.74 0.16 55); }
[data-theme="dark"] .vehicle-tag.is-auto::before   { background: oklch(0.72 0.13 215); color: oklch(0.72 0.13 215); }

/* Result pills — fixed semantic colors (not impacted by brand).
   Absent uses neutral surface tokens so it visually matches the
   vehicle chip and reads as "no result" rather than a warning. */
:root {
  --pass: oklch(0.56 0.13 155);
  --pass-bg: oklch(0.96 0.04 155);
  --pass-border: oklch(0.85 0.08 155);
  --fail: oklch(0.55 0.17 25);
  --fail-bg: oklch(0.96 0.04 25);
  --fail-border: oklch(0.85 0.10 25);
  --absent: var(--ink-2);
  --absent-bg: var(--bg-sunken);
  --absent-border: var(--line);
}
[data-theme="dark"] {
  --pass: oklch(0.78 0.14 155);
  --pass-bg: oklch(0.28 0.06 155);
  --pass-border: oklch(0.42 0.10 155);
  --fail: oklch(0.74 0.16 25);
  --fail-bg: oklch(0.30 0.07 25);
  --fail-border: oklch(0.45 0.12 25);
  --absent: var(--ink-2);
  --absent-bg: var(--bg-sunken);
  --absent-border: var(--line);
}

.result-cell {
  display: inline-flex; flex-direction: column; align-items: center; gap: 5px;
}
.result-pill {
  display: inline-grid; place-items: center;
  /* Fixed width so Pass / Failed / Absent share the exact same pill
     footprint regardless of label length. EN labels are all 6 chars
     ("Passed" / "Failed" / "Absent") and AR labels are all 4 chars
     ("ناجح" / "راسب" / "غائب"), so 64px comfortably fits both at the
     11px font-size + 600 weight without crowding the inner padding. */
  width: 64px;
  height: 22px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  box-sizing: border-box;
}
[dir="rtl"] .result-pill { width: 56px; }
.result-pill.absent {
  background: var(--absent-bg);
  color: var(--absent);
  border: 1px solid var(--absent-border);
}
.result-pill.pass {
  background: var(--pass-bg);
  color: var(--pass);
  border: 1px solid var(--pass-border);
}
.result-pill.fail {
  background: var(--fail-bg);
  color: var(--fail);
  border: 1px solid var(--fail-border);
}
.result-sub {
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--warn);
  font-weight: 600;
  text-transform: uppercase;
  line-height: 1.1;
}
[dir="rtl"] .result-sub { font-size: 11px; letter-spacing: 0; }
.density-compact .result-cell { gap: 3px; }
.result-sub.is-applied { color: var(--fail); }

/* Flat result for `density-compact` — colored status dot + text on a
   single line. Replaces the rounded pill in dense rows so the column
   is one line tall (~16px) instead of a 22px-pill stacked above an
   optional override caption. The dot uses the same "filled disc with
   a soft halo ring" treatment as the vehicle-tag dot so the two
   columns share a visual language; only the hue differs (pass-green
   vs fail-red vs the vehicle's manual-amber / auto-cyan). */
.result-flat {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
}
.result-flat-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  /* Halo ring identical to .vehicle-tag::before — the ring uses
     `currentColor` so each variant just sets its own colour and gets
     the matching tint automatically. */
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 12%, transparent);
}
.result-flat--pass   .result-flat-dot { background: var(--ok); color: var(--ok); }
.result-flat--pass   .result-flat-label { color: var(--ok); }
.result-flat--fail   .result-flat-dot { background: var(--err); color: var(--err); }
.result-flat--fail   .result-flat-label { color: var(--err); }
.result-flat--absent .result-flat-dot {
  background: transparent;
  color: var(--ink-3);
  box-shadow: inset 0 0 0 1.5px var(--ink-3);
}
.result-flat--absent .result-flat-label { color: var(--ink-3); }

/* Override state — outlined pill drawn around the result-flat group
   itself (dot + label), NOT around the row. Two visually distinct
   stroke styles so the difference reads even without colour:
     - pending  → DASHED warning-yellow ring (reads as "in progress")
     - applied  → SOLID  error-red ring     (reads as "amended/done")
   Padding bumps so the ring sits clear of the dot and label. The
   `data-tip` on the JSX surfaces the textual phrase on hover. */
.result-flat.is-override-pending,
.result-flat.is-override-applied {
  /* 5px vertical padding gives the dot+label comfortable interior
     breathing room inside the ring; horizontal padding stays at 9px
     so the content doesn't crowd the inner edge. The 32px compact
     row still has clear space above/below the pill. */
  padding: 5px 9px;
  border-radius: var(--r-pill);
  border: 1.5px dashed var(--warn, oklch(0.72 0.15 75));
  /* Hint that this element is hover-actionable (tooltip). */
  cursor: help;
}
.result-flat.is-override-applied {
  border-style: solid;
  border-color: var(--err);
}

/* Result cell still needs overflow:visible so the outlined-pill's
   tooltip pseudo-element isn't clipped by the data-table's default
   overflow:hidden on td. */
.data-table td.cell-result { overflow: visible; }
/* Tooltip positioning handled by the unified `.data-table tbody
   [data-tip]::after` rule below. */

/* Compact mode hides the vehicle-column dot. The header already
   carries a car icon and the plate's "M"/"A" suffix encodes
   manual/automatic, so the per-row dot is redundant noise in dense
   rows where every pixel matters. */
.density-compact .vehicle-tag { gap: 0; }
.density-compact .vehicle-tag::before { display: none; }
[data-theme="dark"] .result-sub { color: oklch(0.82 0.14 var(--warn-h)); }
[data-theme="dark"] .result-sub.is-applied { color: var(--fail); }

/* Table footer */
.table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
  background: var(--bg-sunken);
}
.table-count {
  font-size: 11px;
  color: var(--ink-3);
  letter-spacing: 0.04em;
}
.page-size {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  color: var(--ink-3);
}
.page-size select {
  appearance: none;
  -webkit-appearance: none;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 4px 24px 4px 10px;
  font-family: inherit;
  font-size: 12px;
  color: var(--ink);
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%), linear-gradient(-45deg, transparent 50%, var(--ink-3) 50%);
  background-position: calc(100% - 12px) 50%, calc(100% - 8px) 50%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
[dir="rtl"] .page-size select { padding: 4px 10px 4px 24px; background-position: 12px 50%, 8px 50%; }
.page-size select:hover { border-color: var(--line-strong); }
.page-size select:focus { outline: 2px solid var(--ring); outline-offset: 1px; }
.pagination {
  display: flex; align-items: center; gap: 2px;
  font-size: 12px;
}
.page-btn {
  min-width: 28px;
  height: 28px;
  padding: 0 8px;
  display: inline-grid; place-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.page-btn:hover { background: var(--brand-tint); color: var(--ink); }
.page-btn.is-active {
  background: var(--brand-tint-2);
  color: var(--brand-700);
  border-color: color-mix(in oklch, var(--brand-500) 30%, var(--line));
}
[data-theme="dark"] .page-btn.is-active { color: var(--brand-700); }
.page-btn-nav { color: var(--ink-3); font-size: 14px; padding: 0 6px; }
.page-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.page-btn:disabled:hover { background: transparent; color: var(--ink-3); }
.page-btn-ellipsis { font-weight: 700; letter-spacing: 1px; color: var(--ink-3); }
.page-btn-ellipsis.is-open { background: var(--bg-sunken); color: var(--ink); }

.ellipsis-wrap { position: relative; }
.ellipsis-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  padding: 4px;
  max-height: 220px;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  min-width: 220px;
  z-index: 60;
  animation: dropdown-in 120ms ease-out;
}
.ellipsis-menu-item {
  min-width: 32px;
  height: 28px;
  border: 0;
  background: transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink);
}
.ellipsis-menu-item:hover { background: var(--brand-tint); color: var(--brand-700); }
[data-theme="dark"] .ellipsis-menu-item:hover { color: var(--brand-700); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Disabled buttons must not respond to hover — keep their resting paint
   regardless of variant (primary/secondary/text). */
.btn:disabled:hover,
.btn-primary:disabled:hover,
.btn-secondary:disabled:hover,
.btn-text:disabled:hover,
.btn-ghost:disabled:hover {
  background: inherit;
  color: inherit;
  border-color: inherit;
  box-shadow: var(--sh-xs);
}
.btn-primary:disabled,
.btn-primary:disabled:hover { background: var(--brand-600); color: var(--brand-ink); }
.btn-secondary:disabled,
.btn-secondary:disabled:hover { background: var(--bg-raised); color: var(--ink-2); border-color: var(--line); }
.btn-text:disabled,
.btn-text:disabled:hover { background: transparent; color: var(--ink-2); border-color: transparent; box-shadow: none; }

/* Button variants matching the team's `Button` component API */
.btn-secondary {
  background: var(--bg-raised);
  color: var(--ink-2);
  border-color: var(--line);
}
.btn-secondary:hover { background: var(--bg-sunken); border-color: var(--line-strong); color: var(--ink); }
.btn-text {
  background: transparent;
  color: var(--ink-2);
  border-color: transparent;
}
.btn-text:hover { background: var(--brand-tint); color: var(--ink); }
/* Destructive / cautionary variant — used by confirm modals where
   the action is reversible but consequential (e.g. deactivating a
   template stops scheduled deliveries). Uses the system error
   token rather than a fresh hue so the warning ramp stays
   consistent with the failure-rate red elsewhere. */
.btn-danger {
  background: var(--err);
  color: var(--brand-ink, white);
  border-color: var(--err);
}
.btn-danger:hover {
  background: color-mix(in oklab, var(--err) 80%, black);
  border-color: color-mix(in oklab, var(--err) 80%, black);
}
.btn-danger:disabled,
.btn-danger:disabled:hover {
  background: color-mix(in oklab, var(--err) 60%, var(--bg-sunken));
  border-color: transparent;
  color: var(--brand-ink, white);
  opacity: 0.7;
}
.btn-label { display: inline-flex; align-items: center; }
.btn-spinner {
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: btn-spin 600ms linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* ─────────── PAGE HEADER (unified across all pages) ─────────── */
.page-header {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 4px;
}
/* Top row: text-block (h1 + subtitle) on the start side, school-switcher
   pinned to the end side. Wraps on narrow viewports so the switcher drops
   below the title block instead of squeezing it. */
.page-header-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.page-header-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 auto; }
.page-h1 {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1.2;
}
[dir="rtl"] .page-h1 { font-size: 23px; letter-spacing: 0; }
.page-h1-sub {
  font-size: 13px;
  color: var(--ink-3);
  line-height: 1.5;
  max-width: 720px;
}
[dir="rtl"] .page-h1-sub { font-size: 13.5px; }

/* ─────────── TABS — underline-style page tabs ─────────── */
.tabs {
  display: flex;
  align-items: center;
  gap: 0;
  border-bottom: 1px solid var(--line);
  margin-top: 4px;
  /* Allow the row to scroll horizontally when the labels don't fit
     on narrow viewports (mobile / split layouts). The scrollbar is
     hidden so it reads as a clean overflow strip. Touch devices get
     momentum scroll for free. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs-spacer {
  /* Don't fight the scroll: the spacer should only expand to fill
     extra room when the tabs already fit. `flex-shrink: 1` lets it
     collapse to 0 when the row needs to scroll, so the secondary
     tab sits right after the primary group instead of being pushed
     off-screen by an over-greedy spacer. */
  flex: 1 1 0;
  min-width: 16px;
}
.tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink-3);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  margin-bottom: -1px;
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
  white-space: nowrap;
  /* Tabs keep their natural width so labels never truncate; the
     `.tabs` container scrolls instead of letting buttons shrink and
     ellipsize their text. */
  flex-shrink: 0;
}
[dir="rtl"] .tab { font-size: 14px; }
.tab:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 3%, transparent); }
.tab.is-on {
  color: var(--brand-700);
  border-bottom-color: var(--brand-600);
  font-weight: 600;
}
[data-theme="dark"] .tab.is-on { color: var(--brand-700); }
.tab-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.tab.is-on .tab-badge {
  background: var(--brand-tint);
  border-color: color-mix(in oklch, var(--brand-500) 25%, var(--line));
  color: var(--brand-700);
}
.tab--secondary { padding-inline-start: 14px; border-inline-start: 1px solid var(--line); margin-inline-start: 4px; }

/* ─────────── BREADCRUMBS ─────────── */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 4px;
  font-size: 12.5px;
}
[dir="rtl"] .crumbs { font-size: 13.5px; }
.crumb {
  background: transparent;
  border: 0;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: inherit;
  color: var(--ink-3);
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.crumb:hover:not(:disabled) { color: var(--ink); background: var(--brand-tint); }
.crumb.is-current {
  color: var(--ink);
  font-weight: 600;
  cursor: default;
}
.crumb-sep { color: var(--ink-4); padding: 0 2px; }

/* ─────────── KPI STRIP ─────────── */
.kpi-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}
/* Keep every KPI card on a single row from 900px upward, regardless
   of card count (the report views range from 6 to 7 KPIs). Switching
   to `grid-auto-flow: column` + `grid-auto-columns: minmax(0, 1fr)`
   gives each item its own equal-width column with no wrap, so the
   strip only breaks once the viewport drops to ≤899 — at which point
   the base rule above kicks back in and wraps cards at their 140px
   minimum. Without this override, the auto-fit + 140px floor wraps
   7 cards as soon as the viewport falls below ~912px. */
@media (min-width: 900px) {
  .kpi-strip {
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    grid-template-columns: none;
  }
}
.kpi {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
.kpi-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .kpi-label { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.kpi-value {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .kpi-value { font-size: 21px; letter-spacing: 0; }
.kpi.is-pass .kpi-value { color: var(--ok); }
.kpi.is-fail .kpi-value { color: var(--err); }
/* `kpi--wrap` opt-in: lets the value span up to 2 lines and truncate
   with an ellipsis if it still doesn't fit. Used on cards whose
   value is a name (e.g. the "Maneuver" card in Test Analysis L2)
   where the headline read benefits from the full word over a clipped
   single-line ellipsis. The default behaviour for KPI values stays
   single-line nowrap so numeric headlines (like "8,261", "72.7%") read
   on one line. */
.kpi.kpi--wrap .kpi-value {
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.15;
}

/* KPI badge — pinned to the top-right (inline-end) of the card, matching
   the ResultPill placement on the L4 maneuver cards. The KPI gets
   `has-badge` to reserve right-padding on the label so long labels don't
   run under the badge. */
.kpi { position: relative; }
.kpi.has-badge .kpi-label { padding-inline-end: 72px; }
.kpi-badge {
  position: absolute;
  top: 12px;
  inset-inline-end: 12px;
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}
[dir="rtl"] .kpi-badge { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.kpi-badge.is-info {
  background: var(--brand-tint);
  color: var(--brand-700);
  border-color: color-mix(in oklch, var(--brand-500) 25%, var(--line));
}
.kpi-badge.is-pass {
  background: color-mix(in oklab, var(--ok) 12%, var(--bg-raised));
  color: var(--ok);
  border-color: color-mix(in oklab, var(--ok) 30%, var(--line));
}
.kpi-badge.is-warn {
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 14%, var(--bg-raised));
  color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 70%, var(--ink));
  border-color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 35%, var(--line));
}
.kpi-badge.is-fail {
  background: color-mix(in oklab, var(--err) 10%, var(--bg-raised));
  color: var(--err);
  border-color: color-mix(in oklab, var(--err) 30%, var(--line));
}

/* KPI footer-meta — small muted text pinned to the bottom-end (right in
   LTR) of the card. Used at L4 for the score "72/100" — sits below the
   value without competing with it. */
.kpi { padding-bottom: 18px; }

/* Reports KPI cards — give the headline (label + value) and the
   bottom-pinned breakdown sub-row a guaranteed gap between them.
   `.kpi-sub` already pulls itself to the bottom via `margin-top: auto`,
   so the only way to widen the gap visually is to grow the card's
   minimum height — without it, single-line content collapses the two
   rows together. Scoped to `.rpt-doc` so the change is confined to
   reports pages. */
.rpt-doc .kpi { min-height: 104px; }
.kpi-meta {
  position: absolute;
  bottom: 8px;
  inset-inline-end: 14px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
[dir="rtl"] .kpi-meta { font-size: 12px; }
/* Smaller trailing fragment inside a KPI value, e.g. "#1 of 3" or
   gender breakdown "120m / 80f" — keeps the headline number prominent. */
.kpi-value-sub {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
  margin-inline-start: 2px;
}
[dir="rtl"] .kpi-value-sub { font-size: 12.5px; }

/* Sub-row beneath the KPI value — used for breakdowns like
   "1,425 Male · 905 Female", "12 Branches", or "Pending · Completed".
   `margin-top: auto` pins it to the bottom of the flex-column .kpi card,
   so the headline figure breathes at the top and the supporting
   breakdown sits as a quiet footer. Combined with the grid's default
   stretch alignment, all cards in a row share the same height and
   their sub-rows line up across the row. */
.kpi-sub {
  display: flex;
  /* Vertically center the icon glyphs against their numeric/text
     siblings inside the row — baseline alignment makes outlined
     SVGs (especially `male` / `female` and the new `hourglass` /
     `checkCircle`) sit slightly low because their visual centre
     doesn't match the alphabetic baseline of the digits. */
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.2;
  /* Pin the breakdown row to the bottom-start of the .kpi flex
     column so every card's sub-row lines up across the strip,
     regardless of value-line wrapping above. */
  margin-top: auto;
  white-space: nowrap;
  /* No overflow clipping — the GenderSeg tooltip is a CSS ::after
     anchored above (and centered on) the segment, so any horizontal
     clip on this row chops the tooltip when the segment sits near
     the row's left or right edge. KPI breakdowns are short
     ("1,425 · 905", "47 Branches"), so we don't need ellipsis here. */
  overflow: visible;
}
[dir="rtl"] .kpi-sub { font-size: 12px; }
/* Center each segment internally too so the icon and the value
   share a common vertical centerline (matches the `.kpi-sub`
   change above). */
.kpi-sub-seg { display: inline-flex; align-items: center; gap: 2px; }
.kpi-sub-num {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  /* Sub-row is supporting context for the headline value above it —
     drop a step on the ink ramp (ink-2 → ink-3) so the breakdown
     reads as quieter dark-gray rather than competing near-black. */
  color: var(--ink-3);
}
.kpi-sub-tag {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--ink-4);
  letter-spacing: 0.02em;
}
[dir="rtl"] .kpi-sub-tag { font-size: 11.5px; letter-spacing: 0; }
/* Gender icon inside the KPI sub-row — handed-over silhouettes from
   assets/male.svg and assets/female.svg, drawn through `currentColor`
   so they pick up the surrounding ink token automatically (light theme
   ⇒ dark ink, dark theme ⇒ light ink). Both genders share the same
   ink-3 muted tone — the figure shape itself is the differentiator,
   not colour, which keeps the design system's palette consistent. */
.kpi-sub-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* No directional margin — the parent `.kpi-sub-seg gap: 2px` is the
     sole source of icon↔value spacing, so the gap is exactly 2px in
     both LTR and RTL. */
  color: var(--ink-3);
  /* Optical-centering nudge. Flex `align-items: center` aligns the
     icon's 12×12 box against the number's line-box, but digits have
     no descenders so their visual centerline sits ~1px above the
     line-box geometric center. Shift the icon up by 1px so its
     optical center matches the numerals' rather than the line-box's. */
  transform: translateY(-1px);
}
[data-theme="dark"] .kpi-sub-icon { color: var(--ink-3); }
.kpi-sub-icon svg {
  /* The asset is tall (211:310 ≈ 0.68:1) and is an outlined glyph
     (stroke only, no fill, stroke-width 32 in viewBox units). The Icon
     component sets the SVG box to a square via inline width/height
     (controlled by the `size` prop in GenderSeg), and the default
     preserveAspectRatio centers the figure inside that box at its
     true aspect ratio. So `size=12` yields a ~8.2px-wide × 12px-tall
     figure with a ~1.24px stroke — visually balanced beside the
     12.5px KPI sub numbers. */
  display: block;
  overflow: visible;
}
.kpi-sub-seg[data-tip] { cursor: default; }
.kpi-sub-sep { color: var(--ink-4); }
.kpi-sub-plain { color: var(--ink-3); }

/* ─────────── PASS-RATE CELL & FAIL-RATE BAR ─────────── */
.pr-cell {
  display: flex; align-items: center; gap: 8px;
  min-width: 120px;
  /* Tooltip needs a positioning context. */
  position: relative;
}
/* The td containing a tooltip-bearing PrCell must opt out of the
   table-wide `overflow: hidden` so the floating breakdown card can
   rise above the row. Other cells (vehicle, result, action cells)
   have their own opt-out classes; PrCell uses `:has()` so any td
   that ends up holding one inherits the opt-out automatically,
   without each call site having to remember to add a class. */
.data-table td:has(.pr-cell--has-tip) { overflow: visible; }
.pr-cell--has-tip { cursor: default; }
.pr-bar {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
}
/* Split bar — the track itself represents fail share (red); the fill on top
   represents pass share (green). Reading left-to-right the user sees the
   green/red border land exactly at the percentage. */
.pr-bar--split {
  background: color-mix(in oklab, var(--err) 28%, var(--bg-sunken));
}
/* Inverted variant — track is green (pass share), fill is red (fail
   share). Used on failure-rate columns so the red portion reads from the
   bar's leading edge (where the eye lands first). */
.pr-bar--inverted {
  background: color-mix(in oklab, var(--ok) 28%, var(--bg-sunken));
}
.pr-fill {
  height: 100%;
  border-radius: 3px;
  transition: width 200ms ease;
}
.pr-fill--pass { background: var(--ok); }
.pr-fill--fail { background: var(--err); }
.pr-num {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 48px;
  text-align: end;
}
/* Hover tooltip on the cell itself (instead of via a wrapping host).
   Keeps the same look as the existing pr-cell-tip used by PrCellGender. */
.pr-cell .pr-cell-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  /* Anchor to the trailing edge of the cell (right in LTR, left in
     RTL) so the floating card extends inward rather than off the
     page. PR columns sit toward the right side of the table — left-
     anchoring caused the breakdown card to spill past the right
     edge of the page on Institute L1 + Test Analysis splits. */
  inset-inline-end: 0;
  inset-inline-start: auto;
  z-index: 50;
  display: none;
  /* 4 columns: Label | Overall | Male | Female. Helpers without
     gender data span the right 3 columns with their single value
     so the grid stays consistent across breakdowns. */
  grid-template-columns: max-content max-content max-content max-content;
  gap: 4px 14px;
  padding: 8px 10px;
  background: var(--bg-raised);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  font-size: 11.5px;
  white-space: nowrap;
  pointer-events: none;
}
.pr-cell--has-tip:hover .pr-cell-tip { display: grid; }

.fail-bar { display: flex; align-items: center; gap: 8px; min-width: 100px; }
.fail-bar-track {
  flex: 1;
  height: 6px;
  background: var(--bg-sunken);
  border-radius: 3px;
  overflow: hidden;
}
.fail-bar-fill {
  height: 100%;
  background: color-mix(in oklab, var(--err) 70%, transparent);
  border-radius: 3px;
}
.fail-bar-num {
  font-size: 12px;
  font-weight: 500;
  color: var(--err);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 48px;
  text-align: end;
}

/* ─────────── SEVERITY PILL (Minor / Major) ─────────── */
.sev-pill {
  display: inline-flex; align-items: center;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: 500;
  border: 1px solid transparent;
  white-space: nowrap;
}
[dir="rtl"] .sev-pill { font-size: 12px; }
.sev-pill.is-minor {
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 14%, transparent);
  color: oklch(0.5 0.13 75);
  border-color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 35%, var(--line));
}
[data-theme="dark"] .sev-pill.is-minor { color: oklch(0.78 0.12 75); }
.sev-pill.is-major {
  background: color-mix(in oklab, var(--err) 12%, transparent);
  color: var(--err);
  border-color: color-mix(in oklab, var(--err) 35%, var(--line));
}

/* Schedules — Report cell stacks the report name with the run-cadence
   string ("Every Monday · 08:00") as a quiet subtitle. Replaces the
   standalone Frequency column so the row reads at a glance: what is
   it, and how often does it run? */
/* Match the Custom Reports template-table row height + cell font
   metrics so Custom Reports and Schedules read as siblings. The
   `.sched-table` (no density-cozy class on its parent) would
   otherwise size rows to content and end up shorter/uneven. */
.sched-table-wrap .sched-table tbody td,
.sched-table-wrap .sched-table tbody tr { height: 56px; }
.sched-report-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.sched-report-cell .cell-strong {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
[dir="rtl"] .sched-report-cell .cell-strong { font-size: 13.5px; }
.sched-report-freq {
  font-size: 11.5px;
  color: var(--ink-3);
  /* Same line-height as `.tpl-desc` so the two-line cell sums to
     the same vertical reach in both tables. */
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
[dir="rtl"] .sched-report-freq { font-size: 12px; }

/* ─────────── STATUS PILL (unified) ─────────── */
/* Single shared pill for every status surface in the app. Replaces
   the per-feature `.sched-status` and inline `.tpl-status` styles so
   the visual treatment (shape, dot size, padding, color ramp) stays
   identical across Custom Reports, Schedules, and any future feature
   that needs a status indicator (requests, tests, vehicles). */
.status-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
}
[dir="rtl"] .status-pill { font-size: 12.5px; padding: 2px 8px 2px 10px; }
.status-pill-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.status-pill.is-active   { color: var(--ok); border-color: color-mix(in oklab, var(--ok) 35%, var(--line)); }
.status-pill.is-active   .status-pill-dot { background: var(--ok); }
.status-pill.is-paused   { color: oklch(0.5 0.13 75); border-color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 35%, var(--line)); }
.status-pill.is-paused   .status-pill-dot { background: var(--warn, oklch(0.72 0.15 75)); }
.status-pill.is-inactive { color: var(--ink-3); }
.status-pill.is-inactive .status-pill-dot { background: var(--ink-4); }
/* Draft = "not yet committed". Differentiates from Paused (which is
   already amber) by switching to a dashed border in muted ink — the
   same visual shorthand the filter pills use for "pending change".
   Reads as informational, not alarming, and never collides with
   Paused at a glance. */
.status-pill.is-draft    {
  color: var(--ink-3);
  border-color: var(--line-strong);
  border-style: dashed;
  background: var(--bg-sunken);
}
.status-pill.is-draft    .status-pill-dot { background: var(--ink-4); }

/* ─────────── CALLOUT BANNER ─────────── */
.callout {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px;
  background: var(--brand-tint);
  border: 1px solid color-mix(in oklch, var(--brand-500) 22%, var(--line));
  border-radius: var(--r-md);
  color: var(--ink);
  font-size: 12.5px;
  line-height: 1.5;
}
[dir="rtl"] .callout { font-size: 13.5px; }
.callout-icon {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  background: var(--bg-raised);
  border-radius: 50%;
  color: var(--brand-700);
  flex-shrink: 0;
}
[data-theme="dark"] .callout-icon { color: var(--brand-700); }
.callout-text { padding-top: 3px; }
.callout-text strong { font-weight: 600; color: var(--ink); }
.callout-text em { font-style: normal; font-weight: 600; color: var(--brand-700); }
.callout-text .em { font-weight: 600; color: var(--err); }
[data-theme="dark"] .callout-text em { color: var(--brand-700); }

/* ─────────── REPORT DOC HEAD ─────────── */
/* Two-row layout:
     row 1 — title (left) + action buttons (right)
     row 2 — level chain spanning the full head width
   Stacking lets the chain use the whole card before wrapping, instead
   of competing with the actions for horizontal space. */
.rpt-head {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-xs);
}
.rpt-head-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  /* Container query scope: when this row gets narrow enough that the
     title would need a 2nd line (title text + "X levels" badge no
     longer fit on a single line), we also stack the action CTAs
     vertically — see `@container rpt-head-row` below. Using a
     container query (rather than a viewport media query) keeps the
     stack tied to the actual row width, which is what governs whether
     the title wraps in the first place. */
  container-type: inline-size;
  container-name: rpt-head-row;
}
.rpt-head-info { min-width: 0; flex: 1; }
.rpt-head-kind {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-4);
  text-transform: uppercase;
}
[dir="rtl"] .rpt-head-kind { font-size: 12px; letter-spacing: 0; text-transform: none; }
.rpt-head-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-top: 4px;
  line-height: 1.25;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
[dir="rtl"] .rpt-head-title { font-size: 19px; letter-spacing: 0; }
/* Small "X levels" badge that sits next to the report head title.
   Same shape grammar as `.tab-badge` (pill with subtle background)
   but tied to the head's typography so it scales correctly when the
   title font size changes. */
.rpt-head-title-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
[dir="rtl"] .rpt-head-title-badge { font-size: 12px; }
.rpt-head-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 4px 24px;
  margin: 12px 0 0;
}
.rpt-head-grid > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rpt-head-grid dt {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin: 0;
}
[dir="rtl"] .rpt-head-grid dt { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.rpt-head-grid dd {
  font-size: 12.5px;
  color: var(--ink);
  margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .rpt-head-grid dd { font-size: 13px; }
.rpt-head-actions {
  display: inline-flex; align-items: center; gap: 6px;
  flex-shrink: 0;
}

/* When the head row narrows past the point where the title can keep
   its name + "X levels" badge on one line (~560px of row width),
   stack the action CTAs vertically. Using `column-reverse` puts
   Generate Report on top and Schedule beneath it (visual order is
   reversed from source order, where Schedule comes first). The
   stacked column right-aligns each button at its content width so
   they don't blow up to fill the column. */
@container rpt-head-row (max-width: 559px) {
  .rpt-head-actions {
    flex-direction: column-reverse;
    align-items: flex-end;
    gap: 6px;
  }
}

/* ─────────── LEVEL CHAIN (inside rpt-head) ─────────── */
/* Drill-down navigation: replaces the previous <Breadcrumbs> that sat
   above the report tab. Sits directly under the report title in the
   head card. Each level is a pill with an "L1/L2" badge plus the
   level's label; non-current pills are buttons that drill back up the
   hierarchy when clicked, and the current pill is filled with brand
   tint and not interactive. */
.rpt-level-chain-wrap {
  /* Margin removed — the chain now sits as a sibling of `.rpt-head-row`
     inside `.rpt-head`, which is a column flex with its own `gap: 10px`.
     Adding margin here would double the spacing between rows. */
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Stretch the chain and hint to the wrap container's full width
     (which is now the full `.rpt-head` width since we hoisted the
     wrap out of `.rpt-head-info`). Gives the chain its full flex-wrap
     budget — pills wrap only when they truly can't fit on one line. */
  align-items: stretch;
}
.rpt-level-chain {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}
.rpt-level-pill { display: inline-flex; }
.rpt-level-pill-inner {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 3px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  background: var(--bg-raised);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
  white-space: nowrap;
}
[dir="rtl"] .rpt-level-pill-inner { padding: 3px 3px 3px 10px; font-size: 13px; }
button.rpt-level-pill-inner:hover {
  border-color: color-mix(in oklab, var(--brand-500) 35%, var(--line-2));
  background: var(--brand-tint);
  color: var(--brand-700);
}
button.rpt-level-pill-inner:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
.rpt-level-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--bg-sunken);
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  transition: background var(--t-fast), color var(--t-fast);
}
button.rpt-level-pill-inner:hover .rpt-level-num {
  background: color-mix(in oklab, var(--brand-500) 22%, var(--bg-raised));
  color: var(--brand-700);
}
.rpt-level-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
}
/* Current level — filled, marked, non-interactive. The brand-filled
   number badge plus the dot at the end gives two redundant cues that
   this is the current view ("you are here") without relying solely
   on weight/colour. */
.rpt-level-pill.is-current .rpt-level-pill-inner {
  background: var(--brand-tint);
  border-color: color-mix(in oklab, var(--brand-500) 35%, var(--line-2));
  color: var(--brand-700);
  cursor: default;
}
.rpt-level-pill.is-current .rpt-level-num {
  background: var(--brand-600);
  color: var(--brand-ink);
}
.rpt-level-pill.is-current .rpt-level-pill-inner::after {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--brand-600);
  margin-inline-start: 2px;
  flex-shrink: 0;
}
/* When a current-level re-pick is wired up, the current pill becomes
   a clickable button — opens a popover that lets the user swap to a
   different entity at this level. Hover deepens the brand-tint so the
   interactivity reads at-a-glance; .is-open inverts to brand-fill
   while the popover is showing (matching the dashed drill trigger's
   open state grammar). */
button.rpt-level-pill-inner.is-clickable-current {
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
button.rpt-level-pill-inner.is-clickable-current:hover {
  background: var(--brand-tint-2);
  border-color: color-mix(in oklab, var(--brand-500) 50%, var(--line-2));
}
button.rpt-level-pill-inner.is-clickable-current.is-open {
  background: var(--brand-600);
  color: var(--brand-ink);
  border-color: var(--brand-600);
}
button.rpt-level-pill-inner.is-clickable-current.is-open .rpt-level-num {
  background: var(--brand-ink);
  color: var(--brand-600);
}
button.rpt-level-pill-inner.is-clickable-current.is-open::after {
  background: var(--brand-ink);
}
button.rpt-level-pill-inner.is-clickable-current:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
.rpt-level-sep {
  display: inline-flex;
  align-items: center;
  color: var(--ink-4);
  font-size: 14px;
  user-select: none;
  padding: 0 2px;
  list-style: none;
}
[dir="rtl"] .rpt-level-sep { transform: scaleX(-1); }
/* Affordance hint — explicitly tells the user the levels are
   navigable. Hidden when there's only one level in the chain (nothing
   to navigate to). Reuses the click-cursor glyph from the table-row
   drill hint so the "this is clickable" cue stays consistent. */
.rpt-level-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--ink-3);
  letter-spacing: 0;
  /* Always single-line. The hint is short enough that allowing it to
     wrap looks like a typesetting bug rather than a deliberate break;
     keeping it on one line also stops it from competing visually with
     the level pills above it. */
  white-space: nowrap;
}
.rpt-level-hint > span { white-space: nowrap; }
[dir="rtl"] .rpt-level-hint { font-size: 12px; }
.rpt-level-hint svg {
  color: var(--brand-600);
  flex-shrink: 0;
}

/* ─────────── DRILL-DOWN PICKER (tail of LevelChain) ─────────── */
/* Trigger: small text-button styled to read as "next action available"
   without competing with the level pills themselves. Sits at the tail
   of the chain after a › separator. Open state inverts the colour to
   give a clear "popover is showing" signal. */
.rpt-level-drill-tail {
  display: inline-flex;
  position: relative;
}
.rpt-drill-down {
  position: relative;
  display: inline-flex;
}
.rpt-drill-down-trigger {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--brand-700);
  background: transparent;
  border: 1px dashed color-mix(in oklab, var(--brand-500) 45%, var(--line));
  border-radius: var(--r-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
[dir="rtl"] .rpt-drill-down-trigger { font-size: 12.5px; }
.rpt-drill-down-trigger:hover {
  background: var(--brand-tint);
  border-style: solid;
  border-color: color-mix(in oklab, var(--brand-500) 50%, var(--line));
}
.rpt-drill-down-trigger.is-open {
  background: var(--brand-600);
  color: var(--brand-ink);
  border-style: solid;
  border-color: var(--brand-600);
}
.rpt-drill-down-trigger:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
.rpt-drill-down-trigger svg { color: currentColor; flex-shrink: 0; }

/* Popover: anchored below the trigger, opens to the inline-start so
   the search input aligns with the trigger's leading edge. Wider than
   the trigger to comfortably hold the search input + a list of items
   without cramping. Drops above the page on small screens via the
   max-height + scroll on the list. */
.rpt-drill-down-popover {
  position: absolute;
  top: calc(100% + 6px);
  inset-inline-start: 0;
  z-index: 50;
  width: min(420px, calc(100vw - 32px));
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: drillFade 120ms cubic-bezier(.4,0,.2,1);
}
@keyframes drillFade {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.rpt-drill-down-head {
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.rpt-drill-down-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}
[dir="rtl"] .rpt-drill-down-title { font-size: 13px; letter-spacing: 0; text-transform: none; }
.rpt-drill-down-search-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.rpt-drill-down-search-icon {
  position: absolute;
  inset-inline-start: 9px;
  color: var(--ink-4);
  pointer-events: none;
}
.rpt-drill-down-search {
  width: 100%;
  padding: 7px 10px 7px 30px;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  outline: none;
  transition: border-color var(--t-fast), background var(--t-fast);
}
[dir="rtl"] .rpt-drill-down-search { padding: 7px 30px 7px 10px; }
.rpt-drill-down-search:focus {
  border-color: color-mix(in oklab, var(--brand-500) 45%, var(--line));
  background: var(--bg-raised);
}
.rpt-drill-down-search::placeholder { color: var(--ink-4); }

.rpt-drill-down-list {
  list-style: none;
  margin: 0;
  padding: 4px;
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.rpt-drill-down-empty {
  padding: 24px 12px;
  text-align: center;
  color: var(--ink-4);
  font-size: 12.5px;
}
.rpt-drill-down-item {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  transition: background var(--t-fast);
}
.rpt-drill-down-item:hover:not(.is-disabled) { background: var(--brand-tint); }
.rpt-drill-down-item:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}
.rpt-drill-down-item.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.rpt-drill-down-item-main {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  flex: 1;
}
.rpt-drill-down-item-primary {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rpt-drill-down-item-secondary {
  font-size: 11.5px;
  color: var(--ink-3);
  flex-shrink: 0;
}
.rpt-drill-down-item-secondary.mono {
  font-variant-numeric: tabular-nums;
}
.rpt-drill-down-item-sub {
  font-size: 11px;
  color: var(--ink-4);
  flex-shrink: 0;
  white-space: nowrap;
}

/* ─────────── EMPTY STATE ─────────── */
.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 48px 24px;
  text-align: center;
  color: var(--ink-3);
}
.empty-state-icons {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: var(--bg-sunken);
  border-radius: 50%;
  color: var(--ink-4);
  margin-bottom: 12px;
}
.empty-state-text {
  font-size: 13px;
  color: var(--ink-3);
  max-width: 360px;
  line-height: 1.5;
}
[dir="rtl"] .empty-state-text { font-size: 13.5px; }

/* ─────────── STEPPER (Custom Report Builder wizard) ─────────── */
.stepper { display: flex; flex-direction: column; gap: 2px; }
.step {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font-family: inherit;
  text-align: start;
  cursor: pointer;
  color: var(--ink-3);
  transition: background var(--t-fast), color var(--t-fast);
}
.step:hover { background: var(--bg-sunken); color: var(--ink-2); }
.step-num {
  flex-shrink: 0;
  width: 24px; height: 24px;
  display: inline-grid; place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  background: var(--bg-raised);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.step-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.step-title {
  font-size: 12.5px;
  font-weight: 600;
  color: inherit;
  line-height: 1.3;
}
[dir="rtl"] .step-title { font-size: 13px; }
.step-sub {
  font-size: 11px;
  color: var(--ink-4);
  line-height: 1.3;
}
[dir="rtl"] .step-sub { font-size: 12px; }
.step.is-on { color: var(--ink); background: var(--brand-tint); }
.step.is-on .step-num { background: var(--brand-600); border-color: var(--brand-600); color: var(--brand-ink); }
.step.is-on .step-title { color: var(--brand-700); }
[data-theme="dark"] .step.is-on .step-title { color: var(--brand-700); }
.step.is-done .step-num { background: var(--ok); border-color: var(--ok); color: white; }
.step.is-done .step-title { color: var(--ink-2); }

/* ─────────── REPORTS PAGE ─────────── */
.rpt-tab-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 4px;
}
.rpt-doc {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.rpt-table-wrap {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  /* Clip horizontally to keep the rounded corners but let vertical overflow
     escape so vehicle/result tooltips above rows don't get cut off.
     `overflow-clip-margin: 32px` lets descendants paint up to 32px outside
     the clip box — enough headroom for action-button tooltips in the top
     and bottom rows of the table. */
  overflow-x: clip;
  overflow-y: visible;
  overflow-clip-margin: 32px;
  box-shadow: var(--sh-xs);
}
.rpt-table-wrap .data-table { margin: 0; }
.rpt-table-wrap .data-table th.num,
.rpt-table-wrap .data-table td.num {
  text-align: end;
  font-variant-numeric: tabular-nums;
}
.rpt-table-wrap .data-table th.num .th-btn { justify-content: flex-end; }
.rpt-table-wrap .data-table td.col-chev,
.rpt-table-wrap .data-table th.col-chev {
  width: 36px;
  text-align: end;
  color: var(--ink-4);
}
.rpt-table-wrap .data-table tr.is-drillable { cursor: pointer; }
.rpt-table-wrap .data-table tr.is-drillable:hover { background: var(--brand-tint); }
.rpt-table-wrap .data-table tr.is-drillable:hover td.col-chev { color: var(--brand-700); }
/* Visible spacer between FAIL count and PASS RATE bar — keeps the user from
   reading the fail number as part of the bar that follows. */
.rpt-table-wrap .data-table th.col-fail,
.rpt-table-wrap .data-table td.col-fail { padding-inline-end: 18px; }

/* Column-width plan for Institute Performance L1/L2 tables.
   Numeric counts (Total tests / Pass / Fail / Branches) only ever
   carry 4–7 digits, so we keep them narrow. The two bar columns
   (Pass Rate, 1st-Attempt PR) need room for the bar + percentage —
   wider, with a gap between them so the % reads against the right bar. */
.rpt-table-wrap .data-table colgroup .col-narrow { width: 108px; }
.rpt-table-wrap .data-table colgroup .col-pr     { width: 180px; }
.rpt-table-wrap .data-table colgroup .col-chev   { width: 36px; }
/* Maneuver Performance Summary lives in a split-pane (half the doc width).
   Make Tests / Passed / Failed narrower than the global `.col-narrow`
   default and drop the explicit width on the maneuver column so it can
   absorb the freed space — long names like "General Violations" then
   read end-to-end without clipping. */
.rpt-table-wrap .data-table.rpt-table-man-summary colgroup .col-maneuver-name { width: auto; }
.rpt-table-wrap .data-table.rpt-table-man-summary colgroup .col-narrow        { width: 76px; }
/* Failure Rate column trimmed twice (180→144→130px, total ~28% off the
   default `.col-pr` width) for this split-pane table; both reductions
   flow into the auto-width Maneuver column so longer names like
   "General Violations" get more breathing room without clipping. */
.rpt-table-wrap .data-table.rpt-table-man-summary colgroup .col-pr            { width: 130px; }
/* "Failure rate by branch" table reuses the man-summary structure but
   has no trailing chevron column, so the Failure Rate column sits
   flush against the table's right border. Bumping its width by 8px
   adds breathing room between the bar/value and the right edge. The
   chained class boosts specificity above the man-summary rule above
   so this override wins without `!important`. */
.rpt-table-wrap .data-table.rpt-table-man-summary.rpt-table-fail-by-branch colgroup .col-pr { width: 138px; }
.rpt-table-wrap .data-table.rpt-table-man-summary tbody td:first-child        { white-space: nowrap; }
/* Push the 1st-attempt PR column away from the headline PR so the % values
   read unambiguously against their own bar. */
.rpt-table-wrap .data-table th.col-2nd-pr,
.rpt-table-wrap .data-table td.col-2nd-pr { padding-inline-start: 18px; }

/* Schedules table — full-page-width table that mirrors the Saved
   Tests / Custom Reports responsive pattern: at wide viewports the
   table fills its container naturally (Report column absorbs the
   slack); at ≤1079 it freezes at the 1080-state width and the
   wrapper scrolls horizontally. The freeze rule is in the ≤1079
   media query block at the bottom of the file. Total fixed meta
   columns now sum to 840px (170 + 90 + 105 + 105 + 130 + 240) so
   the flex Report column sits at ~206px when the table is locked
   at 1046 — enough for "Quarterly fail-rate by branch". */

/* Examiner Performance L1 — examiners list. Drop the Branch column (L2
   profile card lists branches instead since examiners rotate). Add a
   visible inline-start padding on the Overrides column so it reads as a
   distinct group from the preceding Pass Rate bar. */
.rpt-table-wrap .data-table.rpt-table-exam-l1 colgroup .col-exam-name      { width: 180px; }
.rpt-table-wrap .data-table.rpt-table-exam-l1 thead th.col-exam-overrides .th-btn,
.rpt-table-wrap .data-table.rpt-table-exam-l1 tbody td.col-exam-overrides {
  padding-inline-start: 18px;
}

/* Institute Performance L3 — tests-in-branch grid. Student and Examiner
   are pinned to a moderate width (the marquee handles longer names with a
   slide-on-hover); the freed horizontal space goes to the metadata
   columns so they don't feel cramped. The Attempt column is widened to
   fit the new "n of m" format. */
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-tfn      { width: 122px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-date     { width: 110px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-student  { width: 168px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-veh      { width: 78px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-result   { width: 104px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-attempt  { width: 96px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-dur      { width: 100px; }
.rpt-table-wrap .data-table.rpt-table-inst-l3 colgroup .col-examiner { width: 148px; }
/* Attempt cell — the index number reads bold (the eye lands on it as the
   headline), the "of N" trailer is small + muted (just there for context). */
.cell-attempt-num {
  font-weight: 600;
  color: var(--ink);
  font-size: 13px;
}
.cell-attempt-of {
  color: var(--ink-4);
  font-weight: 400;
  font-size: 11.5px;
  margin-inline-start: 3px;
}
[dir="rtl"] .cell-attempt-of { font-size: 12.5px; }

/* Test Analysis parameter tables — give the parameter column most of the
   width because parameter names can be long full sentences ("Touching the
   exit line (partially or pass it completely) without completing maneuver").
   The numeric / severity columns only need enough room for their content. */
.rpt-table-wrap .data-table.rpt-table-params colgroup .col-rank       { width: 36px; }
.rpt-table-wrap .data-table.rpt-table-params colgroup .col-sev        { width: 100px; }
.rpt-table-wrap .data-table.rpt-table-params colgroup .col-pct-narrow { width: 76px; }
/* Layout-fixed lets the colgroup widths stick instead of being overridden
   by content-based widening from a long parameter name. */
.rpt-table-wrap .data-table.rpt-table-params { table-layout: fixed; }
.rpt-table-wrap .data-table.rpt-table-params td:first-child,
.rpt-table-wrap .data-table.rpt-table-params td:nth-child(2) {
  /* The parameter cell uses .cell-marquee for ellipsis + slide-on-hover. */
  overflow: hidden;
}
/* Trailing numeric columns (Count + Failed) are left-aligned, matching
   the rest of the report tables. Add a small right padding on the last
   column so the data doesn't read as flush with the table border. */
.rpt-table-wrap .data-table.rpt-table-params thead th:last-child .th-btn,
.rpt-table-wrap .data-table.rpt-table-params tbody td:last-child {
  padding-inline-end: 14px;
}

/* Cell-marquee — host wrapper around a single .marquee-inner span.
   On hover the inner span gets a `transform: translateX(...)` animation
   (GPU-composited, no per-frame layout) so long text smoothly slides to
   reveal its end. The host has overflow:hidden + a fade-gradient mask on
   the trailing edge as a "there's more" cue (replaces text-overflow:
   ellipsis, which is incompatible with translating an inline-block child
   past the container edge). */
.cell-marquee {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  max-width: 100%;
  cursor: default;
  /* Host needs to be the positioning context for the inner span. */
  position: relative;
}
.cell-marquee .marquee-inner {
  display: inline-block;
  white-space: nowrap;
  /* Default state: no transform. Animation sets transform via Web
     Animations API; we keep transition off here so the explicit duration
     wins. `will-change: transform` keeps the browser composited path warm
     when the user is about to hover. */
  will-change: transform;
}
/* Fade-gradient mask — applied ONLY when the host is actually too narrow
   for its inner text. The `is-overflowing` class is toggled by a JS scan
   that compares the inner span's scrollWidth to the host's clientWidth.
   Doing it this way avoids fading the last few characters of short names
   that happen to sit inside a short column. */
.cell-marquee.is-overflowing {
  -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 14px), transparent 100%);
          mask-image: linear-gradient(to right, black 0, black calc(100% - 14px), transparent 100%);
}
[dir="rtl"] .cell-marquee.is-overflowing {
  -webkit-mask-image: linear-gradient(to left, black 0, black calc(100% - 14px), transparent 100%);
          mask-image: linear-gradient(to left, black 0, black calc(100% - 14px), transparent 100%);
}

/* Numbers in Reports tables align with text (start), not flush-right.
   Tables here are read top-to-bottom to compare entries, not column-totalled,
   so left/start alignment groups the value next to its row label. */
.rpt-table-wrap .data-table .th-btn { justify-content: flex-start; }
.rpt-table-wrap .data-table .th-btn[style*="justify-content: center"] { justify-content: center !important; }
/* Header icon slot — replaces the semantic icon with a sort indicator on
   hover or when this column is the active sort key. All three children
   (default icon, hover hint, active arrow) share the same slot via
   absolute positioning, so swapping never shifts the label horizontally. */
.th-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.th-icon > svg:first-child { width: 14px; height: 14px; }
.th-icon > svg { transition: opacity var(--t-fast); }
.th-icon-hint,
.th-icon-active {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
/* Defaults: semantic icon visible, hover/active SVGs hidden. */
.th-icon-hint   { opacity: 0; }
.th-icon-active { opacity: 0; }
/* Hover (no active sort yet) — swap the semantic icon out for the hint
   stack at the SAME position. */
.th-btn:hover:not(.is-sorted) .th-icon > svg:first-child { opacity: 0; }
.th-btn:hover:not(.is-sorted) .th-icon-hint              { opacity: 1; }
/* Sorted column — semantic icon and hint are off; the active arrow takes
   the slot. */
.th-btn.is-sorted .th-icon > svg:first-child { opacity: 0; }
.th-btn.is-sorted .th-icon-hint              { opacity: 0; }
.th-btn.is-sorted .th-icon-active            { opacity: 1; }

/* Total row sits right under the last data row but should read like a
   header band — match thead height so the eye lands on it as a summary. */
.rpt-table-wrap .data-table tr.rpt-totals td {
  padding-top: 12px;
  padding-bottom: 12px;
  font-size: 12.5px;
}
[dir="rtl"] .rpt-table-wrap .data-table tr.rpt-totals td { font-size: 13px; }
/* Hint above tables that contain drillable rows */
.rpt-drill-hint {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px;
  color: var(--ink-3);
  padding-inline-start: 4px;
  margin-top: -2px;
}
[dir="rtl"] .rpt-drill-hint { font-size: 12.5px; }
.rpt-drill-hint svg { color: var(--brand-700); }
.rpt-table-wrap .data-table tr.rpt-totals {
  font-weight: 600;
  background: var(--bg-sunken);
}
.rpt-table-wrap .data-table tr.rpt-totals td { border-top: 1px solid var(--line-strong); }
.rpt-table-wrap .data-table .cell-strong { font-weight: 600; color: var(--ink); }
.rpt-table-wrap .data-table .cell-sub { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
.rpt-table-wrap .data-table .cell-ok  { color: var(--ok); }
.rpt-table-wrap .data-table .cell-err { color: var(--err); }

/* Two-column report layout (e.g. Maneuver L1: summary + Top 10) */
.rpt-split {
  display: grid;
  /* Equal-width side-by-side panes at ≥1080 — the previous 1.1:1
     weighting biased the left pane (Maneuver Performance Summary at
     L1, "Failed parameters in <maneuver>" at L2) to make room for
     longer maneuver names, but left the two cards visually
     misaligned. Equal columns keep the row balanced and the page
     reads as a clean grid. The ≤1079 query collapses this to a
     single column anyway, so the equal-fr split applies only at
     wide viewports. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 14px;
}
.rpt-split-pane { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.rpt-split-head {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  letter-spacing: 0.01em;
  padding-inline-start: 2px;
}
[dir="rtl"] .rpt-split-head { font-size: 13px; letter-spacing: 0; }

/* Section header (used by Schedules, Templates, Builder header rows) */
.rpt-section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.rpt-section-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  line-height: 1.2;
}
[dir="rtl"] .rpt-section-title { font-size: 17px; letter-spacing: 0; }
.rpt-section-sub {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-top: 4px;
  line-height: 1.4;
}
[dir="rtl"] .rpt-section-sub { font-size: 13px; }
.rpt-section-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }

/* Maneuver cards (Institute L4 — single test detail) */
.maneuver-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.maneuver-card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px;
  box-shadow: var(--sh-xs);
}
.maneuver-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.maneuver-card-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
[dir="rtl"] .maneuver-card-name { font-size: 14px; }
.maneuver-card-meta {
  display: flex; align-items: center; gap: 12px;
  font-size: 12px;
  color: var(--ink-3);
}
[dir="rtl"] .maneuver-card-meta { font-size: 12.5px; }
.maneuver-card-meta strong {
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.maneuver-card-top {
  font-size: 11.5px;
  color: var(--ink-3);
  border-top: 1px solid var(--line);
  padding-top: 6px;
  line-height: 1.4;
}
[dir="rtl"] .maneuver-card-top { font-size: 12px; }

/* Inline icon-button row (used in Schedules + Templates lists) */
.row-icon-btns { display: inline-flex; align-items: center; gap: 2px; }
.row-icon-btn {
  width: 28px; height: 28px;
  display: inline-grid; place-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  cursor: pointer;
  padding: 0;
  transition: background var(--t-fast), color var(--t-fast);
}
.row-icon-btn:hover { background: var(--brand-tint); color: var(--brand-700); }

/* Templates table — inherits the standard data-table styling. The
   row height is bumped slightly (56px vs 60px default) because
   the cells stack two lines (name + description). Scroll +
   freeze behavior comes from the global ≤1079 rule — same as
   every other table. */
.tpl-table-card .data-table tbody td,
.tpl-table-card .data-table tbody tr { height: 56px; }
.tpl-name-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.tpl-name-cell .tpl-name-text {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
[dir="rtl"] .tpl-name-cell .tpl-name-text { font-size: 13.5px; }
.tpl-name-cell .tpl-desc {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
[dir="rtl"] .tpl-name-cell .tpl-desc { font-size: 12px; }

/* Visibility pill — Private (lock icon, neutral ink) vs Public (globe
   icon, brand tint). Same pill grammar; the icon + colour combo
   carries the semantic. */
.tpl-shared {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px;
  font-weight: 500;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
}
[dir="rtl"] .tpl-shared { font-size: 11.5px; }
.tpl-shared.is-public {
  color: var(--brand-700);
  border-color: color-mix(in oklch, var(--brand-500) 30%, var(--line));
  background: var(--brand-tint);
}
.tpl-shared.is-private {
  color: var(--ink-3);
  background: var(--bg-sunken);
}
/* Shared-with-specific-users — reads as in-between Private (closed)
   and Public (wide-open): an explicit recipient list, so we use a
   subtle tint that sits between the brand-tint of Public and the
   muted sunken of Private. Same shape as the others; the icon +
   secondary count line carry the rest of the semantic. */
.tpl-shared.is-shared {
  color: var(--ink-2);
  border-color: color-mix(in oklab, var(--brand-500) 18%, var(--line));
  background: color-mix(in oklab, var(--brand-500) 6%, var(--bg-raised));
}

/* Lifecycle status pill — three semantic colors so the user can
   scan the column at a glance:
     • active   — ok-green (running + scheduled)
     • inactive — neutral ink (paused but saved)
     • draft    — warn-amber (not yet shipped, editable)
   The colored dot + text-tint is enough; no border to keep the row
   line scannable. */
.tpl-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 500;
  padding: 2px 9px;
  border-radius: var(--r-pill);
}
[dir="rtl"] .tpl-status { font-size: 12.5px; }
.tpl-status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 12%, transparent);
}
.tpl-status--active   { color: var(--ok); background: color-mix(in oklab, var(--ok) 10%, var(--bg-raised)); }
.tpl-status--active   .tpl-status-dot { background: var(--ok); }
.tpl-status--inactive { color: var(--ink-3); background: var(--bg-sunken); }
.tpl-status--inactive .tpl-status-dot { background: var(--ink-3); }
.tpl-status--draft    { color: var(--warn, oklch(0.62 0.16 75)); background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 10%, var(--bg-raised)); }
.tpl-status--draft    .tpl-status-dot { background: var(--warn, oklch(0.72 0.15 75)); }

/* Disabled action button — keeps icon visible but greys it out + no
   pointer events. Tooltip on the same element explains why (e.g.
   "Deactivate to edit"). */
.row-icon-btn.is-disabled,
.row-icon-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.row-icon-btn.is-disabled:hover,
.row-icon-btn:disabled:hover { background: transparent; color: var(--ink-3); }

/* Custom-Reports filter bar — search input on the left, multi-
   select pills + date-range pill + Clear on the right. Reuses the
   `.filter-bar--reports` row layout so it visually matches the
   filter bars on the other report tabs. */
.tpl-filter-bar { margin-bottom: 8px; }
/* Search width trimmed 10% (320 → 288) so the pill row gets a bit
   more breathing room on the same flex line. The ≤1279 query trims
   another step (260 ≈ 280 × 10%) for narrower viewports. */
.tpl-filter-search { flex: 0 0 288px; }
/* Pills stay on a single row inside the Custom Reports filter bar.
   The chained-class selector (`.filter-bar--reports.tpl-filter-bar`)
   bumps specificity to 3 classes so this `nowrap` declaration wins
   over `.filter-bar--reports .filter-pills { flex-wrap: wrap }`
   declared later in the file (same-specificity cascade would
   otherwise pick the later rule, which is what was making the pills
   break onto a 2nd row in the 900–905 zone). At ≤899 the bar itself
   wraps so the pills sit on their own row beneath the search; nowrap
   on the pills container is still correct there since the row is
   already wide enough to hold all pills inline. */
.filter-bar--reports.tpl-filter-bar .filter-pills { flex-wrap: nowrap; }
@media (max-width: 1279px) {
  /* Narrow viewports: shrink the search input so the pills + Clear
     fit beside it on one line. 252 ≈ 10% off the previous 280
     baseline; still leaves room for typing template names. */
  .tpl-filter-bar .filter-search { flex: 0 1 252px; }
}

/* Custom Reports templates table —
   At ≥1080 the table fills its container with no horizontal scroll;
   the flex Template column absorbs all remaining space (and shrinks
   along with the viewport since the meta columns are fixed-width:
   110 + 90 + 120 + 95 + 70 + 128 + 240 = 853px). At ≤1079 the global
   `.table-scroll` rule in the breakpoint query freezes the table at
   1046px min-width and enables horizontal scroll, so Template stops
   shrinking the moment the freeze kicks in. */
/* Empty-row inside the templates table — used when filters narrow
   the list to zero matches. Spans all columns and centers the copy
   so the table stays visually balanced rather than collapsing. */
.tpl-empty-row {
  text-align: center;
  padding: 32px 16px !important;
  color: var(--ink-3);
  font-size: 13px;
}

/* Visibility cell — pill stacked above a small "shared with" caption
   so the cell carries who-can-see-this context without needing a
   separate column. */
.tpl-vis-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  min-width: 0;
}

/* Last-run cell — date on top (regular ink), time below in mono +
   muted ink. Two-line stack lets the column stay narrow while
   surfacing precise timing. */
.tpl-when-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.tpl-when-date {
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.2;
}
.tpl-when-time {
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

/* Last-run-by cell — icon + name. System runs get a cog glyph in
   brand-ink; manual runs get a person silhouette in muted ink. The
   icon's hue is the primary "automated vs human" cue. */
.tpl-by-cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.tpl-by-cell svg { color: var(--ink-3); flex-shrink: 0; }
.tpl-by-cell.is-system {
  color: var(--brand-700);
}
.tpl-by-cell.is-system svg { color: var(--brand-600); }
.tpl-shared-with {
  font-size: 11px;
  color: var(--ink-4);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
[dir="rtl"] .tpl-shared-with { font-size: 11.5px; }

.tpl-actions-cell,
.sched-actions-cell {
  /* Block-level flex (not inline) + width:100% + justify-content
     flex-end keeps the action cluster pinned to the row's end even
     when buttons are conditionally hidden (e.g. Run is suppressed
     for draft templates). With inline-flex the cluster shrunk to
     content width and drifted left because `text-align: end` on
     the parent td didn't reliably re-position it. Now the cluster
     always anchors to the right border, so draft rows visually
     align with active rows above and below. */
  display: flex;
  width: 100%;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  /* Matches the system-standard cell padding so the visual gap
     between this cell and the previous one is the same as every
     other column transition. End-padding 16px gives the rightmost
     icon button visible breathing room from the table's right
     border (the column is 240/260px wide to fit Run + 4 icons +
     this padding without crushing). Shared between Custom Reports
     and Schedules so both action clusters read identically. */
  padding-inline-start: 6px;
  padding-inline-end: 16px;
}
.tpl-actions-cell .btn,
.sched-actions-cell .btn { margin-inline-end: 4px; }
.tpl-actions-cell .row-icon-btn,
.sched-actions-cell .row-icon-btn {
  /* Always-visible (not just on row-hover) since this is the only
     way to access these actions in the table rows. */
  opacity: 1;
}
.tpl-actions-cell .row-icon-btn.is-disabled,
.tpl-actions-cell .row-icon-btn:disabled,
.sched-actions-cell .row-icon-btn.is-disabled,
.sched-actions-cell .row-icon-btn:disabled {
  opacity: 0.4;
}
/* CRITICAL: data-tip tooltips on the action buttons (Edit / Duplicate
   / Schedule) anchor above the button via a ::after pseudo-element.
   The .data-table td has `overflow: hidden` by default (so other
   columns can ellipsize long text), which clips the tooltip at the
   cell's top edge. Same fix as `.cell-vehicle` and `.cell-result`:
   opt this cell out of the table-wide overflow rule. The tooltip
   also needs a higher z-index than the sticky thead (z:10). */
.data-table td.tpl-actions-cell,
.data-table td.sched-actions-cell { overflow: visible; }

/* ─────────── UNIFIED IN-TABLE TOOLTIP RULE ───────────
   Every tooltip rendered inside a `.data-table tbody` (action
   buttons, vehicle tags, result pills, etc.) anchors BELOW the
   trigger and sits at z-index 200. Why:
     • The sticky thead (`position: sticky; z-index: 10`) creates
       its own stacking context. Tooltips rendered ABOVE the trigger
       on row 1 were being clipped or covered by the header even
       with z-index workarounds. Flipping to below sidesteps the
       conflict — the tip paints into the row-line gap below the
       row, never into the header's vertical territory.
     • Sharing one rule means every per-feature class (vehicle-tag,
       result-flat, row-icon-btn in tpl/sched action cells, …)
       behaves identically without each one redefining z-index +
       offset overrides that drift apart over time. */
.data-table tbody [data-tip]::after {
  bottom: auto;
  top: calc(100% + 8px);
  z-index: 200;
}

/* Builder layout */
.builder {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 14px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 14px;
  box-shadow: var(--sh-xs);
}
.builder-side {
  border-inline-end: 1px solid var(--line);
  padding-inline-end: 12px;
}
.builder-main {
  padding: 6px 4px 0 12px;
  display: flex;
  flex-direction: column;
  /* Reserve room for the wizard footer at the bottom of the panel.
     min-height: 0 so the inner content area can shrink and scroll
     instead of pushing the footer below the viewport. */
  min-height: 0;
}
[dir="rtl"] .builder-main { padding: 6px 12px 0 4px; }
.builder-main-content {
  flex: 1 1 auto;
  min-height: 0;
}
/* Wizard nav footer — Cancel pinned to the start, Back/Next/Save
   group on the end. Sticky bottom-aligned via flex above so it stays
   visible whether the step content is short or long-scrolling. */
.builder-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  margin-top: 16px;
  border-top: 1px solid var(--line);
  /* On narrow viewports the right-side group (Back + Save-as-draft +
     Save template at Step 7) overflows the builder card. Allow the
     footer to wrap so Cancel stays left and the right group drops
     onto its own row instead of clipping past the edge. */
  flex-wrap: wrap;
  row-gap: 8px;
}
.builder-foot-end {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Wrap inside the right-side group so Back / Save-as-draft / Save
     template can stack at very narrow widths without breaking the
     wizard footer's containment. */
  flex-wrap: wrap;
  justify-content: flex-end;
  row-gap: 8px;
}

.builder-step-head { margin-bottom: 14px; }
.builder-step-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
[dir="rtl"] .builder-step-title { font-size: 17px; letter-spacing: 0; }
.builder-step-sub {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-top: 4px;
  line-height: 1.5;
  max-width: 640px;
}
[dir="rtl"] .builder-step-sub { font-size: 13px; }
.builder-step-stub { display: flex; flex-direction: column; gap: 12px; }

/* Step 1 — data sources grid */
.source-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
}
.source-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  position: relative;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.source-card:hover { border-color: var(--line-strong); background: color-mix(in srgb, var(--brand-500) 2%, var(--bg-raised)); }
.source-card.is-on {
  border-color: color-mix(in oklch, var(--brand-500) 40%, var(--line));
  background: var(--brand-tint);
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--brand-500) 25%, transparent);
}
.source-card-icon {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  background: var(--bg-sunken);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  flex-shrink: 0;
}
.source-card.is-on .source-card-icon { background: var(--brand-tint-2); color: var(--brand-700); }
.source-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.source-card-name { font-size: 13px; font-weight: 600; color: var(--ink); }
[dir="rtl"] .source-card-name { font-size: 14px; }
.source-card-desc { font-size: 11.5px; color: var(--ink-3); }
[dir="rtl"] .source-card-desc { font-size: 12.5px; }
.source-card-check {
  position: absolute;
  top: 8px;
  inset-inline-end: 8px;
  width: 18px; height: 18px;
  display: grid; place-items: center;
  background: var(--brand-600);
  color: var(--brand-ink);
  border-radius: 50%;
}

/* Step 2 — fields-checkbox grid. Min-card 220 lets us hit 3
   columns at builder-main width ≈740 (which is what we get at
   1079 viewport: 1047 main inner − 240 stepper − 14 gap −
   builder padding/main padding ≈ 740). At 1280+ viewport the
   main grows enough for 4 columns. With 8 groups total that
   gives a 2-row × 4-col arrangement on desktop or 3-row × 3-col
   at the 1079 freeze. */
.fields-grid {
  display: grid;
  /* Lock to exactly 3 columns at ≥1080 — at wider viewports the grid
     would otherwise fit a 4th column and disrupt the visual rhythm of
     the 8 field groups. The ≤1079 media query drops to 2 columns. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
.fields-group {
  display: flex; flex-direction: column; gap: 4px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px 12px;
  /* Cap each group's height so the grid stays balanced — long
     groups (20+ fields) scroll internally instead of stretching the
     whole row. The list inside has its own overflow:auto. */
  max-height: 280px;
}
.fields-group-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  overflow-y: auto;
  /* Reserve scrollbar space so the rightmost checkbox label doesn't
     shift when scroll kicks in. */
  scrollbar-gutter: stable;
  /* Pad inline-end so the scrollbar (when shown) doesn't visually
     hug the labels. */
  padding-inline-end: 2px;
}
.fields-group-name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin-bottom: 4px;
}
[dir="rtl"] .fields-group-name { font-size: 12px; letter-spacing: 0; text-transform: none; }
.fields-check {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 4px;
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
  border-radius: var(--r-sm);
}
[dir="rtl"] .fields-check { font-size: 13px; }
.fields-check:hover { background: var(--bg-sunken); }
.fields-check input[type="checkbox"] {
  width: 14px; height: 14px;
  accent-color: var(--brand-600);
  cursor: pointer;
}

/* Step 2 — toolbar (search + global Clear) above the field grid.
   The search re-uses the global `.filter-search` styling so it
   matches the look of every other search input in the app. */
.fields-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.fields-search { flex: 1 1 320px; }
.fields-toolbar .filter-reset { flex-shrink: 0; }
.fields-empty {
  grid-column: 1 / -1;
  padding: 32px 16px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13px;
  background: var(--bg-raised);
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
}

/* Per-group header with name + Select all / Clear toggle. */
.fields-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}
.fields-group-head .fields-group-name { margin-bottom: 0; }
.fields-group-toggle {
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
  background: transparent;
  border: 0;
  padding: 2px 4px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklab, var(--ink-3) 50%, transparent);
  transition: color var(--t-fast), text-decoration-color var(--t-fast);
}
[dir="rtl"] .fields-group-toggle { font-size: 12px; }
.fields-group-toggle:hover { color: var(--brand-700); text-decoration-color: var(--brand-700); }
.fields-group-toggle.is-all { color: var(--brand-700); text-decoration-color: var(--brand-700); }

/* Step 3 — selected-values chips below each filter dropdown. Lets
   the user see all current selections at a glance without expanding
   the pill, plus remove individual values with the × on each chip
   and a global Clear at the end of the row. */
.builder-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
  align-items: center;
}
.builder-filter-empty {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--ink-4);
  font-style: italic;
}
[dir="rtl"] .builder-filter-empty { font-size: 12px; }
.builder-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 10px;
  background: var(--brand-tint);
  border: 1px solid color-mix(in oklch, var(--brand-500) 25%, var(--line));
  border-radius: var(--r-pill);
  font-size: 12px;
  color: var(--brand-700);
}
[dir="rtl"] .builder-filter-chip { padding: 2px 10px 2px 4px; font-size: 12.5px; }
.builder-filter-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 0;
  background: transparent;
  color: var(--brand-700);
  cursor: pointer;
  padding: 0;
  transition: background var(--t-fast);
}
.builder-filter-chip-x:hover { background: color-mix(in oklab, var(--brand-500) 20%, transparent); }
.builder-filter-clear {
  margin-inline-start: 4px;
  /* Inherits the standard `.filter-reset` underline-text style. */
}

/* Step 5 — limit-mode toggle + conditional number input. */
.builder-limit-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Step 7 — schedule row. Schedule preset, optional day-picker, and
   the Run-at time picker share one flex row so related controls
   stay grouped. Each field uses a `flex: 1 1 180px` basis so they
   wrap to a stack on narrow viewports while filling the row evenly
   on wider ones. The schedule preset gets a slightly larger basis
   since its options are longer ("Bi-weekly on a specific day"). */
.builder-sched-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}
.builder-sched-row .builder-field {
  flex: 1 1 180px;
  min-width: 0;
}
.builder-sched-row .builder-sched-primary { flex: 1 1 240px; }
.builder-sched-row .builder-sched-day     { flex: 0 1 160px; }
.builder-sched-row .builder-sched-time    { flex: 0 1 240px; min-width: 0; }

/* Step 7 — custom schedule mini-form. Groups date+time on one row,
   repeat on its own row below, inside a subtly-tinted container so
   it visually nests under the schedule preset that opened it. */
.builder-custom-sched {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

/* Steps 3–7 — shared form layout. Stacked vertically with consistent
   field labels, controls, and hint text. `.builder-row` lets two
   fields sit side-by-side on wide viewports; collapses to stacked
   below 720px so labels stay legible. */
.builder-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 720px;
}
.builder-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.builder-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 720px) {
  .builder-row { grid-template-columns: 1fr; }
}
.builder-field-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  letter-spacing: 0.02em;
}
[dir="rtl"] .builder-field-label { font-size: 12.5px; letter-spacing: 0; }
.builder-field-req { color: var(--err); margin-inline-start: 2px; }
.builder-field-hint {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.4;
}
[dir="rtl"] .builder-field-hint { font-size: 12px; }
.builder-checks {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Form controls — match the rest of the design system. Native
   <select> styling with a chevron pseudo, native <input> for text +
   number, native <textarea>. */
.builder-input,
.builder-textarea,
.builder-select {
  width: 100%;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 9px 12px;
  outline: 0;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.builder-input:focus,
.builder-textarea:focus,
.builder-select:focus {
  border-color: color-mix(in oklab, var(--brand-500) 45%, var(--line));
  box-shadow: 0 0 0 3px var(--ring);
}
.builder-input--narrow { width: 140px; }
.builder-textarea { resize: vertical; min-height: 60px; line-height: 1.4; }
.builder-select {
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-inline-end: 32px;
  cursor: pointer;
}
[dir="rtl"] .builder-select {
  background-position: 13px 50%, 18px 50%;
  background-image: linear-gradient(135deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(45deg, var(--ink-3) 50%, transparent 50%);
  padding-inline-end: 12px;
  padding-inline-start: 32px;
}
.builder-select:disabled { color: var(--ink-4); cursor: not-allowed; }

/* SingleSelect / TimePicker / SingleDatePicker — custom dropdown
   trigger that looks like a styled input but opens the global
   `.filter-menu` popover on click. Replaces native <select> in the
   builder so every dropdown shares the same look + open state +
   highlighted item grammar as the filter pills elsewhere in the app. */
.builder-select-wrap { position: relative; display: inline-flex; }
.builder-select-wrap.is-full { display: flex; width: 100%; }
.builder-select-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: auto;
  min-width: 100px;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 9px 12px;
  cursor: pointer;
  text-align: start;
  white-space: nowrap;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.builder-select-trigger.is-full { width: 100%; }
.builder-select-trigger.is-narrow { min-width: 80px; padding: 9px 10px; }
.builder-select-trigger:hover { border-color: var(--line-strong); }
.builder-select-trigger:focus-visible,
.builder-select-trigger.is-open {
  outline: 0;
  border-color: color-mix(in oklab, var(--brand-500) 45%, var(--line));
  box-shadow: 0 0 0 3px var(--ring);
}
.builder-select-trigger > svg { color: var(--ink-3); flex-shrink: 0; }
.builder-select-trigger.is-open > svg:last-child { transform: rotate(180deg); }
.builder-select-trigger-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* TimePicker — three SingleSelects laid out horizontally with a
   colon separator between hour and minute. */
.builder-time-picker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
}
.builder-time-sep {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink-3);
  user-select: none;
  line-height: 1;
}
/* Compact the three SingleSelects inside a TimePicker so HH/MM/AM-PM
   fit inside the 240px `.builder-sched-time` field without overflowing
   the container border. The default `.builder-select-trigger` min-width
   of 100px would push three selects + separators well past 240px. */
.builder-time-picker .builder-select-trigger {
  min-width: 64px;
  padding: 9px 8px;
}

/* SingleDatePicker popover — calendar-only (no presets list). The
   month grid is the same `CalendarMonth` used by DateRangeFilter so
   the look matches the filter calendar pixel-for-pixel. */
.single-date-menu {
  padding: 0;
}
.single-date-menu .cal-month {
  /* Slightly tighter padding than the range version since there's
     just one month, not two side-by-side. */
  padding: 8px;
}

/* No-results row inside the SingleSelect menu (shown when the search
   query has no matches). */
.filter-menu-empty {
  padding: 12px;
  text-align: center;
  color: var(--ink-4);
  font-size: 12.5px;
}
.builder-input::placeholder,
.builder-textarea::placeholder { color: var(--ink-4); }

/* Segmented control for step 5 sort direction. Reuses the same `.seg`
   tokens as the tweaks panel so themes apply consistently. */
.builder-seg {
  display: inline-flex;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 2px;
  align-self: flex-start;
}
.builder-seg .seg-opt {
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-3);
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.builder-seg .seg-opt:hover { color: var(--ink); }
.builder-seg .seg-opt.is-on {
  background: var(--bg-raised);
  color: var(--ink);
  box-shadow: var(--sh-xs);
}

/* Step 7 — visibility radio group. Each option is a card with icon +
   name + hint. Selected card gets brand-tint + primary border. Same
   grammar as the GenerateReportModal's format picker so the wizard's
   pattern stays familiar. */
.builder-radio-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Visibility segmented control — used by Builder Step 7 + the
   Create-schedule modal. Three pills on a single row (icon + name)
   instead of stacked radio cards. The active pill's hint shows
   beneath in muted ink so the explanatory copy still has a place. */
.vis-seg {
  display: inline-flex;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 2px;
  gap: 2px;
  align-self: flex-start;
  flex-wrap: wrap;
}
.vis-seg-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-3);
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  white-space: nowrap;
}
[dir="rtl"] .vis-seg-opt { font-size: 13px; }
.vis-seg-opt:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 4%, transparent); }
.vis-seg-opt.is-on {
  background: var(--bg-raised);
  color: var(--brand-700);
  box-shadow: 0 1px 2px -1px rgba(0,0,0,0.08);
}
.vis-seg-opt > svg { color: currentColor; flex-shrink: 0; }
.vis-seg-opt:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}
.vis-seg-hint {
  font-size: 11.5px;
  color: var(--ink-3);
  margin-top: 4px;
  line-height: 1.35;
}
[dir="rtl"] .vis-seg-hint { font-size: 12.5px; }
@media (max-width: 459px) {
  /* Pills stack to two rows on tiny phones — the segmented row
     would otherwise overflow horizontally past the modal edge. */
  .vis-seg { display: flex; }
  .vis-seg-opt { flex: 1 1 0; justify-content: center; }
}
.builder-radio-opt {
  display: grid;
  /* col 1 = radio dot, col 2 = icon, col 3 = name+hint stack */
  grid-template-columns: auto auto 1fr;
  column-gap: 10px;
  align-items: center;
  padding: 10px 12px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.builder-radio-opt:hover { border-color: var(--line-strong); }
.builder-radio-opt.is-on {
  background: var(--brand-tint);
  border-color: color-mix(in oklch, var(--brand-500) 35%, var(--line));
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--brand-500) 20%, transparent);
}
.builder-radio-opt input[type="radio"] {
  grid-column: 1;
  grid-row: 1 / span 2;
  accent-color: var(--brand-600);
  cursor: pointer;
  align-self: center;
}
.builder-radio-opt > svg {
  grid-column: 2;
  grid-row: 1 / span 2;
  color: var(--ink-3);
  align-self: center;
}
.builder-radio-opt.is-on > svg { color: var(--brand-700); }
.builder-radio-name {
  grid-column: 3;
  grid-row: 1;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.builder-radio-hint {
  grid-column: 3;
  grid-row: 2;
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.35;
}
[dir="rtl"] .builder-radio-hint { font-size: 12px; }

/* Step 6 — config summary. Stacked label/value rows mirror the
   GenerateReportModal's confirmation block so the wizard's review
   surface uses the same grammar. */
.builder-summary-label {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-bottom: 12px;
}
[dir="rtl"] .builder-summary-label { font-size: 13px; }
.builder-summary {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.builder-summary-row {
  display: grid;
  /* The label column reads as a "row header" — fixed-width and
     visually distinct from the value column via background +
     divider. Mirrors the data-table thead grammar (sunken bg,
     uppercase + small + muted typography) so the summary feels
     like a transposed report header. */
  grid-template-columns: 200px 1fr;
  align-items: stretch;
  border-bottom: 1px solid var(--line);
}
.builder-summary-row:last-child { border-bottom: 0; }
.builder-summary-row dt {
  background: var(--bg-sunken);
  border-inline-end: 1px solid var(--line);
  padding: 12px 14px;
  margin: 0;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
}
[dir="rtl"] .builder-summary-row dt { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.builder-summary-row dd {
  font-size: 13px;
  color: var(--ink);
  margin: 0;
  min-width: 0;
  padding: 12px 14px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
[dir="rtl"] .builder-summary-row dd { font-size: 13.5px; }
.builder-summary-empty {
  color: var(--ink-4);
  font-style: italic;
}
.builder-summary-chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}
.builder-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
}
[dir="rtl"] .builder-chip { font-size: 12.5px; }
.builder-summary-cta {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}
/* Inline preview generator. Uses the same gen-progress-bar tokens
   as the export modal so the visual language is consistent. */
.builder-preview-loader {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 32px 24px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
/* Preview header — caption on the left, regenerate button on the
   right. Sits above the table. */
.builder-preview-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.builder-preview-meta {
  font-size: 11.5px;
  color: var(--ink-3);
  letter-spacing: 0.02em;
}
[dir="rtl"] .builder-preview-meta { font-size: 12px; }

.builder-step7-hint { margin-top: 4px; }
/* Allow both axes to scroll inside the preview card. The default
   .table-card has `overflow-x: clip` (so other tables can ellipsize
   long cells) but the preview wants horizontal scroll when the user
   selects many fields — they'd rather scroll than have each column
   crushed to ~30px. */
.builder-preview-card { overflow: hidden; }
.builder-preview-card .table-scroll {
  max-height: 380px;
  overflow: auto;
}
/* Switch to `auto` table-layout so each column sizes to its content
   instead of the default `fixed` 1/N split. Combined with `width:
   max-content` and `min-width: 100%`, the table grows beyond the
   container and triggers horizontal scroll only when needed. Each
   cell gets a sensible min-width so headers never get crushed. */
.builder-preview-card .data-table {
  table-layout: auto;
  width: max-content;
  min-width: 100%;
}
.builder-preview-card .data-table th,
.builder-preview-card .data-table td {
  min-width: 130px;
  white-space: nowrap;
}
.builder-preview-card .data-table th {
  padding: 0;
}
/* Sticky header within the scroll container so it stays visible as
   the user scrolls preview rows. Uses the same `--bg-sunken` as
   every other data-table header (and a bottom border) so the header
   row is visually distinct from the first body row — which it
   wasn't when we earlier overrode the background to `--bg-raised`
   (it merged with odd-zebra rows). The `top: 0` anchor is what
   differs from the global thead rule (which uses `top: 64px` to
   clear the topnav); inside this scroll container the sticky frame
   is the container itself. */
.builder-preview-card .data-table thead th {
  position: sticky;
  top: 0;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--line);
  z-index: 5;
}

/* ─────────── LOGIN PAGE (login.html) ───────────
   Lightweight standalone page — doesn't depend on React. Uses
   the same tokens (brand color, ink ramp, radii) as the rest of
   the app so the visual language stays consistent. The page is
   entered first; on submit, the user lands at saved-tests.html.
   `index.html` is a tiny redirect stub that bounces here so the
   GitHub Pages root URL still works. */
.auth-body {
  min-height: 100vh;
  background: var(--bg-sunken);
  background-image:
    radial-gradient(1200px 600px at 110% -10%, color-mix(in oklab, var(--brand-500) 18%, transparent), transparent 60%),
    radial-gradient(900px 600px at -10% 110%, color-mix(in oklab, var(--brand-500) 14%, transparent), transparent 60%);
  color: var(--ink);
  font-family: var(--font-ui);
  display: flex;
  flex-direction: column;
}
[dir="rtl"] .auth-body { font-family: var(--font-ui-ar, var(--font-ui)); }
/* Dark-mode gradient — same composition (top-right + bottom-left
   corner blooms) but the alpha needs to be ~2× the light-mode
   values to read as a clear brand presence. Two reasons the same
   percentages undershoot in dark:
     1. The radial center is parked at `110% -10%` / `-10% 110%`
        — *outside* the viewport. The visible corner only sees the
        gradient's falloff curve, so the on-screen alpha is roughly
        half of the stop value at the centre.
     2. Eye adaptation to a near-black canvas reads the result
        through a different gain curve — small Lab-L deltas that
        are obvious in light mode get absorbed by the dark surround.
   Pushing to 30% / 22% lands the visible portion of the bloom at
   a perceptual level that mirrors light mode, while the natural
   chroma damping from the alpha keeps it a soft tint, not a glow. */
[data-theme="dark"] .auth-body {
  background-image:
    radial-gradient(1200px 600px at 110% -10%, color-mix(in oklab, var(--brand-500) 38%, transparent), transparent 60%),
    radial-gradient(900px 600px at -10% 110%, color-mix(in oklab, var(--brand-500) 28%, transparent), transparent 60%);
}
.auth-page {
  flex: 1;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 16px;
  padding: 20px 28px 16px;
  min-height: 100vh;
}

/* Top bar — logo on the start side, theme + language toggles on
   the end. Matches the topnav's .tenant-icon sizing so the logo
   reads as the same brand mark seen throughout the app. */
.auth-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.auth-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}
.auth-brand .tenant-icon {
  width: 144px;
  height: 36px;
}
.auth-controls {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.auth-ctl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  min-width: 36px;
  padding: 0 12px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-2);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.auth-ctl:hover { background: var(--brand-tint); color: var(--brand-700); border-color: color-mix(in oklab, var(--brand-500) 30%, var(--line)); }
.auth-ctl > svg { color: currentColor; }
/* Tooltips on the auth top-bar buttons anchor below + right —
   above-anchoring would clip the viewport's top edge, and a
   center-anchored tooltip below would clip the right edge since
   the buttons sit in the top-right corner. The unified `[data-tip]`
   base rule still provides the styling; we just override position. */
.auth-controls [data-tip]::after {
  bottom: auto;
  top: calc(100% + 6px);
  left: auto;
  right: auto;
  inset-inline-end: 0;
  transform: none;
}

/* Centered card */
.auth-main {
  display: grid;
  place-items: center;
  padding: 24px 0;
  min-height: 0;
}
.auth-card {
  width: 100%;
  max-width: 460px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 32px 32px 28px;
  box-shadow: var(--sh-lg);
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.auth-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 4px;
}
/* Title-row layout — welcome heading on the leading edge, platform
   logo on the trailing edge. `space-between` is writing-direction
   aware, so the visual mirroring in RTL is automatic. `flex-end`
   on the cross axis bottom-aligns the row, so the logo's baseline
   sits flush with the title's descender line. */
.auth-head-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
}

/* Platform brand mark inside the auth card — sized as a small
   companion mark next to the welcome title rather than a hero
   logo above it. The tenant logo in the page header (.tenant-icon)
   is the company brand; this is the platform brand. Aspect ratio
   mirrors the SVG viewBox (226:83 ≈ 2.72:1).

   Sized at 66×24 — sits next to the 22px title at a comparable
   cap-height so the wordmark reads as a peer to the heading rather
   than dominating it. The asset is theme-swapped via the same
   pattern as .tenant-icon so dark/light variants flip on
   `data-theme`. `flex-shrink: 0` so the logo never collapses if
   the title grows long (e.g. localized strings). */
.auth-product-logo {
  width: 66px;
  height: 24px;
  flex-shrink: 0;
  background-image: url('assets/ive-light.svg?v=1');
  background-repeat: no-repeat;
  background-size: contain;
  background-position: center;
}
[data-theme="dark"] .auth-product-logo {
  background-image: url('assets/ive-dark.svg?v=1');
}
.auth-title {
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--ink);
}
[dir="rtl"] .auth-title { font-size: 23px; letter-spacing: 0; }
.auth-sub {
  margin: 0;
  font-size: 13px;
  color: var(--ink-3);
  line-height: 1.4;
}
[dir="rtl"] .auth-sub { font-size: 13.5px; }

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.auth-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
}
[dir="rtl"] .auth-label { font-size: 12.5px; }
.auth-input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  font-family: inherit;
  font-size: 13.5px;
  color: var(--ink);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
  box-sizing: border-box;
}
.auth-input::placeholder { color: var(--ink-4); }
.auth-input:hover { border-color: var(--line-strong); }
.auth-input:focus {
  outline: 0;
  border-color: color-mix(in oklab, var(--brand-500) 50%, var(--line));
  box-shadow: 0 0 0 3px var(--ring);
}

.auth-link {
  font-size: 12px;
  font-weight: 500;
  color: var(--brand-700);
  text-decoration: none;
}
.auth-link:hover { text-decoration: underline; }

/* Password input + show/hide toggle on the trailing side. The toggle
   sits 1px inside the input's border (top/bottom/end) so the hover
   background reaches the input's edge and the trailing corners can
   match the input's outer radius — the result is a clean rounded
   "tab" that fills the slot rather than a small floating chip. */
.auth-pwd-wrap { position: relative; }
.auth-pwd-toggle {
  position: absolute;
  top: 1px;
  bottom: 1px;
  inset-inline-end: 1px;
  width: 40px;
  display: inline-grid;
  place-items: center;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  cursor: pointer;
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  border-start-end-radius: calc(var(--r-md) - 1px);
  border-end-end-radius: calc(var(--r-md) - 1px);
  transition: background var(--t-fast), color var(--t-fast);
}
.auth-pwd-toggle:hover { background: var(--bg-sunken); color: var(--ink); }
.auth-pwd-wrap .auth-input { padding-inline-end: 44px; }

.auth-remember {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
}
.auth-remember input { accent-color: var(--brand-600); }

/* Sign-in button — full-width, primary. */
.auth-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 16px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--brand-ink);
  background: var(--brand-600);
  border: 1px solid var(--brand-600);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), transform 80ms ease;
}
.auth-submit:hover { background: var(--brand-700); border-color: var(--brand-700); }
.auth-submit:active { transform: translateY(1px); }
.auth-submit:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Inline error banner — sits between the heading and the email field
   when credentials don't match. Hidden via `is-hidden` rather than
   removed from the DOM so screen readers see the live region update. */
.auth-error {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: color-mix(in oklab, #d92d20 10%, var(--bg-sunken));
  border: 1px solid color-mix(in oklab, #d92d20 35%, var(--line));
  border-radius: var(--r-md);
  font-size: 12.5px;
  color: color-mix(in oklab, #d92d20 70%, var(--ink));
  line-height: 1.4;
}
[dir="rtl"] .auth-error { font-size: 13px; }
.auth-error.is-hidden { display: none; }
.auth-error-mark {
  width: 20px; height: 20px;
  flex-shrink: 0;
  border-radius: 50%;
  background: color-mix(in oklab, #d92d20 18%, transparent);
  color: #d92d20;
  display: inline-grid;
  place-items: center;
}
/* When credentials are rejected, both fields take a red border so the
   user immediately sees the form is in an error state. Cleared on the
   first `input` event in either field. */
.auth-input.is-error {
  border-color: color-mix(in oklab, #d92d20 55%, var(--line));
}
.auth-input.is-error:focus {
  border-color: #d92d20;
  box-shadow: 0 0 0 3px color-mix(in oklab, #d92d20 20%, transparent);
}

/* Per-field validation message — sits 4px below the input when the
   field is empty on submit. The slot's vertical space is *always*
   reserved (via `visibility: hidden` rather than `display: none` when
   inactive) so showing the message doesn't push the form taller and
   shift everything below it. The slot uses `min-height` to lock the
   space at exactly one line so the reservation is consistent whether
   the message is currently visible or not. */
.auth-field-error {
  margin-top: 4px;
  min-height: 1.35em;
  font-size: 12px;
  font-weight: 500;
  color: #d92d20;
  line-height: 1.35;
}
[dir="rtl"] .auth-field-error { font-size: 12.5px; }
.auth-field-error.is-hidden { visibility: hidden; }

/* "Need access?" help line below the submit button — directs
   first-time visitors to their administrator. */
.auth-help {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink-3);
  padding-top: 2px;
}
[dir="rtl"] .auth-help { font-size: 12.5px; }

/* Footer — copyright + Privacy / Terms / Support links. */
.auth-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 11.5px;
  color: var(--ink-3);
  padding: 4px 0 8px;
}
[dir="rtl"] .auth-foot { font-size: 12px; }
.auth-foot a {
  color: var(--brand-700);
  text-decoration: none;
}
.auth-foot a:hover { text-decoration: underline; }
.auth-foot-sep { color: var(--ink-4); user-select: none; }

/* Smaller phones: drop card padding, top bar tightens. */
@media (max-width: 480px) {
  .auth-page { padding: 14px 16px; }
  .auth-card { padding: 24px 22px 22px; max-width: 100%; }
  .auth-title { font-size: 19px; }
  .auth-brand .tenant-icon { width: 120px; height: 30px; }
}

/* ─────────── BRANDING PANEL ───────────
   Class prefix is `.tweaks-*` for historical reasons (the panel
   was formerly named TweaksPanel). Class names are internal-only
   — keep them stable so existing CSS hooks aren't broken. */
.tweaks-overlay {
  position: fixed; inset: 0;
  background: color-mix(in oklab, var(--ink) 30%, transparent);
  z-index: 100;
  display: flex;
  justify-content: flex-end;
  align-items: stretch;
  animation: fade-in 180ms ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.tweaks-panel {
  width: 380px;
  max-width: 100%;
  background: var(--bg-raised);
  border-inline-start: 1px solid var(--line);
  display: flex; flex-direction: column;
  box-shadow: var(--sh-lg);
  animation: slide-in 240ms cubic-bezier(.4,0,.2,1);
}
@keyframes slide-in { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }
[dir="rtl"] .tweaks-panel { animation-name: slide-in-rtl; }
@keyframes slide-in-rtl { from { transform: translateX(-20px); opacity: 0; } to { transform: none; opacity: 1; } }

.tweaks-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.tweaks-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.tweaks-body {
  padding: 4px 16px 24px;
  overflow-y: auto;
  flex: 1;
}

.tweak-sec {
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}
.tweak-sec:last-of-type { border-bottom: 0; }

.tweak-sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 12px;
}
.tweak-sec-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.tweak-sec-value {
  font-size: 10px;
  color: var(--ink-4);
}

/* Swatch grid */
.swatch-grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 6px;
  margin-bottom: 12px;
}
.swatch {
  aspect-ratio: 1;
  border-radius: var(--r-sm);
  border: 0;
  cursor: pointer;
  position: relative;
  transition: transform var(--t-fast);
}
.swatch:hover { transform: scale(1.08); }
.swatch.is-on::after {
  content: '';
  position: absolute; inset: -3px;
  border: 2px solid var(--ink);
  border-radius: calc(var(--r-sm) + 3px);
}

/* Segmented controls */
.seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  background: var(--bg-sunken);
  padding: 4px;
  border-radius: var(--r-md);
}
.seg-opt {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 8px;
  background: transparent;
  border: 0;
  border-radius: 7px;
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  transition: all var(--t-fast);
}
.seg-opt:hover { color: var(--ink); }
.seg-opt.is-on {
  background: var(--bg-raised);
  color: var(--ink);
  box-shadow: var(--sh-xs);
}

.hex-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}
.hex-color {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
}
.hex-color::-webkit-color-swatch { border: 0; border-radius: calc(var(--r-sm) - 2px); }
.hex-color::-webkit-color-swatch-wrapper { padding: 2px; }
.hex-text {
  flex: 1;
  min-width: 0;
  padding: 6px 10px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 12.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.hex-text:focus {
  outline: 2px solid var(--ring);
  outline-offset: 0;
  border-color: var(--line-strong);
}

/* Mobile drawer (hamburger menu) */
.mobile-overlay {
  position: fixed;
  inset: 0;
  background: color-mix(in oklab, var(--ink) 35%, transparent);
  z-index: 130;
  animation: fade-in 180ms ease;
}
.mobile-drawer {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  width: 300px;
  max-width: 86vw;
  height: 100vh;
  background: var(--bg-raised);
  border-inline-end: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  box-shadow: var(--sh-lg);
  animation: drawer-slide 220ms cubic-bezier(.4,0,.2,1);
}
[dir="rtl"] .mobile-drawer { animation-name: drawer-slide-rtl; }
@keyframes drawer-slide { from { transform: translateX(-100%); } to { transform: none; } }
@keyframes drawer-slide-rtl { from { transform: translateX(100%); } to { transform: none; } }

.mobile-drawer-close {
  position: absolute;
  top: 10px;
  inset-inline-end: 14px;
  width: 28px; height: 28px;
  display: inline-grid;
  place-items: center;
  padding: 0;
  z-index: 1;
}
.mobile-drawer-body { flex: 1; overflow-y: auto; padding: 32px 8px 8px; }
.mobile-drawer-section { padding: 8px 0; }
.mobile-drawer-section + .mobile-drawer-section { border-top: 1px solid var(--line); margin-top: 8px; }
.mobile-nav-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  cursor: pointer;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  text-align: start;
  text-decoration: none;          /* anchor variants */
}
.mobile-nav-item:hover { background: var(--brand-tint); color: var(--ink); }
.mobile-nav-item.is-active { background: var(--brand-tint-2); color: var(--brand-700); }
[data-theme="dark"] .mobile-nav-item.is-active { color: var(--brand-700); }

/* ─────────── Responsive ─────────── */
/* ≤ 1280: compact-desktop / large-tablet — sidebar shrinks, nav becomes
   icon-only, filter bar wraps to 2 rows. Sidebar still visible so the
   user can browse clients without opening a drawer. */
/* ≤ 1279: topnav grid tightens. Filter-bar wrapping moved to the
   ≤1079 query so the search + pills stay on one row across the full
   1080–1279 zone (the pills + density actions fit comfortably until
   the icons start losing room around 1079). */
@media (max-width: 1279px) {
  .topnav-inner { grid-template-columns: 140px 1fr auto; }
}

/* ≤ 1079: top-nav text-only (icons hidden) and filter pill icons
   drop so the labels carry the full pill width. The filter bar
   itself stays on a single row at this range — pills + search keep
   sharing the bar; only the per-pill leading icon is hidden. The
   bar's wrap behaviour is moved down to the ≤899 query. */
@media (max-width: 1079px) {
  .nav-item .nav-icon-asset,
  .nav-item > svg { display: none; }
  .nav-item { padding: 6px 8px; }

  /* Pill icons drop. Pill labels carry the full width at this density. */
  .filter-pill > svg:first-of-type,
  .filter-pill .nav-icon-asset { display: none; }
  .filter-pill { padding-inline-start: 12px; }

  /* Saved Tests freezes its columns at the 1080-state width (1080 −
     32 main padding − 2 card border = 1046) and the wrapper scrolls
     horizontally. Reports tables (`.rpt-table-wrap`) DO NOT need a
     forced min-width here: once `.rpt-split` collapses to one column
     at this viewport they get the full doc width and fit naturally,
     so we deliberately leave them alone. Tables that need their own
     freeze (Custom Reports templates, Schedules) declare their
     min-width on their specific class outside this query. */
  .table-scroll { overflow-x: auto; overflow-y: visible; }
  .table-scroll .data-table { min-width: 1046px; }
  .table-scroll .data-table thead th { position: static; }

  /* Schedules freezes the same way Saved Tests / Custom Reports do.
     `.rpt-table-wrap.sched-table-wrap` (3 classes) overrides the
     ≤899 generic 866 freeze on `.rpt-table-wrap .data-table`, so
     Schedules holds its 1080-state width across the entire 0–1079
     range instead of stepping down at 899. */
  .rpt-table-wrap.sched-table-wrap { overflow-x: auto; overflow-y: visible; }
  .rpt-table-wrap.sched-table-wrap .sched-table { min-width: 1046px; }
  .rpt-table-wrap.sched-table-wrap .sched-table thead th { position: static; }

  /* Maneuver L1 (and any other 2-pane split layout) collapses to
     a single column at this viewport so each table gets the full
     doc width without horizontal scroll, instead of being squeezed
     into a ~520px pane that can't display 5–6 columns. */
  .rpt-split { grid-template-columns: 1fr; }

  /* Builder fields drop from 3 → 2 columns at the freeze. */
  .fields-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Step 7 Run-at field wraps to its own row at this viewport ONLY
     when the optional day picker is present (Weekly → "Day of the
     week", Monthly → "Day of the month"). Schedule + Day + Run-at
     together can't fit one row at ≤1079, so Run-at drops below.
     When the day picker isn't showing (e.g. Daily / "On test
     completion" presets), there's plenty of room for Schedule +
     Run-at side-by-side, so we leave them on row 1. The `:has()`
     selector flips the rule on/off based on the actual children
     rendered. */
  .builder-sched-row:has(.builder-sched-day) .builder-sched-time { flex-basis: 100%; }
}

/* ≤ 899: mobile — sidebar hidden, hamburger drawer carries nav and
   clients, table scrolls horizontally to keep readable columns.
   Filter bar wraps here so search + density share row 1 and the
   (icon-less) pills sit on row 2. */
@media (max-width: 899px) {
  /* Pill-icon hiding lives in the ≤1079 block above; this query
     only adds the bar-wrap behaviour. */
  .filter-bar {
    flex-wrap: wrap;
    padding: 8px;
    gap: 8px;
  }
  /* Row 1: search grows to fill remaining space beside the density
     actions (compact icon button), so the two share the row even at
     very narrow viewports. `flex-basis: 0` is critical here — with
     `flex-basis: auto` the search's natural width (input + placeholder
     text) made the row's basis-sum exceed the bar at ~297px, which
     triggered `flex-wrap: wrap` on the bar and dropped density onto
     its own row. Starting at basis 0 and growing via flex-grow keeps
     the basis-sum always smaller than the bar so wrap never fires
     between these two children. */
  .filter-search { flex: 1 1 0; order: 0; min-width: 0; }
  .filter-actions { order: 1; margin-inline-start: auto; flex-shrink: 0; }
  /* Row 2: pills span full width and wrap onto multiple rows when
     they can't all fit on a single line. Earlier this used a
     `nowrap` + `overflow-x: auto` horizontal scroll, but pages
     are easier to read when the bar grows vertically rather than
     hiding pills behind a swipe. */
  .filter-pills {
    order: 2;
    flex: 1 1 100%;
    flex-wrap: wrap;
    overflow: visible;
  }

  .shell { grid-template-columns: 1fr; }
  /* Rail keeps working as an overlay on mobile, just narrower so it
     doesn't cover the whole viewport. The school-switcher button in the
     page header remains the primary way to open it. */
  .client-rail { width: min(296px, 88vw); }
  /* `.main` padding stays at 16px (set above 1079) so the visible
     table-card width doesn't jump at the 899 breakpoint. The table
     is already frozen at 1046 by the 1079 rule; changing main
     padding here would shift the card edge and read as a column
     change visually. */
  /* School switcher stays visible and usable in mobile */
  .school-switcher { min-width: 0; max-width: 100%; }
  .topnav-inner {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .topnav-brand { flex-shrink: 0; }
  .hamburger-btn { display: inline-grid; flex-shrink: 0; }
  .topnav-items { display: none; }
  .topnav-tools { margin-inline-start: auto; flex-shrink: 0; }
  /* Table-scroll/min-width rules moved to the wider 1079 query so
     the 900–1079 zone benefits too. */

  /* Reports tables freeze at the 900-state width and scroll
     horizontally below this breakpoint. Above 899 the `.rpt-table-wrap`
     tables fit naturally (the `.rpt-split` collapse rule at ≤1079
     already gives split-pane tables full width); from 899 down they
     would otherwise keep shrinking each column, so we lock them at
     the table's rendered width at 900 viewport (900 − 32 main padding
     − 2 card border = 866) and let the wrapper scroll. Applies to
     Institute L1/L2/L3, Maneuver L1/L2, Examiner L1/L2 and any other
     report table that uses `.rpt-table-wrap` as its wrapper. */
  .rpt-table-wrap { overflow-x: auto; overflow-y: visible; }
  .rpt-table-wrap .data-table { min-width: 866px; }
  .rpt-table-wrap .data-table thead th { position: static; }

  /* Test Analysis split-pane tables freeze at their 1080-state width
     (~515px each, since `.rpt-split` is now equal-fr at ≥1080) rather
     than the 866 default. This means columns keep shrinking naturally
     as the viewport narrows below 1079 — the table only freezes and
     scrolls once it would shrink below the size it had when sitting
     beside its twin in the 2-pane split layout. Applies to Maneuver
     Performance Summary (L1), Top 10 Failed Parameters (L1), Failed
     parameters in <maneuver> (L2), and Failure rate by branch (L2);
     they all live inside `.rpt-split`. */
  .rpt-split .rpt-table-wrap .data-table { min-width: 515px; }

  /* Custom Reports filter bar — search drops onto its own row above
     the filter pills at this dense viewport. The chained class
     selector (`.filter-bar--reports.tpl-filter-bar`) bumps the
     specificity to 2 classes so this `wrap` declaration wins over
     the unscoped `.filter-bar--reports { flex-wrap: nowrap }` rule
     declared later in the file (same-specificity cascade would
     otherwise pick the later, non-media-query rule). Same trick on
     the search input override and on `.filter-pills` to make sure
     they don't fight the base reports overrides. */
  .filter-bar--reports.tpl-filter-bar { flex-wrap: wrap; }
  .filter-bar--reports.tpl-filter-bar .filter-search { flex: 1 1 100%; }
  /* Pills now sit on their own full-width row beneath the search; if
     they still can't all fit on that row at very narrow viewports
     (small phones), let them wrap onto multiple rows here — without
     this override the always-on `nowrap` rule above the @media block
     keeps applying and the pills overflow instead of wrapping. */
  .filter-bar--reports.tpl-filter-bar .filter-pills {
    flex: 1 1 100%;
    flex-wrap: wrap;
  }

  /* Notification panel becomes a side drawer in responsive */
  .notif-panel {
    position: fixed;
    top: 0;
    inset-inline-end: 0;
    width: 100%;
    max-width: 380px;
    height: 100vh;
    max-height: none;
    border-radius: 0;
    border-inline-start: 1px solid var(--line);
    border-inline-end: 0;
    border-top: 0;
    border-bottom: 0;
    z-index: 200;
    animation: drawer-slide-end 220ms cubic-bezier(.4,0,.2,1);
  }
  [dir="rtl"] .notif-panel { animation-name: drawer-slide-start; }
  @keyframes drawer-slide-end { from { transform: translateX(100%); } to { transform: none; } }
  @keyframes drawer-slide-start { from { transform: translateX(-100%); } to { transform: none; } }
  .notif-backdrop {
    position: fixed;
    inset: 0;
    background: color-mix(in oklab, var(--ink) 40%, transparent);
    z-index: 199;
    animation: fade-in 180ms ease;
  }

  /* Profile menu becomes a side drawer in responsive */
  .profile-menu {
    position: fixed;
    top: 0;
    inset-inline-end: 0;
    width: 100%;
    max-width: 320px;
    height: 100vh;
    max-height: none;
    border-radius: 0;
    border-top: 0;
    border-bottom: 0;
    border-inline-end: 0;
    border-inline-start: 1px solid var(--line);
    padding: 24px 12px 12px;
    z-index: 200;
    animation: drawer-slide-end 220ms cubic-bezier(.4,0,.2,1);
  }
  [dir="rtl"] .profile-menu { animation-name: drawer-slide-start; }
  .profile-backdrop {
    position: fixed;
    inset: 0;
    background: color-mix(in oklab, var(--ink) 40%, transparent);
    z-index: 199;
    animation: fade-in 180ms ease;
  }

  /* Table-scroll / min-width / static thead are owned by the 1079
     query so the 900–1079 zone benefits too — duplicating them
     here with `min-width: 1100` was the bug that caused columns
     to expand at the 899 breakpoint. */
}

/* ─────────── REPORTS — FILTER ACTIONS, KPI SEP, PR CELL TIP, OVERRIDE BANNER, DRILL HINT ─────────── */
/* Reports filter bar — Apply pinned to the far end (right in LTR) on the
   same row as the pills. The base .filter-bar has a ≤1279px media query
   that orders .filter-actions before .filter-pills (used by Saved Tests
   where Apply lives in a fixed-width search/density row). For Reports we
   want the opposite — pills first, Apply at the end — so we override the
   order + flex-wrap explicitly at all breakpoints. */
.filter-bar--reports {
  flex-wrap: nowrap;
  /* Pin filters to the top of the bar so the pill row sits aligned
     with the top-left corner regardless of how tall the bar grows
     (multi-row pills, stacked actions). The base `.filter-bar` rule
     uses `align-items: center` which would otherwise push the pill
     row down toward the vertical center as soon as anything else in
     the bar (e.g. stacked actions) makes it taller. */
  align-items: flex-start;
  /* Match the padding shape of `.rpt-head` (16px 18px) so the filter
     bar reads as a sibling card with the same internal whitespace. */
  padding: 16px 18px;
}
.filter-bar--reports .filter-pills {
  flex: 1 1 0;
  min-width: 0;
  /* Pills wrap internally if there are too many to fit; the pills div
     becomes taller but Apply still sits beside it on the bar's row. */
  flex-wrap: wrap;
  /* Reset any order assigned by the base mobile media query. */
  order: 0;
}
.filter-actions--reports {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  /* Top-align to match the pill row's pinned-to-top behavior — the
     parent's `align-items: flex-start` already defaults children here,
     this is just the explicit cue. */
  align-self: flex-start;
  /* Override the base ≤1279px rule (.filter-actions { order: 1 }) which
     would otherwise render Apply before the pills — i.e. on the LEFT. */
  order: 1;
  margin-inline-start: auto;
}
.filter-actions--reports .btn,
.filter-actions--reports .filter-reset { align-self: center; }
/* Below 1080 the InstitutePerformance pill row gets tight enough that
   adding the side-by-side "Unapplied changes" hint pushes pills onto a
   2nd line. At that point we stack the actions vertically — hint on
   top, Apply pinned beneath it, both right-aligned — so the actions
   column shrinks to ~Apply-button width and the pills row reclaims
   its horizontal space. Source order is unchanged (hint first, Apply
   second) so the visual order matches: hint above Apply. */
@media (max-width: 1079px) {
  .filter-actions--reports {
    flex-direction: column;
    align-items: flex-end;
    align-self: flex-start;
  }
  .filter-actions--reports .btn,
  .filter-actions--reports .filter-reset { align-self: flex-end; }
}
@media (max-width: 760px) {
  /* Narrow viewports: let the bar wrap so Apply drops below the pills
     cleanly. Apply still pushed to the end via margin-inline-start: auto. */
  .filter-bar--reports { flex-wrap: wrap; }
}

/* Very dense viewports (≤439): the filter pills and the actions
   group can no longer share a row, and the rpt-head's title + action
   CTAs need to stack vertically too. Both surfaces drop their
   trailing actions onto their own row below the rest of the content. */
@media (max-width: 439px) {
  /* Filter bar: actions claim a full-width row beneath the pills. The
     ≤1079 rule already stacks the hint above the Apply button inside
     `.filter-actions--reports` and right-aligns them; the only thing
     this width: 100% adds is forcing the group onto its own line
     instead of sitting beside whatever's left of the pills. */
  .filter-bar--reports { flex-wrap: wrap; }
  .filter-actions--reports { width: 100%; }

  /* Rpt-head: title + level chain occupy the top, action CTAs
     (Generate report / Schedule) stack below them. Column flex on
     `.rpt-head-row` does the row-stack; `.rpt-head-actions` already
     stacks vertically from the ≤559 container query (Generate Report
     on top, Schedule below). */
  .rpt-head-row {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
}

/* Pending pill — user changed this filter but hasn't clicked Apply yet.
   Dashed amber outline distinguishes "selected but pending" from the solid
   brand-tinted "selected and applied" state. The dashed border is what the
   user reads as "this isn't live yet". */
.filter-pill.is-pending {
  border-color: var(--warn, oklch(0.72 0.15 75));
  border-style: dashed;
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 8%, var(--bg-raised));
  color: var(--ink);
}
.filter-pill.is-pending:hover { background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 14%, var(--bg-raised)); }
/* When a pill is BOTH active and pending, keep the dashed pending outline
   on top — pending is the more important signal until the user applies. */
.filter-pill.is-active.is-pending {
  border-color: var(--warn, oklch(0.72 0.15 75));
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 12%, var(--bg-raised));
  color: var(--ink);
}

/* Inline hint shown next to the Apply button while there are staged but
   unapplied changes. Disappears the moment the user clicks Apply. */
.filter-pending-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: 12px;
  font-weight: 500;
  color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 75%, var(--ink));
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 12%, var(--bg-raised));
  border: 1px solid color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 35%, var(--line));
  border-radius: var(--r-md);
  white-space: nowrap;
  animation: hint-fade-in 180ms ease-out;
}
[dir="rtl"] .filter-pending-hint { font-size: 13px; }
.filter-pending-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--warn, oklch(0.72 0.15 75));
  flex-shrink: 0;
  animation: hint-pulse 1.6s ease-in-out infinite;
}
@keyframes hint-fade-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes hint-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 50%, transparent); }
  50%      { box-shadow: 0 0 0 5px color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 0%, transparent); }
}

/* Apply button while changes are pending — subtle brightness / shadow lift
   so the eye lands on it. Doesn't change the disabled state colour because
   pending implies the button is enabled. */
.btn.btn-attention {
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand-500) 22%, transparent), var(--sh-xs);
}

/* Inline separator inside a KPI value — used for "1 of 3" so "of" reads as a
   light grammatical connector rather than a number. */
.kpi-value-sep {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-4);
  letter-spacing: 0;
  margin: 0 2px;
}
/* "of N" trailer — keeps the headline number ("1") at full kpi-value size
   and pushes "of 3" into a small, muted segment to its right at a single
   uniform smaller size for both "of" and the total. */
.kpi-value-of {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
  margin-inline-start: 6px;
  font-variant-numeric: tabular-nums;
}
[dir="rtl"] .kpi-value-of { font-size: 13px; }

/* Pass-rate cell with a hover tooltip showing PR/FR by gender. The tooltip
   floats above the row — anything that could clip it (table-wrap overflow,
   row hover background) has been opened up upstream. */
.pr-cell-tip-host { position: relative; display: inline-flex; }
.pr-cell-tip-host > .pr-cell { cursor: default; }
.pr-cell-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  /* Anchored to the trailing edge to keep the floating card inside
     the page. See `.pr-cell .pr-cell-tip` above for the full
     rationale. */
  inset-inline-end: 0;
  inset-inline-start: auto;
  z-index: 50;
  display: none;
  /* 4 columns: Label | Overall | Male | Female. */
  grid-template-columns: max-content max-content max-content max-content;
  gap: 4px 14px;
  padding: 8px 10px;
  background: var(--bg-raised);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
  font-size: 11.5px;
  white-space: nowrap;
  pointer-events: none;
}
.pr-cell-tip-host:hover .pr-cell-tip { display: grid; }
.pr-cell-tip-row { display: contents; }
.pr-cell-tip-head { color: var(--ink-4); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; }
.pr-cell-tip-label { font-weight: 600; }

/* Override-request status banner — single-line layout: status dot + label,
   reviewer/date in the middle, "View request" CTA pinned to the end. Tone
   is set by a modifier class (.is-info / .is-pass / .is-warn / .is-fail). */
.override-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 10px 14px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  font-size: 13px;
  line-height: 1.4;
}
.override-banner-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ink-3);
  flex-shrink: 0;
}
.override-banner-label { font-weight: 600; color: var(--ink); }
.override-banner-meta { color: var(--ink-3); font-size: 12px; }
/* "View request" CTA — pinned to the trailing edge of the banner via auto
   margin so it stays right-aligned regardless of the meta text length. */
.override-banner-cta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-inline-start: auto;
  padding: 5px 10px;
  background: var(--bg-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: all var(--t-fast);
}
.override-banner-cta:hover {
  background: var(--bg-sunken);
  border-color: var(--ink-3);
}
.override-banner-cta svg { color: var(--ink-3); }
.override-banner.is-info  { border-color: color-mix(in oklab, var(--brand-500) 30%, var(--line)); background: var(--brand-tint); }
.override-banner.is-info  .override-banner-dot { background: var(--brand-600); box-shadow: 0 0 0 4px color-mix(in oklab, var(--brand-500) 20%, transparent); animation: ovr-pulse 1.6s ease-in-out infinite; }
.override-banner.is-pass  { border-color: color-mix(in oklab, var(--ok) 30%, var(--line)); background: color-mix(in oklab, var(--ok) 8%, var(--bg-raised)); }
.override-banner.is-pass  .override-banner-dot { background: var(--ok); }
.override-banner.is-warn  { border-color: color-mix(in oklab, var(--warn) 35%, var(--line)); background: color-mix(in oklab, var(--warn) 10%, var(--bg-raised)); }
.override-banner.is-warn  .override-banner-dot { background: var(--warn); }
.override-banner.is-fail  { border-color: color-mix(in oklab, var(--err) 30%, var(--line)); background: color-mix(in oklab, var(--err) 8%, var(--bg-raised)); }
.override-banner.is-fail  .override-banner-dot { background: var(--err); }
@keyframes ovr-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--brand-500) 30%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in oklab, var(--brand-500) 0%, transparent); }
}

/* Drill hint — finger-pointer icon + label. The icon already signals "tap
   to open detail"; the surrounding hint stays small and inline so it sits
   quietly above the table. */
.rpt-drill-hint svg { color: var(--brand-600); }
[data-theme="dark"] .rpt-drill-hint svg { color: var(--brand-700); }

/* L4 student card — sits between the document head and the KPI strip.
   Photo on the inline-start, name + identity grid alongside. Mirrors the
   density of the saved-tests popover but as a static block on the page. */
.l4-student-card {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 16px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-xs);
}
.l4-student-photo {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-sunken);
  flex-shrink: 0;
}
.l4-student-info { flex: 1; min-width: 0; }
.l4-student-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
[dir="rtl"] .l4-student-name { font-size: 17px; letter-spacing: 0; }
.l4-student-name-sub {
  font-size: 13px;
  color: var(--ink-3);
  font-weight: 400;
  line-height: 1.3;
  margin-top: 2px;
  /* Force the block to start on the LEFT (in LTR) so the Arabic name sits
     directly under "Ahmed Al Hashimi". `direction: ltr` sets the line's
     base direction to LTR; the Arabic glyphs still render RTL within the
     line because of Unicode bidi (the strong characters' natural
     direction), but the BLOCK's start edge is now flush with the English
     name above. `text-align: start` then resolves to left. We avoid
     `unicode-bidi: plaintext` here — it would flip the paragraph dir to
     RTL based on the strong chars and push the block to the right. */
  text-align: start;
  direction: ltr;
}
.l4-student-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 6px 18px;
  margin: 10px 0 0;
}
.l4-student-grid > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.l4-student-grid dt {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
}
[dir="rtl"] .l4-student-grid dt { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.l4-student-grid dd {
  margin: 0;
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Inline copy-icon variant — same flex pattern the profile popover uses
   for mobile / email. The icon sits flush after the value, separated by
   a small gap; the value can still ellipsis-truncate if it doesn't fit. */
.l4-student-grid dd.l4-student-grid-copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  overflow: visible;
}
.l4-student-grid dd.l4-student-grid-copy > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.profile-vehicle-cat { color: var(--ink-3); }

/* ─────────── GENERATE-REPORT MODAL ─────────── */
.modal-backdrop {
  position: fixed; inset: 0;
  background: color-mix(in oklab, var(--ink) 50%, transparent);
  backdrop-filter: blur(2px);
  z-index: 300;
  display: grid; place-items: center;
  padding: 16px;
  animation: fade-in 160ms ease;
}
.modal {
  width: 100%;
  max-width: 520px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
  display: flex; flex-direction: column;
  max-height: calc(100vh - 32px);
  animation: gen-modal-in 200ms cubic-bezier(.4,0,.2,1);
}
@keyframes gen-modal-in {
  from { transform: translateY(8px) scale(0.985); opacity: 0; }
  to   { transform: translateY(0) scale(1); opacity: 1; }
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.modal-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
[dir="rtl"] .modal-title { font-size: 16px; letter-spacing: 0; }
.modal-close {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  cursor: pointer;
  transition: all var(--t-fast);
}
.modal-close:hover { background: var(--bg-sunken); color: var(--ink); }
.modal-body {
  padding: 16px;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 16px;
}
.modal-foot {
  display: flex; justify-content: flex-end; align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  background: var(--bg-sunken);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* ─────────── CREATE SCHEDULE MODAL ─────────── */
/* Wider than the gen modal (more form fields) but still capped so it
   doesn't run edge-to-edge on a desktop. Body scrolls internally
   when the form's tall (custom-cadence + shared-recipients can
   stretch it past the viewport on a laptop). */
.modal--sched {
  width: 640px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
}
.modal--sched .modal-body { gap: 20px; max-height: none; }
.sched-modal-body { min-height: 0; }

.sched-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sched-section-title {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  text-transform: uppercase;
}
[dir="rtl"] .sched-section-title { font-size: 13.5px; letter-spacing: 0; text-transform: none; }
.sched-section-sub {
  font-size: 12px;
  color: var(--ink-3);
  line-height: 1.4;
  margin-top: -4px;
}
[dir="rtl"] .sched-section-sub { font-size: 12.5px; }

/* Source provenance hint that surfaces under the picker once a
   source is selected. Format: icon · type · owner · created timestamp.
   Reads as a single inline trail so the user gets the full context
   on one row without breaking the modal's vertical rhythm. The
   parts wrap to a 2nd row only on very narrow viewports. */
.sched-source-hint {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 8px;
  font-size: 11.5px;
  color: var(--ink-2);
  padding: 8px 12px;
  background: var(--bg-sunken);
  border-radius: var(--r-sm);
}
.sched-source-hint > svg { color: var(--ink-2); flex-shrink: 0; }
.sched-source-sep {
  color: var(--ink-4);
  user-select: none;
}
.sched-source-part {
  white-space: nowrap;
}
.sched-source-part.is-kind  { color: var(--ink); font-weight: 600; }
.sched-source-part.is-owner { color: var(--ink-2); }
.sched-source-part.is-created,
.sched-source-part.is-builtin { color: var(--ink-3); }

.sched-recipients-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}
/* Recipient dropdowns inside the schedule modal carry user names +
   email/role subtitles; bump the menu width so a "Mariam Al Tayer
   · mariam@ive.ae" row reads on a single line instead of ellipsing
   in the middle of the email. Scoped to this surface so the rest
   of the app's filter-menus keep their original 200/280 sizing. */
.sched-recipients-field .filter-menu {
  min-width: 300px;
  max-width: 420px;
}
/* Trigger button (pill) takes the full field width here so the
   "Pick users…" / "Pick roles…" target is a full-row click target
   instead of a tight content-sized pill. The caret stays pinned
   to the row's end via `justify-content: space-between`. */
.sched-recipients-field .filter-pill-wrap {
  display: block;
  width: 100%;
}
.sched-recipients-field .filter-pill {
  width: 100%;
  justify-content: space-between;
}

@media (max-width: 599px) {
  .modal--sched { width: 100%; max-width: 100%; max-height: 100vh; border-radius: 0; }
}

/* ─────────── SAVE TEMPLATE CONFIRM MODAL ─────────── */
.modal--confirm {
  width: 560px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
}
.modal--confirm .modal-body { gap: 16px; }
.confirm-body { min-height: 0; }

.confirm-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.confirm-section-title {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  text-transform: uppercase;
}
[dir="rtl"] .confirm-section-title { font-size: 12.5px; letter-spacing: 0; text-transform: none; }

/* Definition-list block for the template summary — `dt` is the
   field name (muted), `dd` is the value (regular ink). Two columns
   so the values align vertically across rows. */
.confirm-rows {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 6px 12px;
  margin: 0;
  padding: 12px 14px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.confirm-row { display: contents; }
.confirm-row dt {
  font-size: 11.5px;
  color: var(--ink-3);
  font-weight: 500;
  margin: 0;
}
.confirm-row dd {
  font-size: 13px;
  color: var(--ink);
  margin: 0;
  word-break: break-word;
}
.confirm-row-strong { font-weight: 600; }

.confirm-sharing-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.confirm-reach {
  font-size: 12px;
  color: var(--ink-2);
  font-weight: 500;
}

/* Visibility pill in confirm modals — larger and more substantive
   than the tiny `.tpl-shared` row pill. Carries the visibility name
   and the total-reach count on a 2-line stack so the user reads the
   intended scope at a glance. Tinted background per tone, matching
   the tone semantics elsewhere (private = neutral, public = brand,
   shared = brand-light). */
.confirm-vis-pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  align-self: flex-start;
  min-width: 220px;
}
.confirm-vis-pill-icon {
  width: 32px; height: 32px;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-sunken);
  color: var(--ink-3);
  flex-shrink: 0;
}
.confirm-vis-pill-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.confirm-vis-pill-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.005em;
}
[dir="rtl"] .confirm-vis-pill-name { font-size: 14.5px; letter-spacing: 0; }
.confirm-vis-pill-sub {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-3);
}
[dir="rtl"] .confirm-vis-pill-sub { font-size: 12.5px; }
/* Tone tints */
.confirm-vis-pill.is-public {
  background: var(--brand-tint);
  border-color: color-mix(in oklab, var(--brand-500) 28%, var(--line));
}
.confirm-vis-pill.is-public .confirm-vis-pill-icon {
  background: var(--bg-raised);
  color: var(--brand-700);
}
.confirm-vis-pill.is-public .confirm-vis-pill-name { color: var(--brand-700); }
.confirm-vis-pill.is-shared {
  background: color-mix(in oklab, var(--brand-500) 6%, var(--bg-raised));
  border-color: color-mix(in oklab, var(--brand-500) 20%, var(--line));
}
.confirm-vis-pill.is-shared .confirm-vis-pill-icon {
  background: var(--brand-tint);
  color: var(--brand-700);
}

.confirm-recipient-list {
  list-style: none;
  margin: 0;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  max-height: 220px;
  overflow-y: auto;
}
.confirm-recipient {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.confirm-recipient-empty {
  font-size: 12px;
  color: var(--ink-3);
  text-align: center;
  padding: 12px;
}

/* Quiet info banner used for Public / Private notes — sets context
   on the share without yelling. */
.confirm-callout {
  font-size: 12px;
  color: var(--ink-2);
  background: color-mix(in oklab, var(--brand-500) 6%, var(--bg-sunken));
  border: 1px solid color-mix(in oklab, var(--brand-500) 18%, var(--line));
  border-radius: var(--r-md);
  padding: 10px 12px;
  line-height: 1.4;
}

@media (max-width: 599px) {
  .modal--confirm { width: 100%; max-width: 100%; max-height: 100vh; border-radius: 0; }
  .confirm-rows { grid-template-columns: 1fr; gap: 2px; }
  .confirm-row dt { margin-top: 6px; }
}

/* Small status-action confirm modal — narrower than the heavy
   save-template/save-schedule confirms; just title + prose body
   + Cancel/Confirm. Used by the row Activate / Deactivate buttons. */
.modal--action-confirm {
  width: 460px;
  max-width: calc(100vw - 32px);
}
.confirm-action-body {
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.5;
}
.confirm-action-body p {
  margin: 0 0 8px;
}
.confirm-action-body p:last-child {
  margin-bottom: 0;
}
.confirm-action-body strong {
  color: var(--ink);
  font-weight: 600;
}

/* ─────────── SCHEDULE HISTORY MODAL ─────────── */
/* Source line above the history list — surfaces which schedule's
   runs the user is looking at. Reuses the modal-body padding from
   the schedule modal. */
.history-source-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 12.5px;
  color: var(--ink-3);
  margin-bottom: 4px;
}
.history-source-label {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-4);
}
[dir="rtl"] .history-source-label { font-size: 11.5px; letter-spacing: 0; text-transform: none; }
.history-source-name {
  font-weight: 600;
  color: var(--ink);
}

.history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.history-row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.history-row.is-fail {
  background: color-mix(in oklab, var(--err) 6%, var(--bg-sunken));
  border-color: color-mix(in oklab, var(--err) 25%, var(--line));
}
.history-row.is-skipped {
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 8%, var(--bg-sunken));
  border-color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 28%, var(--line));
}
.history-mark {
  width: 28px; height: 28px;
  border-radius: 50%;
  display: inline-grid;
  place-items: center;
  background: var(--bg-raised);
  color: var(--ink-3);
  border: 1px solid var(--line);
}
.history-mark.is-ok      { background: color-mix(in oklab, var(--ok)  16%, var(--bg-raised)); color: var(--ok);  border-color: color-mix(in oklab, var(--ok) 30%, var(--line)); }
.history-mark.is-fail    { background: color-mix(in oklab, var(--err) 16%, var(--bg-raised)); color: var(--err); border-color: color-mix(in oklab, var(--err) 35%, var(--line)); }
.history-mark.is-skipped { background: var(--bg-raised); color: oklch(0.5 0.13 75); border-color: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 35%, var(--line)); }
.history-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.history-date-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.history-date {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.history-time {
  font-size: 11.5px;
  color: var(--ink-3);
}
.history-meta {
  font-size: 11.5px;
  color: var(--ink-3);
}
[dir="rtl"] .history-meta { font-size: 12px; }
.history-duration {
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.history-empty {
  font-size: 13px;
  color: var(--ink-3);
  text-align: center;
  padding: 24px 16px;
}

/* Inline duplicate-conflict banner inside the duplicate modal.
   Flags that the source's exact configuration is already saved as
   another template — uses the warn ramp (amber) since this is a
   blocked state, not a destructive one (no data is at risk). */
.dup-conflict {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  margin: 10px 0;
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 10%, var(--bg-raised));
  border: 1px solid color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 36%, var(--line));
  border-radius: var(--r-md);
}
.dup-conflict-icon {
  width: 22px; height: 22px;
  flex-shrink: 0;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  background: color-mix(in oklab, var(--warn, oklch(0.72 0.15 75)) 28%, var(--bg-raised));
  color: oklch(0.5 0.13 75);
}
.dup-conflict-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.dup-conflict-title {
  font-size: 12.5px;
  font-weight: 600;
  color: oklch(0.42 0.13 75);
}
[dir="rtl"] .dup-conflict-title { font-size: 13.5px; }
.dup-conflict-body {
  font-size: 12px;
  color: var(--ink-2);
  line-height: 1.4;
}
[dir="rtl"] .dup-conflict-body { font-size: 12.5px; }
@media (max-width: 599px) {
  .modal--action-confirm { width: 100%; max-width: 100%; }
}

/* Email-source highlight in the schedule confirm — set expectation
   that deliveries arrive from a dedicated noreply mailbox so users
   know what address recipients should look for / safe-list. */
.confirm-email-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  background: color-mix(in oklab, var(--brand-500) 5%, var(--bg-sunken));
  border: 1px solid color-mix(in oklab, var(--brand-500) 22%, var(--line));
  border-radius: var(--r-md);
}
.confirm-email-icon {
  width: 28px; height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  display: inline-grid;
  place-items: center;
  background: var(--brand-tint);
  color: var(--brand-700);
  border: 1px solid color-mix(in oklab, var(--brand-500) 30%, var(--line));
}
.confirm-email-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.confirm-email-headline {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
}
[dir="rtl"] .confirm-email-headline { font-size: 13.5px; }
.confirm-email-body {
  font-size: 12px;
  color: var(--ink-2);
  line-height: 1.45;
}
[dir="rtl"] .confirm-email-body { font-size: 12.5px; }
.confirm-email-from {
  display: inline-block;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--brand-700);
  background: var(--bg-raised);
  border: 1px solid color-mix(in oklab, var(--brand-500) 30%, var(--line));
  border-radius: var(--r-sm);
  padding: 1px 6px;
  margin: 0 2px;
  white-space: nowrap;
}
.confirm-email-tip {
  font-size: 11.5px;
  color: var(--ink-3);
  font-style: italic;
}
[dir="rtl"] .confirm-email-tip { font-size: 12px; font-style: normal; }

/* Confirm-phase summary block — what's about to be generated. */
.gen-summary {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px;
}
.gen-summary-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
}
[dir="rtl"] .gen-summary-label { font-size: 12px; letter-spacing: 0; text-transform: none; }
/* Stacked label / value rows. Each row is its own line so a long scope trail
   (e.g. "Northline Academy › Al Barsha › 1668593044 · Ahmed Al Hashimi") can
   wrap cleanly without competing with neighbouring fields for width. */
.gen-summary-rows {
  display: flex;
  flex-direction: column;
  margin: 0;
}
.gen-summary-row {
  display: grid;
  grid-template-columns: 88px 1fr;
  align-items: baseline;
  gap: 12px;
  padding: 7px 0;
  font-size: 13px;
  border-top: 1px solid color-mix(in oklab, var(--ink) 6%, transparent);
}
.gen-summary-row:first-child { border-top: 0; padding-top: 4px; }
.gen-summary-row dt {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  white-space: nowrap;
}
[dir="rtl"] .gen-summary-row dt { font-size: 12px; letter-spacing: 0; text-transform: none; }
.gen-summary-row dd {
  margin: 0;
  color: var(--ink);
  font-weight: 500;
  word-break: break-word;
  min-width: 0;
}
@media (max-width: 480px) {
  .gen-summary-row {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* Scope trail — chevron-separated breadcrumb of the hierarchy this export
   covers. Direction-aware: the chevron flips for RTL via .gen-scope-sep. */
.gen-scope-trail {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  font-weight: 500;
}
.gen-scope-seg {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 2px 8px;
  font-size: 12.5px;
  color: var(--ink);
  white-space: nowrap;
}
.gen-scope-sep {
  color: var(--ink-4);
  font-size: 14px;
  line-height: 1;
}
[dir="rtl"] .gen-scope-sep { transform: scaleX(-1); }
.gen-scope-empty {
  color: var(--ink-3);
  font-style: italic;
  font-weight: 400;
}

/* Applied-filters list inside the modal. Each filter dimension is its own
   group on its own line: "Vehicle: 1A 2A". The chips are smaller than the
   scope-trail chips because there can be many of them; line wrapping is
   allowed so a many-value filter doesn't push the modal wider. */
.gen-filter-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.gen-filter-group {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: 12.5px;
}
.gen-filter-key {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  flex-shrink: 0;
}
[dir="rtl"] .gen-filter-key { font-size: 12px; letter-spacing: 0; text-transform: none; }
.gen-filter-vals {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}
.gen-filter-chip {
  background: var(--brand-tint);
  color: var(--brand-700);
  border: 1px solid color-mix(in oklch, var(--brand-500) 20%, var(--line));
  border-radius: var(--r-sm);
  padding: 1px 7px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}
[data-theme="dark"] .gen-filter-chip { color: var(--brand-700); }

/* Format radio cards — bigger hit-target than a default radio. */
.gen-format-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin-bottom: 8px;
}
[dir="rtl"] .gen-format-label { font-size: 12px; letter-spacing: 0; text-transform: none; }
.gen-format-options { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.gen-format-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: all var(--t-fast);
  background: var(--bg-raised);
}
.gen-format-opt:hover { border-color: var(--line-strong); }
.gen-format-opt.is-on {
  border-color: var(--brand-500);
  background: var(--brand-tint);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand-500) 15%, transparent);
}
.gen-format-opt input { position: absolute; opacity: 0; pointer-events: none; }
.gen-format-opt svg { color: var(--ink-3); flex-shrink: 0; }
.gen-format-opt.is-on svg { color: var(--brand-700); }
.gen-format-name { font-weight: 600; color: var(--ink); font-size: 13px; }
.gen-format-hint { font-size: 11.5px; color: var(--ink-3); margin-inline-start: auto; }
@media (max-width: 540px) {
  .gen-format-options { grid-template-columns: 1fr; }
  .gen-format-hint { margin-inline-start: 0; }
}

/* Generating-phase progress bar. */
.gen-progress { gap: 8px; }
.gen-progress-bar {
  height: 8px;
  background: var(--bg-sunken);
  border-radius: 4px;
  overflow: hidden;
}
.gen-progress-fill {
  height: 100%;
  background: var(--brand-600);
  border-radius: 4px;
  transition: width 160ms ease;
}
.gen-progress-meta {
  display: flex; justify-content: space-between;
  font-size: 12.5px;
  color: var(--ink-3);
}
/* Smoother fill transition that matches the new ~100ms tick rate
   without lagging visibly behind the percent counter. */
.gen-progress-fill { transition: width 110ms ease-out; }

/* Filename being produced — sits below the progress bar so the user
   can read what's being made. The file-format icon (filePdf / fileExcel)
   reinforces the format choice. */
.gen-progress-file {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: 12px;
  color: var(--ink-2);
  margin-top: 4px;
}
.gen-progress-file svg { color: var(--ink-3); flex-shrink: 0; }
.gen-progress-file .mono {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Notify-when-ready opt-in. Inline checkbox + two-line description so
   the affordance is discoverable without dominating the loader. When
   checked, the modal's Cancel button label flips to "Run in
   background" so the user has a clear escape hatch for long
   generations. */
.gen-notify {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-raised);
  margin-top: 8px;
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.gen-notify:hover { border-color: var(--line-strong); }
.gen-notify input[type="checkbox"] {
  width: 16px; height: 16px;
  margin-top: 2px;
  accent-color: var(--brand-600);
  flex-shrink: 0;
  cursor: pointer;
}
.gen-notify-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.gen-notify-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
}
[dir="rtl"] .gen-notify-title { font-size: 13.5px; }
.gen-notify-hint {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.4;
}
[dir="rtl"] .gen-notify-hint { font-size: 12px; }

/* Ready-phase confirmation. */
.gen-ready {
  flex-direction: row;
  align-items: center;
  gap: 14px;
  padding: 18px 16px;
}
.gen-ready-icon {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: color-mix(in oklab, var(--ok) 15%, var(--bg-raised));
  color: var(--ok);
  flex-shrink: 0;
}
.gen-ready-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gen-ready-title { font-weight: 600; color: var(--ink); font-size: 14px; }
.gen-ready-file { font-size: 12px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
