/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN SYSTEM — component layer
   ═══════════════════════════════════════════════════════════════════════════

   Loaded AFTER report-detail.css and BEFORE dark-mode.css. That position is
   deliberate and load order matters:

     azia-slim → main → components → charts → report-detail → THIS → dark-mode

   • It comes after the four legacy sheets so it can settle the long-running
     disagreements between them (three separate `.card` definitions, nine
     different card paddings, 100 distinct font sizes) without editing 500KB
     of accreted rules in place.
   • It comes BEFORE dark-mode.css so `body.theme-dark` still wins everything.
     Never add a dark override here — it belongs in dark-mode.css.

   Tokens are defined in main.css `:root`. This file must not invent values;
   if something here needs a number that is not a token, the token is missing.

   Structure:
     1. Layout shell        6. Tables
     2. Typography          7. Forms
     3. Cards               8. Badges & chips
     4. Focus               9. Scrollbars & misc
     5. Buttons
   ═══════════════════════════════════════════════════════════════════════════ */


/* ───────────────────────────────────────────────────────────────────────────
   1. LAYOUT SHELL
   ───────────────────────────────────────────────────────────────────────────
   The single biggest structural problem: the shell declared `max-width: none`
   (components.css) and `max-width: 100% !important` (report-detail.css), so
   content grew without limit. Measured at a 1512px window: cards 1351px wide,
   with agency table columns flung 1200px apart. On a 27" monitor it is worse.

   Fix: cap the measure at --content-max and centre it, with a fluid --gutter
   that grows from 20px on a phone to 56px on a desktop. Nothing is clipped —
   the cap is on the CONTENT COLUMN, not on any individual card, so wide
   tables still scroll inside their own container. */
#pageContent .az-content-body,
.app-dashboard-content .container,
#toolContainer > .container {
    max-width: var(--content-max) !important;
    margin-left: auto !important;
    margin-right: auto !important;
    padding-left: var(--gutter) !important;
    padding-right: var(--gutter) !important;
    box-sizing: border-box !important;
}

/* #pageContent carried its own asymmetric inset (20px 60.48px 0) on top of
   the container's. Zero the SIDES and let the shell above own the gutters, so
   the left and right margins are finally equal and defined in one place.

   The TOP is not zeroed. #pageContent is also .page-content-surface, and an
   ID selector here beats that class rule — zeroing the top from this block
   silently cancelled the surface's own padding-top and left the first card
   sitting flush against the fixed header on every page.

   --space-8 (32px), not --card-gap: the header is a fixed bar the content
   scrolls under, so the first card needs a visibly larger breath than the
   24px used between two cards in the same column, or it reads as attached
   to the header rather than as the start of the page. */
#pageContent {
    padding-left: 0 !important;
    padding-right: 0 !important;
    padding-top: var(--space-8) !important;
}

/* Vertical rhythm between cards. Was 1.25rem (20px) and scaled with the
   user's text-size setting, which made the gap drift as text grew. */
.page-content-surface {
    /* !important because app.js writes an inline display on this element as a
       defensive "make sure content is visible" step after login. Without it the
       container falls back to `block` and every `gap` below is silently ignored. */
    display: grid !important;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--card-gap);
    /* Matches the #pageContent rule above so the class behaves the same
       wherever else it is used. The header→first-card breath is one step
       larger than the card→card gap; see the note there. */
    padding-top: var(--space-8);
    padding-bottom: var(--space-12);
}

/* The page ground has to be darker than the cards or there is nothing for
   the card to sit ON. The old gradient resolved to near-#fff at the top,
   which is why the first card had no visible top edge. */
body.az-dashboard-page {
    background: var(--bg);
    color: var(--text-body);
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* The truck wallpaper is decorative. With opaque cards it no longer bleeds
   through content, but it still sat at an opacity that competed with real
   data in the empty regions of a page. Knock it back so it reads as texture. */
.truck-sprite { opacity: 0.05; }


/* ───────────────────────────────────────────────────────────────────────────
   2. TYPOGRAPHY
   ───────────────────────────────────────────────────────────────────────────
   Sizes come from the scale (see main.css). What this section fixes is the
   OTHER half of hierarchy: weight, tracking and leading. The app leaned on
   font-weight 800 at 13px for labels, which at small sizes reads as noise
   rather than emphasis, and left display type at default 0 tracking, which
   makes large headings look loose. */

#pageContent h1,
#pageContent h2,
#pageContent h3,
#pageContent h4,
.dashboard-section-header .az-dashboard-title,
.tool-intro-card h1 {
    color: var(--text);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
    font-weight: var(--fw-bold);
    text-wrap: balance;
}

/* Display sizes need MORE negative tracking than headings, or the counters
   drift apart. */
.dashboard-section-header .az-dashboard-title,
.tool-intro-card h1,
#pageContent h1 {
    letter-spacing: var(--ls-display);
}

/* Body copy: 1.6 leading, and a measure cap so a paragraph never runs the
   full 1440px. ~75ch is the readable maximum. */
#pageContent p,
.dashboard-section-header .az-dashboard-text,
.tool-intro-card .subtitle,
.az-dashboard-text,
.settings-desc {
    line-height: var(--lh-normal);
    color: var(--text-soft);
    max-width: 78ch;
}

/* The uppercase micro-label used for every "eyebrow" and card label.
   700 (not 800) + real tracking is what makes these legible at 12-13px. */
