/* =====================================================================================
   Zoey Auto-QA — application stylesheet.

   The bulk of this file is the approved zoey-autoqa-v2-mockup.html <style> block, ported
   VERBATIM so the real app matches the signed-off design rather than approximating it with
   MudBlazor's stock component look. Keep it in step with that mockup; do not "tidy" values.

   Two deliberate additions on top of the mockup:
     1. @font-face blocks below - the mockup pulls Sora / IBM Plex Sans from Google Fonts, but
        the CSP here is `font-src 'self'`, so the woff2 files are vendored under lib/fonts
        (licences alongside them). Same faces, same weights, no external request.
     2. A MudBlazor bridge at the very bottom - MudBlazor still supplies dialogs, snackbars and
        form controls, so its palette variables are pointed at these same design tokens.
   ===================================================================================== */

@font-face {
    font-family: 'Sora';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('lib/fonts/sora-latin-500-normal.woff2') format('woff2');
}

@font-face {
    font-family: 'Sora';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('lib/fonts/sora-latin-600-normal.woff2') format('woff2');
}

@font-face {
    font-family: 'Sora';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('lib/fonts/sora-latin-700-normal.woff2') format('woff2');
}

@font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('lib/fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2');
}

@font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('lib/fonts/ibm-plex-sans-latin-500-normal.woff2') format('woff2');
}

@font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('lib/fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2');
}