.dashboard-section-header .section-label,
.tool-intro-card .section-label,
.ov-card-label,
.ov-side-label,
.ov-section-label,
.ov-side-sub-label {
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
    color: var(--text-muted);
    line-height: var(--lh-snug);
}

/* Card VALUES — the numbers a user actually came to read. These were
   competing with their own labels at nearly the same size and weight. */
.ov-card-val,
.ov-side-value,
.report-stat-value {
    color: var(--text);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
}
.ov-side-value {
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-tight);
}

/* Tabular figures. Any column of numbers that does not use this will not
   align vertically, because the default proportional digits vary in width. */
.ov-side-value,
.ov-card-val,
.report-stat-value,
.agency-table td,
.agency-table th,
.violations-table td,
td[class*="-num"],
.agency-num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* Contrast floor. #94a3b8 measured 2.4-2.5:1 as text on a card — the worst
   failures in the whole audit ("Unsafe Driving" 2.37, "Tier" 2.50, the
   Predictive loading states 2.52). --text-faint is the lightest grey that
   still clears AA at body size. */
.ov-trend-label,
.ov-side-sub-label,
.analytics-empty,
.pred-suggestions-loading,
.prospect-map-hint,
.report-history-empty,
.agency-table-count,
.agency-block-count,
.predictive-score-arrow {
    color: var(--text-faint) !important;
}


/* ───────────────────────────────────────────────────────────────────────────
   3. CARDS
   ───────────────────────────────────────────────────────────────────────────
   Surface/border/radius/elevation are already unified at their three
   original sources. What is left is PADDING, which was the least consistent
   thing in the app — nine values measured across the tabs:
     11.2/13.6 · 15.2/17.6 · 16/20 · 17.6 · 20/24 · 24 · 24/32 · 13.6/16 · 0
   Two are enough: --card-pad (24px) for a normal card, --card-pad-sm (16px)
   for a dense/nested one. */
#pageContent .card:not(.profile-history-card),
#pageContent .subpanel-card,
#pageContent .violation-card,
#pageContent .analytics-tool-card,
#pageContent .fixed-scroll-card,
#pageContent .info-panel {
    padding: var(--card-pad) !important;
}

.dashboard-section-header {
    padding: var(--card-pad) !important;
    margin-bottom: 0;
}

/* The grid `gap` is now the ONLY thing that spaces top-level cards. Legacy
   per-card margin-bottoms stack on top of it (Prospects measured 24 + 28 =
   52px between two cards while every other page had 24px), so they are
   cleared here. Spacing that comes from two places drifts apart. */
.page-content-surface > .card,
.page-content-surface > .dashboard-section,
.page-content-surface > .dashboard-section-header,
.page-content-surface > section,
.page-content-surface > div {
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}

/* Cards that exist only to hold a full-bleed child (a map, a chart canvas,
   a table that draws its own edges) keep their zero inset. */
#pageContent .card.is-flush,
#pageContent .map-shell,
#pageContent .full-width-card {
    padding: 0 !important;
    overflow: hidden;
}

/* One transition declaration for every card type, so a hover reads the same
   everywhere. Explicit properties (never `all`) — transitioning `all` also
   animates layout properties and causes jank on scroll. */
.card,
.ov-card,
.subpanel-card,
.info-panel,
.violation-card,
.basics-chart-container,
.map-shell,
.dashboard-section-header,
.tool-intro-card,
.app-state-card,
.agency-dash-card,
.report-history-item,
/* The big section cards were left off this list, so their edge changed INSTANTLY on
   hover while every small card eased. A snap reads as a redraw, not as a response —
   which is most of why the cards felt inert. */
.dashboard-section,
.agency-block,
.bug-thread,
.agency-method-row {
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
    will-change: auto;
}

/* The agency DASHBOARD table is deliberately absent from every group above.
   Its cards are half-width by design, and the generic treatment — 16px of horizontal
   cell padding with nowrap headers — pushed the Renewals header band 14px past its
   own card. It keeps the compact rules in report-detail.css that were tuned for that
   width (a clipping insurer column, a stacked Expires cell). */

/* ── Measure ────────────────────────────────────────────────────────────────
   The prose classes only — explanatory sub-lines under a heading, empty states,
   tool descriptions. NOT applied to headings (short by nature), table cells
   (columns own their width), or the ACORD sheet's .acf-* legal print, which is a
   1:1 reproduction of a paper form whose lines genuinely run the full page. */
.agency-sub,
.agency-empty,
.az-dashboard-text,
.dashboard-section-sub,
.analytics-tool-desc,
.acord25-blanknote,
.bug-thread-body,
.prospect-map-hint {
    max-width: var(--measure);
}

/* Two tiers of hover, on purpose.
   
   A LIFT means "this does something when you click it". The big section cards do not,
   so giving them the same treatment as a saved report would be a lie told sixty times
   per page. They get the edge alone: the card acknowledges the pointer and stays put.
   Interactive cards keep the lift + shadow below. */
.dashboard-section:hover,
.agency-block:hover,
.bug-thread:hover,
.agency-method-row:hover {
    border-color: var(--border-emphasis);
}

/* Saved-report cards on Profile are CLICKABLE, so they get the affordance
   the rest of the cards deliberately do not: a pointer cursor and a
   brand-tinted edge on hover. A lift on a non-interactive card is decoration;
   on an interactive one it is feedback. */
.report-history-item,
.agency-dash-card[data-href],
.agency-table tbody tr[data-href],
.carrier-search-result {
    cursor: pointer;
}
.report-history-item {
    background: var(--surface-alt);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    padding: var(--card-pad);
    box-shadow: var(--shadow-sm);
}
.report-history-item:hover {
    transform: var(--lift);
    box-shadow: var(--shadow-md);
    border-color: var(--accent);
}
.report-history-item:active {
    transform: translateY(0);
    box-shadow: var(--shadow-xs);
}

/* Nested cards must NOT repeat the parent's elevation — stacked shadows are
   what produce the "wrapper card behind the cards" look. A card inside a
   card gets a border and the recessed surface instead. */
#pageContent .card .card,
#pageContent .card .subpanel-card,
#pageContent .subpanel-card .card {
    box-shadow: none !important;
    background: var(--surface) !important;
    padding: var(--card-pad-sm) !important;
}
#pageContent .card .card:hover,
#pageContent .card .subpanel-card:hover,
#pageContent .subpanel-card .card:hover {
    transform: none !important;
    box-shadow: none !important;
}

/* The stat tiles on a saved-report card. */
.report-stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    gap: var(--space-1);
}
.report-stat-title {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
    color: var(--text-muted);
}
.report-history-meta-grid { gap: var(--space-4); margin-top: var(--space-5); }
.report-history-row { padding: 0; margin-bottom: var(--card-gap); }


/* ───────────────────────────────────────────────────────────────────────────
   4. FOCUS — WCAG 2.4.7 / 2.4.11
   ───────────────────────────────────────────────────────────────────────────
   The audit found 3 `:focus-visible` rules in ~500KB of CSS. Custom
   backgrounds on buttons and inputs suppress the UA default ring, so a
   keyboard user navigating this app had almost no idea where they were.

   :focus-visible (not :focus) so a mouse click does not leave a ring behind.
   The double ring — white inset + brand outline — is what keeps the
   indicator visible against BOTH a white card and a coloured button. */
#pageContent a:focus-visible,
#pageContent button:focus-visible,
#pageContent input:focus-visible,
#pageContent select:focus-visible,
#pageContent textarea:focus-visible,
#pageContent [tabindex]:focus-visible,
#pageContent [role="button"]:focus-visible,
.app-header a:focus-visible,
.app-header button:focus-visible,
.app-header input:focus-visible,
.nav-tab:focus-visible,
.nav-user-btn:focus-visible,
.nav-user-item:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-offset);
    border-radius: var(--radius-sm);
}

/* Filled controls get the ring as a shadow instead, so the offset does not
   cut into an adjacent element in a tight toolbar. */
.btn:focus-visible,
#pageContent .agency-btn:focus-visible,
#pageContent .acord25-btn:focus-visible,
.agency-table tbody tr:focus-visible,
.report-history-item:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-offset);
    box-shadow: var(--focus-ring);
}

/* Skip-link support: if a focused element is scrolled to, keep it clear of
   the fixed header. */
#pageContent :target,
#pageContent :focus-visible {
    scroll-margin-top: calc(var(--app-header-height) + var(--space-6));
}


/* ───────────────────────────────────────────────────────────────────────────
   5. BUTTONS
   ───────────────────────────────────────────────────────────────────────────
   Three tiers, one geometry. Every tier changes on hover AND commits on
   active — the app previously had buttons that did nothing at all on hover.

   Sizing note: 40px min-height is above the 24px WCAG 2.5.8 minimum target
   and comfortable for a mouse without looking oversized in a dense toolbar. */
#pageContent .btn,
#pageContent button.btn,
#pageContent .agency-btn,
#pageContent .acord25-btn,
#pageContent .prospect-map-btn,
#pageContent .text-size-btn,
#pageContent .pred-adj-btn,
#pageContent .pred-field-reset-btn,
#pageContent .card-refresh-btn,
.app-header .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 40px;
    padding: var(--space-2) var(--space-4);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-normal);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}

/* PRIMARY — one per view. The page's single most important action. */
#pageContent .btn-primary,
.app-header .btn-primary,
#pageContent .btn.btn-primary,
#pageContent .agency-btn-primary,
#pageContent .acord25-btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    box-shadow: var(--shadow-xs);
}
#pageContent .btn-primary:hover:not(:disabled),
#pageContent .agency-btn-primary:hover:not(:disabled),
#pageContent .acord25-btn-primary:hover:not(:disabled),
.app-header .btn-primary:hover:not(:disabled) {
    background: var(--accent2);
    border-color: var(--accent2);
    box-shadow: var(--shadow-sm);
}
#pageContent .btn-primary:active:not(:disabled),
#pageContent .agency-btn-primary:active:not(:disabled),
#pageContent .acord25-btn-primary:active:not(:disabled) {
    background: var(--accent2);
    transform: translateY(1px);
    box-shadow: none;
}

/* SECONDARY — the common case. Bordered, not filled, so it never competes
   with the primary. Explicit `color` here is also what stops the Bootstrap
   `.btn { color: #031b4e }` default from leaking through. */