/* ===================== ported verbatim from the approved mockup ===================== */
/* ============ DESIGN TOKENS ============ */
:root {
  --bg-0: #0a141f;
  --bg-1: #0f1e2e;
  --bg-2: #142a3d;
  --bg-3: #1b3548;
  --border: rgba(255,255,255,0.08);
  --border-strong: rgba(255,255,255,0.16);
  --text-0: #ffffff;
  --text-1: rgba(255,255,255,0.78);
  --text-2: rgba(255,255,255,0.55);

  /* Semantic colours - deliberately separate from the tenant accent, per the v1->v2 review:
     "the brand green doubled as the semantic success colour, so chips looked identical to CTAs." */
  --ok: #43c98a;
  --ok-soft: rgba(67,201,138,0.14);
  --warn: #e6b04c;
  --warn-soft: rgba(230,176,76,0.14);
  --err: #f07385;
  --err-soft: rgba(240,115,133,0.14);
  --info: #5eb2e6;
  --info-soft: rgba(94,178,230,0.14);

  /* "What fixes it" category colours - consistent everywhere they appear (spine, KPIs, chips, table) */
  --fix-coaching: #f0748c;
  --fix-coaching-soft: rgba(240,116,140,0.16);
  --fix-process: #e0ad4c;
  --fix-process-soft: rgba(224,173,76,0.16);
  --fix-customer: #5eb2e6;
  --fix-customer-soft: rgba(94,178,230,0.16);
  --fix-unknown: rgba(255,255,255,0.28);
  --fix-unknown-soft: rgba(255,255,255,0.08);

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --font-display: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
html[data-tenant="tapstitch"] { --accent: #4da8ff; --accent-soft: rgba(77,168,255,0.14); --accent-ink: #04101c; }
html[data-tenant="sterling"]  { --accent: #2ec27e; --accent-soft: rgba(46,194,126,0.14);  --accent-ink: #041c12; }
/* Added on top of the mockup: it only ever rendered its two demo tenants, but this app serves any
   tenant in tenants/, so the accent has to have a value before data-tenant matches anything.
   MainLayout overrides these three inline from branding.palette when the tenant declares them. */
:root { --accent: #2ec27e; --accent-soft: rgba(46,194,126,0.14); --accent-ink: #041c12; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

* { box-sizing: border-box; }
body { margin: 0; background: var(--bg-0); color: var(--text-0); font-family: var(--font-body); font-size: 14px; line-height: 1.5; }
h1, h2, h3, .display { font-family: var(--font-display); font-weight: 600; margin: 0; }
.tabular { font-variant-numeric: tabular-nums; }
button, input, select { font-family: inherit; }
a { color: inherit; text-decoration: none; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.layout { display: flex; flex-direction: column; height: 100vh; }
.topbar {
  display: flex; align-items: center; gap: 20px; padding: 12px 20px;
  background: var(--bg-1); border-bottom: 1px solid var(--border); flex: none;
}
.brand-block { display: flex; align-items: center; gap: 12px; }
.zoey-lockup { height: 34px; width: auto; display: block; flex: none; }
.brand-text .brand-tagline { font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2); }
.brand-text .brand-tagline .tenant-slot { color: var(--accent); }
.topbar-spacer { flex: 1; }
.topbar-user { display: flex; align-items: center; gap: 14px; font-size: 12.5px; }
.topbar-user-name { color: var(--text-2); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar-signout {
  color: var(--text-1); text-decoration: none; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 6px 12px; background: var(--bg-3);
}
.topbar-signout:hover, .topbar-signout:focus-visible { color: var(--text-0); border-color: var(--accent); outline: none; }

.segmented { display: inline-flex; align-items: center; gap: 2px; padding: 3px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-2); }
.segmented-label { font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2); padding: 0 10px 0 12px; }
.segmented button {
  border: none; background: transparent; color: var(--text-1); font-size: 13px; font-weight: 600;
  padding: 7px 16px; border-radius: 999px; cursor: pointer;
}
.segmented button.active { background: var(--accent); color: var(--accent-ink); }
.segmented + .segmented { margin-left: 14px; }

.body-row { display: flex; flex: 1; min-height: 0; }
.sidebar { width: 216px; flex: none; background: var(--bg-1); border-right: 1px solid var(--border); padding: 16px 10px; overflow-y: auto; }
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--radius-sm);
  color: var(--text-1); font-size: 13.5px; font-weight: 500; cursor: pointer; margin-bottom: 2px;
}
.nav-item svg { width: 17px; height: 17px; flex: none; }
.nav-item:hover { background: var(--bg-2); }
.nav-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* Correct role-gated visibility from the very first paint (CSS attribute selector on <html
   data-role>), not only after JS runs - a hard reload must never show all 7 nav items at once. */
html[data-role="manager"] .nav-item.agent-only { display: none; }
html[data-role="agent"] .nav-item.manager-only { display: none; }

.main { flex: 1; overflow-y: auto; padding: 24px 28px 60px; }
.page { display: none; }
.page.active { display: block; }

.page-header { margin-bottom: 18px; }
.intelligence-hero-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.intelligence-avatar { width: 64px; height: 64px; object-fit: contain; flex: none; margin-top: -6px; }
.page-title { font-size: 22px; }
.page-subtitle { color: var(--text-1); font-size: 13px; margin-top: 4px; max-width: 640px; }

.date-range-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
.date-input {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text-0); padding: 8px 12px; font-size: 13px; width: 140px;
}

.btn {
  border: 1px solid var(--border-strong); background: var(--bg-2); color: var(--text-0);
  padding: 8px 16px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 600; cursor: pointer;
}
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.sm { padding: 5px 12px; font-size: 12.5px; }

.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 20px; }
.kpi-tile { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 18px; }
.kpi-tile.clickable { cursor: pointer; transition: border-color 0.15s, transform 0.1s; }
.kpi-tile.clickable:hover { border-color: var(--accent); }
.kpi-tile.clickable:active { transform: scale(0.99); }
canvas { cursor: pointer; }
.kpi-overline { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2); margin-bottom: 8px; }
.kpi-value { font-family: var(--font-display); font-size: 26px; font-weight: 700; }
.kpi-value.accent { color: var(--accent); }
.kpi-value.ok { color: var(--ok); }
.kpi-value.warn { color: var(--warn); }
/* A table cell whose direction matters (a change column): green is better, amber is worse. */
.data-table td.ok { color: var(--ok); }
.data-table td.warn { color: var(--warn); }
.kpi-value.fix-coaching { color: var(--fix-coaching); }
.kpi-value.fix-process { color: var(--fix-process); }
.kpi-value.fix-customer { color: var(--fix-customer); }
.kpi-caption { font-size: 11.5px; color: var(--text-2); margin-top: 4px; }