#pageContent .btn-secondary,
#pageContent .btn-outline,
#pageContent .agency-btn:not(.agency-btn-primary):not(.agency-btn-danger),
#pageContent .acord25-btn:not(.acord25-btn-primary):not(.acord25-btn-danger),
#pageContent .btn:not(.btn-primary):not(.btn-danger):not(.btn-link):not(.btn-ghost) {
    background: var(--surface-alt);
    border-color: var(--border-emphasis);
    color: var(--text-body);
}
#pageContent .btn-secondary:hover:not(:disabled),
#pageContent .btn-outline:hover:not(:disabled),
#pageContent .agency-btn:not(.agency-btn-primary):not(.agency-btn-danger):hover:not(:disabled),
#pageContent .acord25-btn:not(.acord25-btn-primary):not(.acord25-btn-danger):hover:not(:disabled),
#pageContent .btn:not(.btn-primary):not(.btn-danger):not(.btn-link):not(.btn-ghost):hover:not(:disabled) {
    background: var(--surface);
    border-color: var(--text-faint);
    color: var(--text);
}
#pageContent .btn-secondary:active:not(:disabled),
#pageContent .btn:not(.btn-primary):not(.btn-danger):not(.btn-link):not(.btn-ghost):active:not(:disabled) {
    background: var(--surface-sunken);
    transform: translateY(1px);
}

/* TERTIARY / ghost — toolbar and inline actions. No chrome until hovered. */
#pageContent .btn-ghost,
#pageContent .btn-link {
    background: transparent;
    border-color: transparent;
    color: var(--accent-ink);
    box-shadow: none;
}
#pageContent .btn-ghost:hover:not(:disabled),
#pageContent .btn-link:hover:not(:disabled) {
    background: var(--accent-soft);
    color: var(--accent2);
}

/* DESTRUCTIVE */
#pageContent .btn-danger,
#pageContent .agency-btn-danger,
#pageContent .acord25-btn-danger {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}
#pageContent .btn-danger:hover:not(:disabled),
#pageContent .agency-btn-danger:hover:not(:disabled),
#pageContent .acord25-btn-danger:hover:not(:disabled) {
    background: var(--danger-ink);
    border-color: var(--danger-ink);
}

#pageContent .btn:disabled,
#pageContent .btn[disabled],
#pageContent .agency-btn:disabled,
#pageContent .acord25-btn:disabled,
#pageContent button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none !important;
    box-shadow: none !important;
}

/* The header search button was a bare blue block with square inner corners. */
.app-header-search button,
.sidebar-search-card.app-header-search button {
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    transition: background-color var(--dur-fast) var(--ease);
    cursor: pointer;
}
.app-header-search button:hover { background: var(--accent2) !important; }


/* ───────────────────────────────────────────────────────────────────────────
   6. TABLES
   ───────────────────────────────────────────────────────────────────────────
   `.agency-table td { padding: 0.2rem }` gave ~3px of vertical padding —
   rows with no breathing room at all, which is most of why the agency
   screens read as cramped. Row height here is driven by padding, not a
   fixed height, so a wrapped cell still grows.

   Zebra striping is deliberately NOT used: with a visible header, a real
   row border and a hover state, stripes add a third competing signal. */
.agency-table,
.agency-customer-table,
.agency-billing-table,
.agency-policy-table,
.agency-map-table,
.prospect-table,
.insurance-table,
.violations-table,
.authority-history-table,
.incident-history-table,
.ch-history-table,
.oos-rates-table,
.peer-group-table,
.coi-history-table,
#pageContent table.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.agency-table th,
.agency-customer-table th,
.agency-billing-table th,
.agency-policy-table th,
.agency-map-table th,
.prospect-table th,
.insurance-table th,
.violations-table th,
.authority-history-table th,
.incident-history-table th,
.ch-history-table th,
.oos-rates-table th,
.peer-group-table th,
.coi-history-table th,
#pageContent table.data-table th {
    padding: var(--space-3) var(--space-4);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
    color: var(--text-muted);
    background: var(--surface);
    border-bottom: 1px solid var(--border-strong);
    text-align: left;
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 1;
}

.agency-table td,
.agency-customer-table td,
.agency-billing-table td,
.agency-policy-table td,
.agency-map-table td,
.prospect-table td,
.insurance-table td,
.violations-table td,
.authority-history-table td,
.incident-history-table td,
.ch-history-table td,
.oos-rates-table td,
.peer-group-table td,
.coi-history-table td,
#pageContent table.data-table td {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
    color: var(--text-body);
    vertical-align: middle;
}

.agency-table tbody tr,
.agency-customer-table tbody tr,
.agency-billing-table tbody tr,
.agency-policy-table tbody tr,
.agency-map-table tbody tr,
.prospect-table tbody tr,
.insurance-table tbody tr,
.violations-table tbody tr,
.authority-history-table tbody tr,
.incident-history-table tbody tr,
.ch-history-table tbody tr,
.oos-rates-table tbody tr,
.peer-group-table tbody tr,
.coi-history-table tbody tr,
#pageContent table.data-table tbody tr {
    transition: background-color var(--dur-fast) var(--ease);
}
.agency-table tbody tr:hover,
.agency-customer-table tbody tr:hover,
.agency-billing-table tbody tr:hover,
.agency-policy-table tbody tr:hover,
.agency-map-table tbody tr:hover,
.prospect-table tbody tr:hover,
.insurance-table tbody tr:hover,
.violations-table tbody tr:hover,
.authority-history-table tbody tr:hover,
.incident-history-table tbody tr:hover,
.ch-history-table tbody tr:hover,
.oos-rates-table tbody tr:hover,
.peer-group-table tbody tr:hover,
.coi-history-table tbody tr:hover,
#pageContent table.data-table tbody tr:hover {
    background: var(--accent-soft);
}
.agency-table tbody tr:last-child td,
.agency-customer-table tbody tr:last-child td,
.agency-billing-table tbody tr:last-child td,
.agency-policy-table tbody tr:last-child td,
.agency-map-table tbody tr:last-child td,
.prospect-table tbody tr:last-child td,
.insurance-table tbody tr:last-child td,
.violations-table tbody tr:last-child td,
.authority-history-table tbody tr:last-child td,
.incident-history-table tbody tr:last-child td,
.ch-history-table tbody tr:last-child td,
.oos-rates-table tbody tr:last-child td,
.peer-group-table tbody tr:last-child td,
.coi-history-table tbody tr:last-child td,
#pageContent table.data-table tbody tr:last-child td { border-bottom: none; }