.card { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px; margin-bottom: 16px; }
.card-title { font-size: 14px; font-weight: 700; margin-bottom: 4px; }
.card-caption { font-size: 11.5px; color: var(--text-2); margin-bottom: 14px; }

.panel-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 980px) { .panel-grid { grid-template-columns: 1fr; } }

/* ============ RESOLUTION SPINE ============ */
.spine-header { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.spine-bar { display: flex; height: 52px; border-radius: var(--radius-md); overflow: hidden; border: 1px solid var(--border); }
.spine-segment {
  display: flex; align-items: center; justify-content: center; font-family: var(--font-display);
  font-weight: 700; font-size: 15px; color: var(--text-0); cursor: pointer; border: none;
  transition: filter 0.15s;
}
.spine-segment:hover, .spine-segment:focus-visible { filter: brightness(1.12); }
.spine-segment.coaching { background: var(--fix-coaching); }
.spine-segment.process { background: var(--fix-process); }
.spine-segment.customer { background: var(--fix-customer); }
.spine-segment.unknown { background: var(--fix-unknown); color: var(--text-1); font-weight: 600; font-size: 13px; }
.spine-legend { display: flex; gap: 24px; margin-top: 12px; flex-wrap: wrap; }
.spine-legend-item { display: flex; gap: 8px; font-size: 12px; }
.spine-legend-swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; margin-top: 3px; }
.spine-legend-reasons { color: var(--text-2); }
.spine-legend-label { font-weight: 600; }

.chip { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 600; border: 1px solid transparent; }
.chip.ok { background: var(--ok-soft); color: var(--ok); border-color: rgba(67,201,138,0.3); }
.chip.warn { background: var(--warn-soft); color: var(--warn); border-color: rgba(230,176,76,0.3); }
.chip.err { background: var(--err-soft); color: var(--err); border-color: rgba(240,115,133,0.3); }
.chip.info { background: var(--info-soft); color: var(--info); border-color: rgba(94,178,230,0.3); }
.chip.neutral { background: rgba(255,255,255,0.06); color: var(--text-1); border-color: var(--border); }
.chip.fix-coaching { background: var(--fix-coaching-soft); color: var(--fix-coaching); border-color: rgba(240,116,140,0.3); }
.chip.fix-process { background: var(--fix-process-soft); color: var(--fix-process); border-color: rgba(224,173,76,0.3); }
.chip.fix-customer { background: var(--fix-customer-soft); color: var(--fix-customer); border-color: rgba(94,178,230,0.3); }

table.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
/* A table wider than its card scrolls inside the card. Without this the page's own scroll area
   moved the table past the card's right edge on a narrow screen, so the rows ran on with no
   background behind them (QA > Agents, Dean, 26 October 2026). */
.card:has(table.data-table) { overflow-x: auto; }
table.data-table th {
  text-align: left; font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2);
  padding: 8px 12px; border-bottom: 1px solid var(--border-strong);
}
table.data-table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
table.data-table th.sortable:hover { color: var(--text-0); }
table.data-table td { padding: 10px 12px; border-bottom: 1px solid rgba(255,255,255,0.05); }
table.data-table tbody tr:hover { background: rgba(255,255,255,0.03); }
.mono { font-family: 'Consolas', monospace; }
.muted { color: var(--text-2); }
/* A defined term: dotted underline, help cursor, definition on hover (see Glossary). */
.term { border-bottom: 1px dotted var(--text-2); cursor: help; text-decoration: none; }
.pagination { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; font-size: 12px; color: var(--text-2); }