/* Numeric columns right-align, so magnitudes line up and can be compared
   by length as well as value. */
.agency-table th.agency-num,
.agency-table td.agency-num,
#pageContent table.data-table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.agency-table-wrap,
.prospect-table-wrap,
.incident-history-table-wrap,
.ch-history-table-wrap,
.revocations-oos-table-wrap {
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    overflow: auto;
    background: var(--surface-alt);
}

/* The FLEET sheet is the exception: it is a fixed-layout table sized to its card, so there
   is nothing to its right to scroll to. `overflow: auto` above (this file loads after
   report-detail.css, so it was winning) still reserved a horizontal scrollbar gutter and
   put the sideways scroll back that the fixed table exists to remove. */
#agencyFleetTab .agency-table-wrap { overflow-x: hidden; }

/* Same story for its header row. The table-header rule above gives every th 16px of side
   padding and `white-space: nowrap`, which on a FIXED table means the header — not the data
   — decides how wide a column is: "DATE ADDED" demanded 123px to caption 84px of dates, and
   the width came out of Address, the one column that actually needed it. Here the header
   wraps to a second line and takes the same tight inset as the cells under it. */
#agencyFleetTab .agency-table-auto th {
    white-space: normal;
    /* The SAME side inset as the cells below. 8px each side left only 26px of a 42px column
       at a 1100px viewport, which is narrower than the word "UNIT" — so the header broke
       mid-word into three lines and overflowed. A header may take two lines; it may not be
       narrower than the widest word it contains. */
    padding: var(--space-2) 0.3rem var(--space-1);
    vertical-align: bottom;
    line-height: 1.25;
    /* Sentence case, not the uppercase every other table header uses. Uppercase runs about a
       quarter wider, and on a FIXED table that width comes out of the data: at a 1100px
       window "YEAR" alone was wider than the whole Year column and broke mid-word. A caption
       must never be the widest thing in its own column. */
    text-transform: none;
    font-size: var(--fs-xs);
    letter-spacing: 0;
}

/* And its cells. The generic table rule gives every td 16px of side padding — comfortable
   for a report table, but on this fixed sheet it was taking 32px out of EVERY column, which
   is where the clipped values came from: a 60px Unit # column had 28px left for the value.
   The fleet sheet is a data grid, so it takes a data-grid inset. */
#agencyFleetTab .agency-table-auto td { padding: 0.25rem 0.3rem; }

/* ── Import column picker ──────────────────────────────────────────────────
   The tick is the decision, so it leads the row and the rest of the row follows it:
   an unticked column dims and its destination select is disabled, which says
   "this one is not coming in" without a second label to read. */
.agency-map-check { width: 2.25rem; text-align: center; }
.agency-map-check input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.agency-map-table tr.is-off .agency-map-header { color: var(--text-faint); }
.agency-map-table tr.is-off .agency-map-select { opacity: 0.55; }
.agency-map-table tr.is-on .agency-map-header { color: var(--text); font-weight: var(--fw-semibold); }


/* ───────────────────────────────────────────────────────────────────────────
   REPORT TABLES — they must stay INSIDE their card
   ───────────────────────────────────────────────────────────────────────────
   Measured at a 1512px window: the Inspection card is 654px wide and its table
   rendered 668px; the Crash card is 654px and its table 1050px — 428px of it
   hanging past the card's right edge, cut off by the card border with no way to
   reach it. The wrapper HAS `overflow: auto`, so why didn't it scroll?

   Because it is a flex item, and a flex item's automatic minimum size is its
   content: it refused to shrink below the table's min-content width, grew to
   1050px itself, and overflowed the card instead of scrolling inside it. The
   whole chain (section-content → #crashHistory → the wrap) needs min-width: 0
   before `overflow: auto` can do anything at all.

   Two fixes together, in this order:
     1. min-width: 0 down the chain, so the wrapper is capped by the CARD and
        scrolls when the table is wider — never the other way round.
     2. a denser inset, so at ordinary widths there is nothing to scroll. The
        generic table rule gives every cell 16px of side padding; on the crash
        table's NINE columns that alone is 288px of padding. */
.fixed-scroll-card .section-content,
#inspectionHistory,
#crashHistory,
.incident-history-table-wrap,
.scrollable-table,
.violation-card,
.violation-library-card,
.violations-grid > * {
    min-width: 0;
}