.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px; }
.tab-btn {
  background: none; border: none; color: var(--text-2); font-size: 13.5px; font-weight: 600;
  padding: 10px 6px; margin-right: 18px; border-bottom: 2px solid transparent; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
}
.tab-btn.active { color: var(--text-0); border-bottom-color: var(--accent); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
.tab-count { background: var(--warn-soft); color: var(--warn); font-size: 10.5px; font-weight: 700; padding: 1px 7px; border-radius: 999px; }

.toggle-group { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.toggle-chip {
  background: var(--bg-3); border: 1px solid var(--border-strong); color: var(--text-1);
  padding: 7px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.toggle-chip.active.coaching { background: var(--fix-coaching-soft); border-color: var(--fix-coaching); color: var(--fix-coaching); }
.toggle-chip.active.process { background: var(--fix-process-soft); border-color: var(--fix-process); color: var(--fix-process); }
.toggle-chip.active.customer { background: var(--fix-customer-soft); border-color: var(--fix-customer); color: var(--fix-customer); }

.switch { position: relative; display: inline-block; width: 38px; height: 22px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track { position: absolute; inset: 0; background: var(--bg-3); border: 1px solid var(--border-strong); border-radius: 999px; cursor: pointer; transition: 0.15s; }
.switch .track::before { content: ""; position: absolute; width: 16px; height: 16px; left: 2px; top: 2px; background: var(--text-0); border-radius: 50%; transition: 0.15s; }
.switch input:checked + .track { background: var(--accent); border-color: var(--accent); }
.switch input:checked + .track::before { transform: translateX(16px); }
.switch input:focus-visible + .track { outline: 2px solid var(--accent); outline-offset: 2px; }

.form-field { margin-bottom: 16px; }
.form-field label { display: block; font-size: 12.5px; font-weight: 600; margin-bottom: 6px; color: var(--text-1); }
.form-field input, .form-field textarea, .form-field select {
  width: 100%; background: var(--bg-3); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  color: var(--text-0); padding: 10px 12px; font-size: 13px;
}
.helper-text { font-size: 12px; color: var(--text-2); }
.alert { display: flex; gap: 10px; padding: 12px 16px; border-radius: var(--radius-md); font-size: 12.5px; margin-bottom: 14px; }
.alert.info { background: var(--info-soft); border: 1px solid rgba(94,178,230,0.3); color: var(--info); }

/* ============ CALL DETAIL MODAL ============ */
.modal-backdrop {
  display: none; position: fixed; inset: 0; background: rgba(4,10,17,0.72); z-index: 100;
  align-items: flex-start; justify-content: center; padding: 40px 20px; overflow-y: auto;
}
.modal-backdrop.open { display: flex; }
.modal {
  background: var(--bg-1); border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  max-width: 720px; width: 100%; padding: 24px 26px 26px; flex: none;
}
.modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 4px; }
.modal-title { font-size: 18px; }
.modal-subtitle { color: var(--text-2); font-size: 12px; margin-top: 3px; }
.modal-close { background: none; border: none; color: var(--text-2); font-size: 20px; cursor: pointer; line-height: 1; padding: 4px; }
.modal-close:hover { color: var(--text-0); }
.modal-section { margin-top: 18px; }
.modal-section-title { font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2); margin-bottom: 8px; font-weight: 700; }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 20px; }
.field-row { display: flex; flex-direction: column; gap: 3px; }
.field-label { font-size: 11px; color: var(--text-2); display: flex; align-items: center; gap: 6px; }
.field-value { font-size: 13.5px; font-weight: 500; }
.confidence-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.confidence-dot.high { background: var(--ok); }
.confidence-dot.mid { background: var(--warn); }
.confidence-dot.low { background: var(--err); }
.confidence-dot.na { background: var(--text-2); opacity: 0.5; }
.confidence-pct { font-size: 10px; color: var(--text-2); font-weight: 600; }
.journey-arc { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
.journey-arc .arrow { color: var(--text-2); }

/* ============ LIVE INDICATOR ============ */
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ok);
  margin-right: 5px; animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.kpi-value.updating { transition: color 0.3s; }

@media (max-width: 720px) { .field-grid { grid-template-columns: 1fr; } }

/* Added on top of the mockup: it defines only `.alert.info`, but the calibration banner needs a
   pass/fail state (its own screenshot shows a green PASSED banner). Same construction as
   `.alert.info`, using the existing semantic tokens. */
.alert.ok { background: var(--ok-soft); border: 1px solid rgba(67,201,138,0.3); color: var(--ok); }
.alert.warn { background: var(--warn-soft); border: 1px solid rgba(230,176,76,0.3); color: var(--warn); }
.alert.err { background: var(--err-soft); border: 1px solid rgba(240,115,133,0.3); color: var(--err); }

/* =====================================================================================
   MudBlazor bridge.

   MudBlazor is still used for the things it does well and the mockup only sketches: dialogs,
   snackbars, date pickers, selects and switches. Everything above is the mockup's own design
   system, so point MudBlazor's palette variables at the SAME tokens - otherwise a dialog or a
   dropdown drops a light-theme surface into the middle of a dark page.
   ===================================================================================== */
:root {
    --mud-palette-background: var(--bg-0);
    --mud-palette-background-gray: var(--bg-0);
    --mud-palette-surface: var(--bg-2);
    --mud-palette-drawer-background: var(--bg-1);
    --mud-palette-drawer-text: var(--text-1);
    --mud-palette-appbar-background: var(--bg-1);
    --mud-palette-appbar-text: var(--text-0);
    --mud-palette-text-primary: var(--text-0);
    --mud-palette-text-secondary: var(--text-2);
    --mud-palette-text-disabled: var(--text-2);
    --mud-palette-action-default: var(--text-2);
    --mud-palette-divider: var(--border);
    --mud-palette-divider-light: var(--border);
    --mud-palette-lines-default: var(--border);
    --mud-palette-lines-inputs: var(--border-strong);
    --mud-palette-table-lines: var(--border);
    --mud-palette-table-hover: rgba(255, 255, 255, 0.03);
    --mud-palette-primary: var(--accent);
    --mud-palette-primary-text: var(--accent-ink);
    --mud-palette-secondary: var(--accent);
    --mud-palette-success: var(--ok);
    --mud-palette-warning: var(--warn);
    --mud-palette-error: var(--err);
    --mud-palette-info: var(--info);
    --mud-default-borderradius: var(--radius-sm);
    --mud-typography-default-family: var(--font-body);
}

/* MudChart is no longer used for dashboards (Chart.js renders those, per the mockup), but keep
   these so any remaining instance is not a light-grey island. */
.mud-chart path[stroke="#e0e0e0"],
.mud-chart line[stroke="#e0e0e0"] {
    stroke: var(--border);
}

.mud-chart text {
    fill: var(--text-2);
}

.mud-chart-donut .mud-donut-ring {
    stroke: var(--border);
}

/* MudBlazor paints its own surface on dialogs/menus; make sure they land on the token surface. */
.mud-dialog,
.mud-popover,
.mud-picker-container {
    background-color: var(--bg-1) !important;
    border: 1px solid var(--border-strong);
}

.mud-dialog-title {
    font-family: var(--font-display);
}

/* Blazor's dev-time error toast - left readable rather than restyled. */
.blazor-error-boundary {
    background: #b32121;
    padding: 1rem;
    color: white;
}

.blazor-error-boundary::after {
    content: "An error has occurred."
}

/* ============================================================================
   Auth pages: sign-in, access-denied, signed-out.

   Their own block because they are the only screens rendered OUTSIDE the app
   shell, under MinimalLayout. A person seeing these is either not signed in or
   not permitted, and showing them the sidebar would imply otherwise.

   Sized for reading rather than for density: these are the only pages in the
   product whose job is to explain something to somebody who is stuck.
   ============================================================================ */

/* The accent is pinned here rather than left to fall through to :root. The decision that
   these pages are neutral rather than tenant-accented is already made and reasoned on
   .auth-button below; this only makes it explicit at the top of the block, so the auth
   screens keep the product colour if the :root default is ever repointed at a tenant.
   Worth knowing the consequence either way: TapStitch's accent is blue, so a person signs
   in on green and arrives somewhere blue. That is the honest trade for not serving tenant
   branding to an anonymous caller. */
.auth-shell {
  --accent: #2ec27e;
  --accent-soft: rgba(46,194,126,0.14);
  --accent-ink: #041c12;

  min-height: 100vh;
  display: flex;
  /* Column, because the product mark sits above the card rather than inside it. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem 1.25rem;
  /* A wash rather than a flat fill, so the card sits on something. Both stops are
     existing tokens; no new colour is introduced. */
  background:
    radial-gradient(1100px 520px at 50% -8%, var(--bg-2), transparent 62%),
    var(--bg-0);
}

/* The product mark, above the card. These screens carried no identity at all, which
   matters most on sign-in: it is the first thing a client ever sees of the platform. */
.auth-mark {
  text-align: center;
  margin-bottom: 1.75rem;
  /* The card's own width, explicitly. The shell centres its children by shrinking them to their
     content, and this box's content is the caption: with the lockup at 46px that was the wider of
     the two and the image centred inside it, at 110px the image is wider than the caption, the box
     stayed caption-sized, and the absolutely positioned image hung off its left edge. Seen on DEV
     2026-09-10 as the mark sitting a hundred pixels left of the card. */
  width: 100%;
  max-width: 27rem;
}

/* Fixed height, with the lockup and the orb stacked in it. They cross-fade rather than
   replace each other in flow, so committing to sign in does not make the page jump. */
/* Sized up on 2026-09-10 at Dean's request: the lockup at 46px read as an icon above the card
   rather than the product's name. First 110px, then 96px after Dean saw it on DEV: about two
   thirds of the card's width, and it still clears the card comfortably on a 768px-high window. */
.auth-mark-media {
  position: relative;
  height: 104px;
}

.auth-mark-media > * {
  position: absolute;
  inset: 0;
  margin: auto;
  width: auto;
  transition: opacity 220ms ease, transform 220ms ease;
}

.auth-lockup { height: 96px; opacity: 1; }
.auth-orb    { height: 104px; opacity: 0; transform: scale(0.82); }

.auth-eyebrow {
  position: relative;
  margin: 14px 0 0;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-2);
}

/* The two captions occupy the same line. The leaving one is taken out of flow so the
   resting caption keeps the element's height and nothing moves when they swap. */
.auth-eyebrow-rest { transition: opacity 160ms ease; }

/* nowrap because this caption is longer than the resting one, and the element's height is
   set by the resting caption. Allowed to wrap, its second line drops out of the box and
   crowds the card below. It is comfortably narrower than the card at this size. */
/* Centred by offset rather than by text-align: this caption is wider than the box, which is
   sized by the shorter resting one, and text-align stops centring once a nowrap line
   overflows its container. It would sit left-aligned and visibly off-centre. */
.auth-eyebrow-leaving {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 220ms ease 80ms;
}

/* ---- the hand-off, once the sign-in anchor has been clicked ---------------------- */

.auth-shell.is-leaving .auth-lockup { opacity: 0; transform: scale(1.06); }

.auth-shell.is-leaving .auth-orb {
  opacity: 1;
  transform: scale(1);
  animation: auth-orb-pulse 1.6s ease-in-out 220ms infinite;
}

.auth-shell.is-leaving .auth-eyebrow-rest { opacity: 0; }
.auth-shell.is-leaving .auth-eyebrow-leaving { opacity: 1; }

/* The card recedes so the orb is what the eye is left on. Not hidden: somebody who lands
   back here wants to see the screen they came from, not an empty one. */