/* The wrapper is the scroller. It is capped by the card, never by the table. */
.incident-history-table-wrap,
.scrollable-table {
    max-width: 100%;
    overflow: auto;
}

/* Dense inset + 13px for the REPORT tables only. These are read as a dense grid
   of dates, codes and counts, not as a report table with room to breathe — and
   the room the generic rule reserves is exactly what pushed them off the card. */
.incident-history-table th,
.incident-history-table td,
.violations-table th,
.violations-table td {
    padding: 0.3rem 0.45rem;
    font-size: var(--fs-sm);
}
/* A floor, so the tighter inset above cannot squeeze a one-character column ("0", "Yes")
   below the width at which it stops being readable as a column at all — the failure this
   table was rebuilt to avoid. Columns wider than this are unaffected. */
.incident-history-table th,
.incident-history-table td {
    min-width: 42px;
}
.incident-history-table th,
.violations-table th {
    font-size: var(--fs-2xs);
    letter-spacing: 0.02em;
    white-space: normal;      /* a two-word heading may take two lines */
    line-height: 1.25;
}
/* The VIOLATION column wraps — it carries the code and the wording together, which is prose
   and belongs on as many lines as it needs.
   The incident tables do NOT wrap, and deliberately so. Every cell on them is a short value
   (a date, a plate, a state, a count) and main.css sets `white-space: nowrap` for a reason
   that is recorded there: squeezed columns broke them one character per line into vertical
   text. Letting the free-text crash columns wrap was tried here and traded that fault back
   in — in a narrow card they collapse into a tall stack — for almost no width saved. The
   crash table is kept on one line per row and the CARD scrolls it instead, which is what
   the containment rules above are for. */
.violations-grid .violations-table td:nth-child(1) {           /* Violation      */
    white-space: normal;
    word-break: break-word;
    min-width: 7ch;
}

/* A box around every cell is 18px of border on a nine-column row, and it reads as a grid
   of boxes rather than rows of data. Horizontal rules only: lighter, and narrower. */
.incident-history-table th,
.incident-history-table td {
    border: none;
    border-bottom: 1px solid var(--border-subtle);
}
.incident-history-table thead th {
    border-bottom: 1px solid var(--border-strong);
}

/* The scroller must FILL its card. `.fixed-scroll-card .section-content` is a row flex
   container and these are its items with no `flex` of their own, so they sized to the
   table's content and left a ragged gap on the right — 77px at 1512px, against 31px on
   the left. They stretch now, which is also what makes the two margins even. */
#inspectionHistory,
#crashHistory {
    flex: 1 1 auto;
    width: 100%;
}

/* ── Analytics: Carrier Status & Metrics ──────────────────────────────────
   Eight columns of monthly snapshots. Measured in a 658px card: the header rendered
   at 16px with 16px of side padding, so the table wanted 1191px against 606px of
   room - nearly double - and the reader had to scroll sideways to see any of it.
   The values are short (a date, a count, a rate), so the width was going almost
   entirely into chrome. Denser cells + a header that may wrap gets all eight into
   the card at desktop widths. */
#pageContent .ch-history-table th,
#pageContent .ch-history-table td {
    padding: 0.3rem 0.45rem;
    font-size: var(--fs-xs);
}
#pageContent .ch-history-table th {
    /* A two-word heading takes two lines rather than setting the column's width - but it
       wraps BETWEEN words, never inside one. With eight equal fixed columns "CARRIER
       OPERATION" was breaking as "OPERATI / ON", which is unreadable. Sentence case buys
       the ~25% that uppercase costs, which is what lets each word fit its column. */
    white-space: normal;
    /* break-word is the LAST resort, not the first: sentence case (below) makes every
       header word fit its column, so in practice nothing breaks - but a word that
       genuinely cannot fit must break rather than overflow the cell, because an
       overflowing header inflates the wrapper and puts the sideways scroll back. */
    overflow-wrap: break-word;
    overflow: hidden;
    text-transform: none;
    line-height: 1.25;
    letter-spacing: 0;
    vertical-align: bottom;
}
/* The wrapper fills the card, so the eight columns share the card's width instead of
   each taking its content width and overflowing. It still scrolls if a narrow window
   leaves genuinely too little. */
#pageContent .ch-history-table { table-layout: fixed; }
#pageContent .ch-history-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#pageContent .ch-history-table-wrap { max-width: 100%; }

/* ── Violations: one Violation column ─────────────────────────────────────
   The code used to have a column of its own. It is not a separate fact — a violation is
   said and written as "392.7(a) - Driver failing to conduct pre-trip inspection" — and
   the extra column cost the wording the width it needed. The code leads its cell, in
   tabular figures so a column of them lines up, and the three short numeric columns are
   held to their content so everything left over goes to the wording. */
/* No #pageContent prefix on this block: the violations grid is styled by
   .violations-grid rules in components.css, which this file loads after — equal
   specificity plus later source order is enough to win, and it keeps the table
   correct wherever it is rendered rather than only inside one container. */