.auth-shell.is-leaving .auth-card {
  opacity: 0.45;
  transition: opacity 260ms ease;
}

/* The action has already been taken. Blocking pointer events stops a second click firing
   a second challenge while the first navigation is still in flight. */
.auth-shell.is-leaving .auth-actions { pointer-events: none; }

@keyframes auth-orb-pulse {
  0%, 100% { transform: scale(1);    opacity: 1;    }
  50%      { transform: scale(1.06); opacity: 0.82; }
}

/* No reduced-motion block here on purpose: the global one further up this file already
   neutralises every animation and transition with !important, and the state stays legible
   without motion because the caption changes and the card dims. */

.auth-card {
  width: 100%;
  /* Narrow, because the card now holds only the message and the action. Anything
     longer than a couple of lines opts into .auth-prose below. */
  max-width: 27rem;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2rem 2rem 1.9rem;
  text-align: center;
}

/* Access-denied is the exception: two paragraphs of explanation plus a footnote. Centred
   prose at that length is genuinely harder to read, so a page carrying real explanation
   widens the card and sets its body copy left. :has() rather than a layout parameter
   keeps this a styling concern; where it is unsupported the card simply stays narrow,
   which is still legible. */
.auth-card:has(.auth-prose) {
  max-width: 34rem;
  padding: 2.25rem 2.25rem 2rem;
}

.auth-card:has(.auth-prose) .auth-body,
.auth-card:has(.auth-prose) .auth-footnote {
  text-align: left;
}

/* And its action stays its natural width, left with the prose. Stretching a secondary
   action across the card would give "sign out" more weight than the explanation above it,
   which is the wrong way round on a page whose job is to explain. */
.auth-card:has(.auth-prose) .auth-actions { justify-content: flex-start; }
.auth-card:has(.auth-prose) .auth-actions > :only-child { flex: 0 1 auto; }

.auth-title {
  margin: 0 0 0.7rem;
  /* Display face, like every other heading in the product. These were inheriting the
     body face, which is why they read as paragraphs set larger rather than as titles. */
  font-family: var(--font-display);
  font-size: 1.2rem;
  line-height: 1.3;
  letter-spacing: -0.01em;
  font-weight: 600;
  color: var(--text-0);
  text-wrap: balance;
}

.auth-body {
  margin: 0 0 1rem;
  font-size: 0.97rem;
  line-height: 1.62;
  color: var(--text-1);
}

.auth-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin: 1.5rem 0 1.1rem;
}

/* One action per screen, so it takes the full width of the card and becomes the obvious
   thing to do. Where a screen ever has two, they wrap and share the row instead. */
.auth-actions > :only-child {
  flex: 1 1 100%;
  text-align: center;
}

/* Anchors, not buttons, and they must still look like the primary action. An
   authentication challenge has to be a real navigation, so these cannot be
   <button> elements without silently doing nothing. */
.auth-button,
.auth-button-secondary {
  display: inline-block;
  padding: 0.65rem 1.15rem;
  border-radius: 6px;
  font-size: 0.94rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

/* The design system's own tokens rather than literals. --accent and --accent-ink are
   tenant-scoped, and hardcoding either would make this the one button in the product that
   ignores the client's palette.

   Note these pages get the :root default accent, not the tenant's. The tenant palette is applied
   by MainLayout from tenant config, which needs an authenticated call, and a person on these pages
   has not made one. Neutral is the honest answer for access-denied and signed-out. If the sign-in
   page should carry the client's accent, the branding subset would have to be served anonymously,
   which is a deliberate decision rather than something to slip in. */
.auth-button {
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid transparent;
}

.auth-button:hover { filter: brightness(1.08); }

.auth-button-secondary {
  background: transparent;
  color: var(--text-0);
  border: 1px solid var(--border-strong);
}

.auth-button-secondary:hover { background: var(--bg-2); }

.auth-button:focus-visible,
.auth-button-secondary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.auth-footnote {
  margin: 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-2);
}

/* Filter bar: a row of toggle buttons above a table.

   The selected state uses the tenant accent rather than a fixed colour, the same way .btn.primary
   does, so a filter reads as selected in whichever tenant's palette is loaded. aria-pressed carries
   the same state for a screen reader, since colour alone is not an accessible affordance. */
.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.filter-bar .btn-active {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

/* Transcript view.

   Two layouts over the same parsed turns: "turns" for voice and chat, which are short alternating
   exchanges, and "email", where one message is several paragraphs and needs a visible boundary. Both
   preserve line breaks inside a turn, because an email body's paragraphing is part of what a reviewer
   is judging. */
.transcript-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.transcript-toggle { display: flex; gap: 6px; }
.transcript-raw {
  white-space: pre-wrap; font-size: 12.5px; margin: 0;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px;
}

.transcript { display: flex; flex-direction: column; gap: 10px; }
.transcript .turn-speaker {
  font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-2); margin-bottom: 3px; display: flex; align-items: center; gap: 6px;
}
.transcript .turn-text { white-space: pre-wrap; font-size: 13.5px; line-height: 1.55; }
.transcript .turn-automated-chip { text-transform: none; letter-spacing: 0; font-weight: 500; }