.violations-grid .violations-table td.violation-cell {
    white-space: normal;
    word-break: break-word;
    line-height: var(--lh-snug);
}
.violation-code {
    font-weight: var(--fw-semibold);
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* table-layout: fixed is what makes the wording WRAP instead of setting the table's
   width. Left to `auto`, the Violation column took its longest description as its
   width and pushed #, OOS and Sev off the right-hand edge of the card. Fixed, the
   three numeric columns take the small widths below and the wording gets the rest,
   wrapping to as many lines as it needs. */
.violations-grid .violations-table { table-layout: fixed; }
/* 60px, not 44. MEASURED: the headers are sort BUTTONS carrying a label and a sort arrow,
   and at 44px "OOS ▲" needed 54px and "Sev ▲" 48px — both overflowed their own cell, which
   is what made the severity column look broken. 60px clears the widest state (the arrow
   appears and disappears as the sort changes) and takes the 48px it needs from the Violation
   column, which is `auto` and wraps. That is the trade the card wants: prose can use another
   line, a number cannot use another pixel. */
.violations-grid .violations-table th:nth-child(n+2),
.violations-grid .violations-table td:nth-child(n+2) {
    width: 60px;
    white-space: nowrap;
    text-align: right;
}
.violations-grid .violations-table th:first-child,
.violations-grid .violations-table td:first-child { width: auto; }
/* On a phone the three numeric columns were taking 132px of a 342px card, leaving the
   wording 117px — narrow enough to read one or two words per line. They hold a single
   digit, so they give up what they do not need and the wording keeps its floor. */
@media (max-width: 480px) {
    .violations-grid .violations-table th:nth-child(n+2),
    .violations-grid .violations-table td:nth-child(n+2) { width: 44px; }
    .violations-grid .violations-table th,
    .violations-grid .violations-table td { padding: 0.45rem 0.3rem; }
}

/* The violations library list: rows in a card, not translucent panels floating on
   one. This kept the old glassmorphism — 73% white with a blur and a white border —
   which on the now-opaque white card reads as a faint wash behind every entry. */
.violation-library-item {
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border-subtle);
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    padding: var(--space-3) 0;
}
.violation-library-item:last-child { border-bottom: none; }
.violation-library-results { gap: 0; }

/* The tab banner's category icon sits absolutely against the left edge, and the
   text is kept clear of it by a left padding on the banner. The card-padding rule
   in section 3 above out-specified that padding and reset it to 24px, so the text
   ran back underneath the icon. Restored here, after that rule. */
#pageContent .dashboard-section-header.has-tab-icon {
    padding-left: 150px !important;
}
@media (max-width: 640px) {
    #pageContent .dashboard-section-header.has-tab-icon {
        padding-left: 88px !important;
    }
}

/* Sortable header affordance — these were sort targets with no cursor hint. */
.agency-table th[data-sort],
.agency-table th.sortable {
    cursor: pointer;
    user-select: none;
}
.agency-table th[data-sort]:hover,
.agency-table th.sortable:hover {
    background: var(--surface-sunken);
    color: var(--text);
}


/* ───────────────────────────────────────────────────────────────────────────
   7. FORMS
   ───────────────────────────────────────────────────────────────────────────
   There was no base input rule anywhere in the app — every input was styled
   ad hoc by its component, which is why field heights and radii varied
   from screen to screen. */
#pageContent input[type="text"],
#pageContent input[type="search"],
#pageContent input[type="number"],
#pageContent input[type="email"],
#pageContent input[type="password"],
#pageContent input[type="date"],
#pageContent input[type="tel"],
#pageContent select:not(.agency-tag),
#pageContent textarea,
.app-header-search input {
    padding: var(--space-2) var(--space-3);
    font-size: var(--fs-base);
    font-family: inherit;
    line-height: var(--lh-snug);
    color: var(--text);
    background: var(--surface-alt);
    border: 1px solid var(--border-emphasis);
    border-radius: var(--radius-sm);
    box-sizing: border-box;
    transition: border-color var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease);
}

/* Single-line controls only. Never add `textarea` here: this selector out-specifies the
   component CSS that gives each textarea its designed height, so a min-height set here
   silently overrides all of them. */
#pageContent input[type="text"],
#pageContent input[type="search"],
#pageContent input[type="number"],
#pageContent input[type="email"],
#pageContent input[type="password"],
#pageContent input[type="date"],
#pageContent input[type="tel"],
#pageContent select:not(.agency-tag),
.app-header-search input {
    min-height: 40px;
}

#pageContent input:hover:not(:disabled):not(:focus),
#pageContent select:hover:not(:disabled):not(:focus),
#pageContent textarea:hover:not(:disabled):not(:focus) {
    border-color: var(--text-faint);
}

/* Focus on an input is the one place the ring replaces the outline entirely:
   the border colour change plus the halo is the conventional field-focus
   pattern and reads more clearly than an offset outline around a box. */
#pageContent input:focus,
#pageContent select:focus,
#pageContent textarea:focus,
.app-header-search input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
}

#pageContent input::placeholder,
#pageContent textarea::placeholder {
    color: var(--text-faint);
    opacity: 1;   /* Firefox dims placeholders by default, compounding the
                     contrast problem on an already-light grey. */
}

#pageContent label,
.change-pw-field label,
.settings-label {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-body);
    line-height: var(--lh-snug);
}

#pageContent input:disabled,
#pageContent select:disabled,
#pageContent textarea:disabled {
    background: var(--surface-sunken);
    color: var(--text-faint);
    cursor: not-allowed;
}

/* Invalid state — colour is not the only signal (WCAG 1.4.1); the border
   weight changes too. */
#pageContent input[aria-invalid="true"],
#pageContent .is-invalid {
    border-color: var(--danger);
    border-width: 2px;
}

/* Settings rows: the label/control pair was stretched across 1300px. Cap the
   row so the control stays near the text it belongs to. */
.settings-item {
    padding: var(--space-5) 0;
    gap: var(--space-6);
    border-bottom: 1px solid var(--border-subtle);
    max-width: 1080px;
}
.settings-container { padding: 0; }
.settings-group { margin-bottom: var(--space-6); }


/* ───────────────────────────────────────────────────────────────────────────
   8. BADGES & CHIPS
   ───────────────────────────────────────────────────────────────────────────
   Status pills carried white text on mid-tone fills — "Pass (1-49)" measured
   2.95:1 and the Predictive zone tags 3.19-3.30:1, all AA failures at their
   size. The soft-fill + dark-ink pattern below clears AA at every status
   colour while keeping the same colour coding. */
.badge,
.chip,
.pred-zone-tag,
.pred-group-tag,
.prospect-sel-chip,
.status-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-normal);
    border-radius: var(--radius-xs);
    border: 1px solid transparent;
    white-space: nowrap;
}

.badge-success, .status-pass, .pred-zone-pass {
    background: var(--success-soft) !important;
    color: var(--success-ink) !important;
    border-color: color-mix(in srgb, var(--success) 28%, transparent) !important;
}
.badge-warning, .status-warn, .pred-zone-optional {
    background: var(--warning-soft) !important;
    color: var(--warning-ink) !important;
    border-color: color-mix(in srgb, var(--warning) 28%, transparent) !important;
}
.badge-danger, .status-fail, .pred-zone-fail {
    background: var(--danger-soft) !important;
    color: var(--danger-ink) !important;
    border-color: color-mix(in srgb, var(--danger) 28%, transparent) !important;
}
.badge-info, .prospect-sel-chip, .pred-group-tag {
    background: var(--accent-soft) !important;
    color: var(--accent-ink) !important;
    border-color: var(--accent-soft-border) !important;
}

/* Where a SOLID status fill is genuinely wanted (the ISS score header), the
   text must be pure white at >=600 weight and the fill must be the dark ink
   variant, not the mid-tone. */
.solid-status {
    color: #fff !important;
    font-weight: var(--fw-semibold);
}


/* ───────────────────────────────────────────────────────────────────────────
   9. SCROLLBARS & MISC
   ─────────────────────────────────────────────────────────────────────────── */
#pageContent ::-webkit-scrollbar { width: 10px; height: 10px; }
#pageContent ::-webkit-scrollbar-track { background: var(--surface); border-radius: var(--radius-pill); }
#pageContent ::-webkit-scrollbar-thumb {
    background: var(--border-emphasis);
    border-radius: var(--radius-pill);
    border: 2px solid var(--surface);
}
#pageContent ::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
#pageContent * { scrollbar-color: var(--border-emphasis) var(--surface); }

/* Anything that acts like a control should say so. The app had several
   click handlers bound to plain divs with a default arrow cursor. */
[onclick]:not(input):not(button):not(select):not(textarea),
[data-action],
.nav-tab,
.clickable {
    cursor: pointer;
}

/* Dividers inside a card use the subtle token — a full-strength border
   inside a bordered card reads as a second box. */
#pageContent hr,
.analytics-tool-header,
.agency-block-head {
    border-color: var(--border-subtle);
}

/* Selection colour picks up the brand instead of the OS default. */
::selection { background: var(--accent-soft); color: var(--text); }


/* ───────────────────────────────────────────────────────────────────────────
   RESPONSIVE
   ───────────────────────────────────────────────────────────────────────────
   --gutter already scales fluidly. These only handle the two places where
   the desktop inset is too generous to be worth keeping on a small screen. */
@media (max-width: 720px) {
    :root {
        --card-pad: var(--space-4);
        --card-gap: var(--space-4);
    }
    .settings-item {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-3);
    }
}

/* Print: strip elevation and colour-on-colour, which cost toner and read
   worse than plain rules on paper. */
@media print {
    #pageContent .card,
    #pageContent .ov-card,
    #pageContent .subpanel-card {
        box-shadow: none !important;
        border: 1px solid #999 !important;
        break-inside: avoid;
    }
    .truck-sprite, .moving-trucks-bg { display: none !important; }
}


/* ───────────────────────────────────────────────────────────────────────────
   10. UTILITIES
   ───────────────────────────────────────────────────────────────────────────
   A deliberately tiny set — only for markup built as strings in app.js, where
   the alternative is a hardcoded hex in a `style=""` attribute. An inline
   colour cannot respond to body.theme-dark, which is exactly how the incident
   loading state ended up at 3.17:1 in dark mode. Prefer a real component
   class over these; reach for a utility only for one-off states. */
.ds-text-muted { color: var(--text-soft); font-size: var(--fs-md); }
.ds-state-muted {
    padding: var(--space-6);
    color: var(--text-soft);
    font-size: var(--fs-base);
    text-align: center;
}

/* Empty / loading states. These were plain grey sentences floating in an
   otherwise empty card; centring them and giving them the recessed surface
   makes "nothing here yet" read as a designed state rather than a failure. */
.analytics-empty,
.pred-suggestions-loading,
.report-history-empty,
.agency-empty {
    display: block;
    padding: var(--space-8) var(--space-6);
    text-align: center;
    color: var(--text-soft) !important;
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
}