/* Voice and chat: compact, indented by side so the exchange is scannable. */
.transcript-turns .turn { max-width: 78%; }
.transcript-turns .turn-business {
  align-self: flex-start;
  border-left: 2px solid var(--accent); padding-left: 10px;
}
.transcript-turns .turn-customer {
  align-self: flex-end; text-align: left;
  border-left: 2px solid var(--border-strong); padding-left: 10px;
}
.transcript-turns .turn-automated { border-left-style: dashed; }

/* Email: full-width message blocks with a boundary, because the bodies are long. */
.transcript-email .turn {
  background: var(--bg-1); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 12px 14px;
}
.transcript-email .turn-business { border-left: 3px solid var(--accent); }
.transcript-email .turn-automated { border-left-style: dashed; }

/* A system notice is neither party talking, so it reads as an aside in both layouts. */
.transcript .turn-system {
  align-self: center; max-width: 100%; text-align: center;
  color: var(--text-2); font-size: 12.5px; font-style: italic;
}
.transcript .turn-system .turn-speaker { display: none; }

/* A failed load: message above, retry below. .alert is a flex row by default. */
.alert.load-error { flex-direction: column; align-items: flex-start; }

/* Human Score tab: transcript beside the scorecard (2026-09-18). The transcript column sticks to the
   top of the viewport and scrolls on its own, so a lead reads and scores without switching tabs. On a
   narrow window the two stack, transcript first, because reading precedes scoring. */
.score-split { display: grid; grid-template-columns: minmax(320px, 5fr) minmax(360px, 6fr); gap: 16px; align-items: start; }
.score-split-transcript { position: sticky; top: 12px; max-height: calc(100vh - 24px); overflow: auto; }
.score-split-transcript .score-transcript-card { margin-bottom: 0; }
.score-split-form { min-width: 0; }

/* The saved-score banner stacks its sentence above the "Next to score" row, and that row wraps,
   so the pill, the remaining count and the Queue button never sit on top of each other. */
.alert.alert-stack { flex-direction: column; align-items: flex-start; gap: 10px; }
.next-to-score { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* A drop-down sizes to its longest option. .date-input's fixed 140px was cutting "Human-scored or
   not" to "Human-scored c" on the Pipeline filter (Dean, 21 September 2026). */
select.date-input { width: auto; min-width: 150px; }

/* A multi-line box fills its column. The fixed 140px meant for date pickers made the roster and
   scored-today lists on Settings > Users wrap every address mid-word. */
textarea.date-input { width: 100%; max-width: 100%; }
@media (max-width: 1100px) {
  .score-split { grid-template-columns: 1fr; }
  .score-split-transcript { position: static; max-height: 60vh; }
}

/* ---- the reconnect banner (App.razor, js/circuit.js) ------------------------------ */
/* Hidden until Blazor adds a state class. A slim bar at the top rather than the default
   full-page box: a dropped connection usually comes back in a few seconds (26 October 2026). */
#components-reconnect-modal { display: none; }
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected { display: block; }
.reconnect-backdrop { position: fixed; inset: 0; z-index: 3000; background: rgba(0,0,0,0.12); cursor: progress; }
.reconnect-bar {
  position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 3001;
  display: flex; align-items: center; gap: 10px; padding: 8px 14px; max-width: calc(100vw - 32px);
  background: var(--bg-2); border: 1px solid var(--border-strong); border-radius: 999px;
  color: var(--text-0); font-size: 12.5px; box-shadow: 0 6px 24px rgba(0,0,0,0.35);
}
.reconnect-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warn); flex: none; animation: reconnect-pulse 1.2s ease-in-out infinite; }
@keyframes reconnect-pulse { 50% { opacity: 0.3; } }
.reconnect-text-show, .reconnect-text-failed, .reconnect-text-rejected { display: none; }
.components-reconnect-show .reconnect-text-show,
.components-reconnect-failed .reconnect-text-failed,
.components-reconnect-rejected .reconnect-text-rejected { display: inline; }
.components-reconnect-failed .reconnect-dot { background: var(--err); animation: none; }
