/* ═══════════════════════════════════════════════════════════════════════════
   ALCINEX COMPONENT LAYER  ·  Batch 7
   ───────────────────────────────────────────────────────────────────────────
   Bootstrap 5.3's components, themed against the tokens.

   HOW THIS FILE IS ALLOWED TO WORK:
     · It reads LAYER 2 semantic tokens only. Never a raw hex, never a raw px,
       never a LAYER 1 ramp — a component that reads --ax-blue-600 directly
       stops responding to dark mode, because dark mode moves --ax-primary.
     · It prefers setting Bootstrap's own component variables (--bs-btn-*,
       --bs-card-*, ...) over writing competing rules. Bootstrap then themes
       itself. `!important` appears in exactly two places, both commented where
       they occur: the loading button's transparent label, and the print block
       (which has to beat inline style attributes it does not control).
     · It knows NOTHING about which direction is applied. If a direction ever
       needs a rule in here, the tokens are wrong, not the markup.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── Base ───────────────────────────────────────────────────────────────── */

body {
    font-family: var(--ax-font-sans);
    font-size: var(--ax-text-md);
    color: var(--ax-text-primary);
    background: var(--ax-surface-app);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
    color: var(--ax-text-heading);
    font-weight: var(--ax-weight-semibold);
    line-height: var(--ax-leading-tight);
}

/* ── Tabular numerals ────────────────────────────────────────────────────────
   THE most important class in this file for this particular application.

   Every money cell, quantity, docket number, serial and date gets `.ax-num`.
   Proportional digits are narrower for 1 than for 0, so a column of rupee
   amounts fails to line up on the decimal and becomes impossible to compare
   down the column — which is the whole reason a dealer looks at the column.
   Inter carries real `tnum`; this turns it on and right-aligns.               */
.ax-num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
    text-align: right;
    white-space: nowrap;
}
.ax-num--left { text-align: left; }

/* Negative / overdue money. The one place dark red is allowed on a number. */
.ax-num--negative { color: var(--ax-danger); }


/* ── Focus ──────────────────────────────────────────────────────────────────
   One ring, everywhere, always visible. Never `outline: none` without a
   replacement — keyboard users lose their place instantly and silently.       */
:focus-visible {
    outline: 2px solid var(--ax-focus-ring);
    outline-offset: var(--ax-focus-offset);
    border-radius: var(--ax-radius-sm);
}


/* ── Buttons ────────────────────────────────────────────────────────────────
   Sized off --ax-control-h so buttons and inputs line up on a filter bar
   without per-screen nudging.                                                 */

.btn {
    --bs-btn-padding-y: 0;
    --bs-btn-padding-x: var(--ax-space-6);
    --bs-btn-font-size: var(--ax-text-md);
    --bs-btn-font-weight: var(--ax-weight-medium);
    --bs-btn-border-radius: var(--ax-radius-md);
    --bs-btn-line-height: var(--ax-leading-snug);
    min-height: var(--ax-control-h);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ax-space-3);
    transition: background-color var(--ax-transition-fast),
                border-color var(--ax-transition-fast),
                color var(--ax-transition-fast);
}
.btn-sm { --bs-btn-padding-x: var(--ax-space-5); --bs-btn-font-size: var(--ax-text-sm); min-height: var(--ax-control-h-sm); }
.btn-lg { --bs-btn-padding-x: var(--ax-space-8); --bs-btn-font-size: var(--ax-text-md); min-height: var(--ax-control-h-lg); }

.btn-primary {
    --bs-btn-bg: var(--ax-primary);
    --bs-btn-border-color: var(--ax-primary);
    --bs-btn-color: var(--ax-primary-on);
    --bs-btn-hover-bg: var(--ax-primary-hover);
    --bs-btn-hover-border-color: var(--ax-primary-hover);
    --bs-btn-hover-color: var(--ax-primary-on);
    --bs-btn-active-bg: var(--ax-primary-active);
    --bs-btn-active-border-color: var(--ax-primary-active);
    --bs-btn-disabled-bg: var(--ax-primary);
    --bs-btn-disabled-border-color: var(--ax-primary);
    --bs-btn-disabled-color: var(--ax-primary-on);
    --bs-btn-disabled-opacity: 0.45;
}

/* Secondary = the neutral action next to a primary. Not Bootstrap's gray fill,
   which reads as heavily as the primary and creates two competing CTAs. */
.btn-secondary {
    --bs-btn-bg: var(--ax-surface-raised);
    --bs-btn-border-color: var(--ax-border-strong);
    --bs-btn-color: var(--ax-text-primary);
    --bs-btn-hover-bg: var(--ax-surface-hover);
    --bs-btn-hover-border-color: var(--ax-border-strong);
    --bs-btn-hover-color: var(--ax-text-primary);
    --bs-btn-active-bg: var(--ax-surface-sunken);
    --bs-btn-active-border-color: var(--ax-border-strong);
    --bs-btn-active-color: var(--ax-text-primary);
    --bs-btn-disabled-bg: var(--ax-surface-disabled);
    --bs-btn-disabled-border-color: var(--ax-border);
    --bs-btn-disabled-color: var(--ax-text-disabled);
}

.btn-outline-primary {
    --bs-btn-color: var(--ax-primary);
    --bs-btn-border-color: var(--ax-primary);
    --bs-btn-bg: transparent;
    --bs-btn-hover-bg: var(--ax-primary-subtle);
    --bs-btn-hover-border-color: var(--ax-primary);
    --bs-btn-hover-color: var(--ax-primary-hover);
    --bs-btn-active-bg: var(--ax-primary);
    --bs-btn-active-color: var(--ax-primary-on);
    --bs-btn-disabled-color: var(--ax-text-disabled);
    --bs-btn-disabled-border-color: var(--ax-border);
}

/* Ghost — row actions and toolbar buttons, where a border per button would
   turn a table row into a grid of boxes. */
.btn-ghost {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-color: var(--ax-text-muted);
    --bs-btn-hover-bg: var(--ax-surface-hover);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-hover-color: var(--ax-text-primary);
    --bs-btn-active-bg: var(--ax-surface-sunken);
    --bs-btn-active-color: var(--ax-text-primary);
    --bs-btn-disabled-color: var(--ax-text-disabled);
}

/* DESTRUCTIVE ONLY. See TOKENS.md — dark red is never a Save button and never
   a primary CTA. If you are reaching for this on a submit, reach for
   .btn-primary instead and put the warning in the confirm dialog. */
.btn-danger {
    --bs-btn-bg: var(--ax-danger);
    --bs-btn-border-color: var(--ax-danger);
    --bs-btn-color: var(--ax-danger-on);
    --bs-btn-hover-bg: var(--ax-danger-700, var(--ax-danger));
    --bs-btn-hover-border-color: var(--ax-danger-700, var(--ax-danger));
    --bs-btn-hover-color: var(--ax-danger-on);
    --bs-btn-disabled-bg: var(--ax-danger);
    --bs-btn-disabled-opacity: 0.45;
}
.btn-outline-danger {
    --bs-btn-color: var(--ax-danger);
    --bs-btn-border-color: var(--ax-danger);
    --bs-btn-bg: transparent;
    --bs-btn-hover-bg: var(--ax-danger-subtle);
    --bs-btn-hover-color: var(--ax-danger);
    --bs-btn-hover-border-color: var(--ax-danger);
}

.btn-icon { --bs-btn-padding-x: 0; width: var(--ax-control-h); }
.btn-icon.btn-sm { width: var(--ax-control-h-sm); }

/* Loading. `aria-busy="true"` is the accessible half — the spinner alone tells
   a screen reader nothing.

   The one `!important` in this file. Every .btn-* variant sets --bs-btn-color at
   the same specificity as this rule, so without it the label stays visible
   underneath the spinner on whichever variant happens to be declared later.
   Hiding the label via a token would mean adding a "transparent" case to all
   six variants, which is worse. */
.btn.is-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-loading::after {
    content: '';
    position: absolute;
    width: var(--ax-spinner-size); height: var(--ax-spinner-size);
    border: var(--ax-border-width-marker) solid currentColor;
    border-right-color: transparent;
    border-radius: var(--ax-radius-pill);
    color: var(--ax-primary-on);
    animation: ax-spin 0.6s linear infinite;
}
.btn-secondary.is-loading::after, .btn-ghost.is-loading::after,
.btn-outline-primary.is-loading::after { color: var(--ax-text-primary); }
@keyframes ax-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn.is-loading::after { animation-duration: 2s; } }


/* ── Cards / panels ─────────────────────────────────────────────────────── */

.card {
    --bs-card-bg: var(--ax-surface-raised);
    --bs-card-border-color: var(--ax-border);
    --bs-card-border-radius: var(--ax-radius-lg);
    --bs-card-inner-border-radius: var(--ax-radius-lg);
    --bs-card-spacer-y: var(--ax-card-padding);
    --bs-card-spacer-x: var(--ax-card-padding);
    --bs-card-cap-bg: var(--ax-surface-raised);
    --bs-card-cap-padding-y: var(--ax-space-5);
    --bs-card-cap-padding-x: var(--ax-card-padding);
    --bs-card-color: var(--ax-text-primary);
    box-shadow: var(--ax-shadow-sm);
}
.card-header {
    border-bottom: var(--ax-border-width) solid var(--ax-border);
    font-weight: var(--ax-weight-semibold);
    font-size: var(--ax-text-md);
    color: var(--ax-text-heading);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ax-space-6);
    min-height: var(--ax-control-h-lg);
}


/* ── Stat cards ─────────────────────────────────────────────────────────────
   The dashboard's headline figures. Label above, figure below: the figure is
   what the eye should land on, and putting the label first stops the user
   reading a number before knowing what it counts.                             */

.ax-stat {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    padding: var(--ax-card-padding);
    box-shadow: var(--ax-shadow-sm);
    display: flex;
    flex-direction: column;
    gap: var(--ax-space-2);
    min-width: 0;
}
.ax-stat__label {
    font-size: var(--ax-text-xs);
    font-weight: var(--ax-weight-medium);
    color: var(--ax-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    display: flex;
    align-items: center;
    gap: var(--ax-space-3);
}
.ax-stat__value {
    font-size: var(--ax-text-2xl);
    font-weight: var(--ax-weight-semibold);
    color: var(--ax-text-heading);
    line-height: var(--ax-leading-tight);
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
}
.ax-stat__value--lg { font-size: var(--ax-text-3xl); }
.ax-stat__meta { font-size: var(--ax-text-xs); color: var(--ax-text-muted); }
.ax-stat__meta--up   { color: var(--ax-success); }
.ax-stat__meta--down { color: var(--ax-danger); }
.ax-stat__accent { border-left: var(--ax-border-width-accent) solid var(--ax-primary); }


/* ── Tables ─────────────────────────────────────────────────────────────────
   BORDERS, NOT ZEBRA — chosen deliberately.

   Zebra striping was designed to help the eye track across a wide row, and it
   does. But it also consumes the row background, and in this app the row
   background is needed for meaning: overdue rows, selected rows, and the hover
   target. Striping plus a status tint gives four background colours competing
   in one table. Horizontal hairlines track the eye just as well at a 36px row
   height and leave the background free to mean something.                      */

.ax-table-wrap {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    overflow: hidden;
}
/* The scroll container is separate from the visual frame so the sticky header
   has something to stick to. */
.ax-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.ax-table {
    width: 100%;
    margin: 0;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--ax-table-font);
    color: var(--ax-text-primary);
}
.ax-table > thead > tr > th {
    height: var(--ax-table-header-h);
    padding: var(--ax-space-3) var(--ax-table-cell-px);
    background: var(--ax-surface-sunken);
    color: var(--ax-text-muted);
    font-size: var(--ax-text-2xs);
    font-weight: var(--ax-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    text-align: left;
    white-space: nowrap;
    border-bottom: var(--ax-border-width) solid var(--ax-border);
    vertical-align: middle;
}
.ax-table > tbody > tr > td {
    height: var(--ax-table-row-h);
    padding: var(--ax-table-cell-py) var(--ax-table-cell-px);
    border-bottom: var(--ax-border-width) solid var(--ax-border-subtle);
    vertical-align: middle;
    line-height: var(--ax-leading-normal);
}
.ax-table > tbody > tr:last-child > td { border-bottom: 0; }
.ax-table > tbody > tr:hover > td { background: var(--ax-surface-hover); }
.ax-table > tbody > tr.is-selected > td { background: var(--ax-surface-selected); }

/* Sticky header — the point of a dense table is long lists, and a long list
   without a sticky header means scrolling back up to remember the columns. */
.ax-table--sticky > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: var(--ax-z-sticky);
}

/* Sortable headers. The arrow is an affordance, not decoration: aria-sort on
   the th is what actually announces the state. */
.ax-table th.is-sortable { cursor: pointer; user-select: none; }
.ax-table th.is-sortable:hover { color: var(--ax-text-primary); }
.ax-table th.is-sortable::after {
    content: '\2195';
    margin-left: var(--ax-space-2);
    opacity: 0.35;
    font-size: var(--ax-text-xs);
}
.ax-table th[aria-sort="ascending"]::after  { content: '\2191'; opacity: 1; color: var(--ax-primary); }
.ax-table th[aria-sort="descending"]::after { content: '\2193'; opacity: 1; color: var(--ax-primary); }

/* Row actions stay quiet until the row is touched, but never fully hidden —
   hiding them on hover alone makes them unreachable by keyboard and on touch. */
.ax-table__actions { display: flex; gap: var(--ax-space-2); justify-content: flex-end; }
.ax-table__actions .btn { opacity: 0.65; transition: opacity var(--ax-transition-fast); }
.ax-table tr:hover .ax-table__actions .btn,
.ax-table__actions .btn:focus-visible { opacity: 1; }

/* Keep Bootstrap's own .table in step for screens not yet converted. */
.table { --bs-table-bg: var(--ax-surface-raised); font-size: var(--ax-table-font); }
.table > :not(caption) > * > * { padding: var(--ax-table-cell-py) var(--ax-table-cell-px); }


/* ── Status badges ──────────────────────────────────────────────────────────
   Subtle tint + strong text, not a saturated fill: a table row can hold three
   or four badges, and four solid blocks of colour turn the row into a stripe
   of noise. Tinted pills stay legible in bulk.

   COLOUR NEVER CARRIES THE MEANING ALONE — every badge shows its label. That
   is not only an accessibility rule; a dealer printing a list in greyscale
   still has to be able to read it.                                            */

.ax-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--ax-space-2);
    padding: var(--ax-space-1) var(--ax-space-4);
    border-radius: var(--ax-radius-pill);
    font-size: var(--ax-text-2xs);
    font-weight: var(--ax-weight-semibold);
    line-height: 1.5;
    white-space: nowrap;
    border: var(--ax-border-width) solid transparent;
}
.ax-badge--neutral { background: var(--ax-surface-sunken);   color: var(--ax-text-muted);  border-color: var(--ax-border); }
.ax-badge--info    { background: var(--ax-info-subtle);      color: var(--ax-info); }
.ax-badge--success { background: var(--ax-success-subtle);   color: var(--ax-success); }
.ax-badge--warning { background: var(--ax-warning-subtle);   color: var(--ax-warning); }
.ax-badge--danger  { background: var(--ax-danger-subtle);    color: var(--ax-danger); }
.ax-badge--primary { background: var(--ax-primary-subtle);   color: var(--ax-primary); }

/* A dot for scanning a column of badges quickly. Still alongside the label. */
.ax-badge__dot { width: 6px; height: 6px; border-radius: var(--ax-radius-pill); background: currentColor; flex: none; }


/* ── Forms ──────────────────────────────────────────────────────────────── */

.form-label {
    font-size: var(--ax-text-xs);
    font-weight: var(--ax-weight-medium);
    color: var(--ax-text-muted);
    margin-bottom: var(--ax-space-3);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
/* Required marker: the asterisk is for sighted scanning; the real signal is
   `required` on the input, which is what assistive tech reads. */
.form-label .ax-req { color: var(--ax-danger); margin-left: var(--ax-space-1); }

.form-control, .form-select {
    --bs-border-radius: var(--ax-radius-md);
    min-height: var(--ax-control-h);
    padding: var(--ax-space-3) var(--ax-control-px);
    font-size: var(--ax-text-md);
    color: var(--ax-text-primary);
    background-color: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border-strong);
    transition: border-color var(--ax-transition-fast), box-shadow var(--ax-transition-fast);
}
.form-control:focus, .form-select:focus {
    border-color: var(--ax-primary);
    box-shadow: 0 0 0 3px rgba(var(--ax-primary-rgb), 0.18);
    outline: none;
}
.form-control::placeholder { color: var(--ax-text-disabled); }
.form-control:disabled, .form-select:disabled,
.form-control[readonly] {
    background-color: var(--ax-surface-disabled);
    color: var(--ax-text-muted);
    cursor: not-allowed;
}
textarea.form-control { min-height: calc(var(--ax-control-h) * 2.2); line-height: var(--ax-leading-normal); }

.form-text { font-size: var(--ax-text-xs); color: var(--ax-text-muted); margin-top: var(--ax-space-2); }

.form-control.is-invalid, .form-select.is-invalid { border-color: var(--ax-danger); }
.form-control.is-invalid:focus { box-shadow: 0 0 0 3px rgba(var(--ax-danger-rgb), 0.18); }
.invalid-feedback { font-size: var(--ax-text-xs); color: var(--ax-danger); }
.form-control.is-valid, .form-select.is-valid { border-color: var(--ax-success); }
.valid-feedback { font-size: var(--ax-text-xs); color: var(--ax-success); }

.form-check-input { border-color: var(--ax-border-strong); }
.form-check-input:checked { background-color: var(--ax-primary); border-color: var(--ax-primary); }
.form-check-input:focus { border-color: var(--ax-primary); box-shadow: 0 0 0 3px rgba(var(--ax-primary-rgb), 0.18); }
.form-check-label { font-size: var(--ax-text-md); color: var(--ax-text-primary); }

/* Input groups — the ₹ prefix, chiefly. */
.input-group-text {
    background: var(--ax-surface-sunken);
    border: var(--ax-border-width) solid var(--ax-border-strong);
    color: var(--ax-text-muted);
    font-size: var(--ax-text-md);
    padding: 0 var(--ax-control-px);
    min-height: var(--ax-control-h);
}
/* A money input is a number that will be read down a column — tabular. */
.input-group .form-control.ax-num { text-align: right; }


/* ── Alerts ─────────────────────────────────────────────────────────────── */

.alert {
    --bs-alert-border-radius: var(--ax-radius-md);
    padding: var(--ax-space-5) var(--ax-space-7);
    font-size: var(--ax-text-md);
    border-width: var(--ax-border-width);
    border-left-width: var(--ax-border-width-accent);
    display: flex;
    align-items: flex-start;
    gap: var(--ax-space-5);
}
.alert-primary { background: var(--ax-primary-subtle); border-color: var(--ax-primary); color: var(--ax-text-primary); }
.alert-success { background: var(--ax-success-subtle); border-color: var(--ax-success); color: var(--ax-text-primary); }
.alert-warning { background: var(--ax-warning-subtle); border-color: var(--ax-warning); color: var(--ax-text-primary); }
.alert-danger  { background: var(--ax-danger-subtle);  border-color: var(--ax-danger);  color: var(--ax-text-primary); }
.alert-info    { background: var(--ax-info-subtle);    border-color: var(--ax-info);    color: var(--ax-text-primary); }
/* Body text stays --ax-text-primary rather than the semantic hue: coloured
   paragraph text is harder to read, and the left border already says which
   kind of alert this is. */


/* ── Modals ─────────────────────────────────────────────────────────────── */

.modal-content {
    --bs-modal-bg: var(--ax-surface-raised);
    --bs-modal-border-color: var(--ax-border);
    --bs-modal-border-radius: var(--ax-radius-lg);
    --bs-modal-color: var(--ax-text-primary);
    box-shadow: var(--ax-shadow-modal);
}
.modal-header {
    padding: var(--ax-space-6) var(--ax-card-padding);
    border-bottom: var(--ax-border-width) solid var(--ax-border);
}
.modal-title { font-size: var(--ax-text-lg); font-weight: var(--ax-weight-semibold); color: var(--ax-text-heading); }
.modal-body   { padding: var(--ax-card-padding); }
.modal-footer {
    padding: var(--ax-space-6) var(--ax-card-padding);
    border-top: var(--ax-border-width) solid var(--ax-border);
    gap: var(--ax-space-4);
}


/* ── Pagination ─────────────────────────────────────────────────────────── */

.pagination {
    --bs-pagination-padding-x: var(--ax-space-5);
    --bs-pagination-padding-y: var(--ax-space-3);
    --bs-pagination-font-size: var(--ax-text-sm);
    --bs-pagination-color: var(--ax-text-primary);
    --bs-pagination-bg: var(--ax-surface-raised);
    --bs-pagination-border-color: var(--ax-border);
    --bs-pagination-border-radius: var(--ax-radius-md);
    --bs-pagination-hover-color: var(--ax-primary);
    --bs-pagination-hover-bg: var(--ax-surface-hover);
    --bs-pagination-hover-border-color: var(--ax-border);
    --bs-pagination-active-bg: var(--ax-primary);
    --bs-pagination-active-border-color: var(--ax-primary);
    --bs-pagination-active-color: var(--ax-primary-on);
    --bs-pagination-disabled-color: var(--ax-text-disabled);
    --bs-pagination-disabled-bg: var(--ax-surface-disabled);
    --bs-pagination-disabled-border-color: var(--ax-border);
    margin: 0;
}


/* ── Application shell ──────────────────────────────────────────────────────
   The frame every screen sits in. These classes read ONLY --ax-shell-* tokens,
   which is what lets Direction C recolour the whole navigation rail without a
   single markup change. Used both by the real layout and by the preview
   specimen, so what the preview shows is what the app renders.                 */

.ax-shell__sidebar {
    background: var(--ax-shell-sidebar-bg);
    color: var(--ax-shell-sidebar-fg);
    border-right: var(--ax-border-width) solid var(--ax-shell-sidebar-border);
}
.ax-shell__brand {
    display: flex;
    align-items: center;
    gap: var(--ax-space-4);
    height: var(--ax-shell-topbar-h);
    padding: 0 var(--ax-space-7);
    font-weight: var(--ax-weight-semibold);
    font-size: var(--ax-text-md);
    color: var(--ax-shell-sidebar-fg);
    border-bottom: var(--ax-border-width) solid var(--ax-shell-sidebar-border);
    text-decoration: none;
}
.ax-shell__section {
    font-size: var(--ax-text-2xs);
    font-weight: var(--ax-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--ax-shell-sidebar-muted);
    padding: var(--ax-space-6) var(--ax-space-7) var(--ax-space-3);
}
.ax-shell__link {
    display: flex;
    align-items: center;
    gap: var(--ax-space-5);
    padding: var(--ax-space-4) var(--ax-space-7);
    color: var(--ax-shell-sidebar-fg);
    font-size: var(--ax-text-md);
    text-decoration: none;
    border-left: var(--ax-border-width-accent) solid transparent;
    transition: background-color var(--ax-transition-fast), color var(--ax-transition-fast);
}
.ax-shell__link:hover { background: var(--ax-shell-sidebar-hover); color: var(--ax-shell-sidebar-fg); }
.ax-shell__link.active,
.ax-shell__link[aria-current="page"] {
    background: var(--ax-shell-sidebar-active-bg);
    color: var(--ax-shell-sidebar-active-fg);
    border-left-color: var(--ax-shell-sidebar-active-marker);
    font-weight: var(--ax-weight-medium);
}
.ax-shell__topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ax-space-6);
    height: var(--ax-shell-topbar-h);
    padding: 0 var(--ax-space-7);
    background: var(--ax-shell-topbar-bg);
    color: var(--ax-shell-topbar-fg);
    border-bottom: var(--ax-border-width) solid var(--ax-shell-topbar-border);
}
/* The tenant name. A user must always be able to tell which dealership they are
   looking at — getting that wrong in a multi-tenant CRM means acting on the
   wrong company's money. It is never truncated away on small screens. */
.ax-shell__tenant {
    font-weight: var(--ax-weight-semibold);
    font-size: var(--ax-text-md);
    color: var(--ax-shell-topbar-fg);
    display: flex;
    align-items: center;
    gap: var(--ax-space-4);
}
.ax-shell__tenant .bi { color: var(--ax-shell-sidebar-muted); }

/* ── Page container ─────────────────────────────────────────────────────── */

.ax-page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--ax-space-6);
    margin-bottom: var(--ax-space-7);
    flex-wrap: wrap;
}
.ax-page-title {
    font-size: var(--ax-text-xl);
    font-weight: var(--ax-weight-semibold);
    color: var(--ax-text-heading);
    margin: 0;
}
.ax-crumbs { font-size: var(--ax-text-xs); color: var(--ax-text-muted); margin-bottom: var(--ax-space-2); }
.ax-crumbs a { color: var(--ax-text-muted); text-decoration: none; }
.ax-crumbs a:hover { color: var(--ax-text-link); text-decoration: underline; }
.ax-crumbs [aria-current="page"] { color: var(--ax-text-primary); font-weight: var(--ax-weight-medium); }


/* ── Tabs ───────────────────────────────────────────────────────────────── */

.nav-tabs {
    --bs-nav-tabs-border-color: var(--ax-border);
    --bs-nav-tabs-link-active-bg: transparent;
    --bs-nav-tabs-link-active-color: var(--ax-primary);
    --bs-nav-tabs-link-active-border-color: transparent;
    --bs-nav-link-color: var(--ax-text-muted);
    --bs-nav-link-hover-color: var(--ax-text-primary);
    --bs-nav-link-font-size: var(--ax-text-md);
    --bs-nav-link-padding-y: var(--ax-space-5);
    --bs-nav-link-padding-x: var(--ax-space-6);
    border-bottom: var(--ax-border-width) solid var(--ax-border);
    gap: var(--ax-space-2);
}
/* An underline marker rather than Bootstrap's folder tabs: at this density the
   folder outline adds three borders per tab and crowds the row. */
.nav-tabs .nav-link {
    border: 0;
    border-bottom: var(--ax-border-width-marker) solid transparent;
    border-radius: 0;
    font-weight: var(--ax-weight-medium);
}
.nav-tabs .nav-link:hover { border-bottom-color: var(--ax-border-strong); }
.nav-tabs .nav-link.active { border-bottom-color: var(--ax-primary); }
.nav-tabs .nav-link .ax-badge { margin-left: var(--ax-space-3); }


/* ── Tooltips ───────────────────────────────────────────────────────────── */

.tooltip {
    --bs-tooltip-bg: var(--ax-neutral-900);
    --bs-tooltip-color: var(--ax-neutral-0);
    --bs-tooltip-border-radius: var(--ax-radius-md);
    --bs-tooltip-font-size: var(--ax-text-xs);
    --bs-tooltip-opacity: 1;
    --bs-tooltip-padding-y: var(--ax-space-3);
    --bs-tooltip-padding-x: var(--ax-space-5);
}
/* A tooltip is never the only place information lives. Anything a user must
   know to complete a task goes in .form-text, not behind a hover — tooltips are
   unreachable on touch and easy to miss with a keyboard. */


/* ── Empty states ───────────────────────────────────────────────────────────
   Batch 6 proved all 45 screens render with zero rows. A blank panel reads as a
   broken screen, so the empty state is designed rather than left to happen:
   say what would be here, why it is not, and offer the action that fills it.   */

.ax-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--ax-space-12) var(--ax-space-7);
    gap: var(--ax-space-5);
}
.ax-empty__icon {
    width: var(--ax-empty-icon-size); height: var(--ax-empty-icon-size);
    display: grid; place-items: center;
    border-radius: var(--ax-radius-pill);
    background: var(--ax-surface-sunken);
    color: var(--ax-text-muted);
    font-size: var(--ax-text-xl);
}
.ax-empty__title { font-size: var(--ax-text-lg); font-weight: var(--ax-weight-semibold); color: var(--ax-text-heading); margin: 0; }
.ax-empty__body  { font-size: var(--ax-text-md); color: var(--ax-text-muted); margin: 0; max-width: 46ch; }

/* The filtered-empty case is a DIFFERENT state and must not reuse the above.
   "No sales yet" when the user has a date filter applied is a lie that sends
   them looking for a bug — say the filter is hiding things and offer to clear
   it. */
.ax-empty--filtered .ax-empty__icon { background: var(--ax-warning-subtle); color: var(--ax-warning); }


/* ── Loading / skeleton ─────────────────────────────────────────────────────
   Skeleton rows keep the table's height stable while data arrives, so the page
   does not jump under the pointer the moment it loads.                         */

.ax-skeleton {
    background: linear-gradient(90deg,
        var(--ax-surface-sunken) 25%,
        var(--ax-surface-hover) 37%,
        var(--ax-surface-sunken) 63%);
    background-size: 400% 100%;
    border-radius: var(--ax-radius-sm);
    animation: ax-shimmer 1.4s ease infinite;
    display: block;
    height: 0.75rem;
}
.ax-skeleton--text  { height: 0.75rem; width: 100%; }
.ax-skeleton--title { height: 1.1rem;  width: 45%; }
.ax-skeleton--pill  { height: 1.1rem;  width: 72px; border-radius: var(--ax-radius-pill); }
@keyframes ax-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
/* Reduced motion: keep the placeholder, drop the movement. */
@media (prefers-reduced-motion: reduce) {
    .ax-skeleton { animation: none; background: var(--ax-surface-sunken); }
}


/* ── Toasts ─────────────────────────────────────────────────────────────── */

.ax-toast-region {
    position: fixed;
    bottom: var(--ax-space-8);
    right: var(--ax-space-8);
    z-index: var(--ax-z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--ax-space-4);
    max-width: min(380px, calc(100vw - var(--ax-space-10)));
}
.toast {
    --bs-toast-bg: var(--ax-surface-raised);
    --bs-toast-border-color: var(--ax-border);
    --bs-toast-border-radius: var(--ax-radius-md);
    --bs-toast-color: var(--ax-text-primary);
    --bs-toast-font-size: var(--ax-text-md);
    box-shadow: var(--ax-shadow-lg);
    border-left: var(--ax-border-width-accent) solid var(--ax-primary);
}
.toast--success { border-left-color: var(--ax-success); }
.toast--warning { border-left-color: var(--ax-warning); }
.toast--danger  { border-left-color: var(--ax-danger); }
.toast .toast-body { padding: var(--ax-space-5) var(--ax-space-6); display: flex; gap: var(--ax-space-4); align-items: flex-start; }


/* ── Select2 ────────────────────────────────────────────────────────────────
   Select2 4.1 is in use across the app and is NOT being replaced — swapping the
   picker on 96 screens is a functional change wearing a design costume. It is
   themed instead, to match .form-select exactly, so a themed native select and
   a Select2 sitting side by side in one form look like one control.

   Select2 ships fixed pixel sizes and its own greys; these rules replace those
   with tokens. The selectors are Select2's own and cannot be renamed.          */

.select2-container--default .select2-selection--single,
.select2-container--default .select2-selection--multiple {
    min-height: var(--ax-control-h);
    height: auto;
    background-color: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border-strong);
    border-radius: var(--ax-radius-md);
    transition: border-color var(--ax-transition-fast), box-shadow var(--ax-transition-fast);
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: calc(var(--ax-control-h) - 2px);
    padding-left: var(--ax-control-px);
    padding-right: var(--ax-space-10);
    color: var(--ax-text-primary);
    font-size: var(--ax-text-md);
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: calc(var(--ax-control-h) - 2px);
    right: var(--ax-space-3);
}
.select2-container--default .select2-selection--single .select2-selection__placeholder { color: var(--ax-text-disabled); }
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--open .select2-selection--single {
    border-color: var(--ax-primary);
    box-shadow: 0 0 0 3px rgba(var(--ax-primary-rgb), 0.18);
}
.select2-container--default .select2-selection--single .select2-selection__clear { color: var(--ax-text-muted); }

.select2-dropdown {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-md);
    box-shadow: var(--ax-shadow-lg);
    z-index: var(--ax-z-dropdown);
}
.select2-container--default .select2-results__option {
    font-size: var(--ax-text-md);
    padding: var(--ax-space-4) var(--ax-control-px);
    color: var(--ax-text-primary);
}
.select2-container--default .select2-results__option--highlighted[aria-selected],
.select2-container--default .select2-results__option--highlighted {
    background: var(--ax-primary);
    color: var(--ax-primary-on);
}
.select2-container--default .select2-results__option[aria-selected="true"] {
    background: var(--ax-surface-selected);
    color: var(--ax-text-primary);
}
.select2-container--default .select2-search--dropdown .select2-search__field {
    border: var(--ax-border-width) solid var(--ax-border-strong);
    border-radius: var(--ax-radius-sm);
    padding: var(--ax-space-3) var(--ax-space-5);
    font-size: var(--ax-text-md);
    color: var(--ax-text-primary);
    background: var(--ax-surface-raised);
}
/* Multi-select chips */
.select2-container--default .select2-selection--multiple .select2-selection__choice {
    background: var(--ax-primary-subtle);
    border: var(--ax-border-width) solid transparent;
    border-radius: var(--ax-radius-sm);
    color: var(--ax-primary);
    font-size: var(--ax-text-sm);
    padding: var(--ax-space-1) var(--ax-space-4);
    margin: var(--ax-space-2) var(--ax-space-2) 0 0;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { color: var(--ax-primary); }


/* ── Filter bar ─────────────────────────────────────────────────────────── */

.ax-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--ax-space-5);
    padding: var(--ax-space-6);
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    margin-bottom: var(--ax-space-6);
}
.ax-filters__field { display: flex; flex-direction: column; min-width: var(--ax-filter-field-min); flex: 1 1 var(--ax-filter-field-min); }
.ax-filters__field .form-label { margin-bottom: var(--ax-space-2); }
.ax-filters__actions { display: flex; gap: var(--ax-space-4); flex: 0 0 auto; }

/* Result count sits with the table, not the filters — it describes what is
   below it. */
.ax-result-count { font-size: var(--ax-text-xs); color: var(--ax-text-muted); margin-bottom: var(--ax-space-4); }


/* ── Topbar pieces ──────────────────────────────────────────────────────────
   These replace inline style attributes that hard-coded hex values in the
   layout. Hard-coded colour in a style attribute cannot respond to dark mode or
   to a direction change — it is the one thing the token system cannot reach —
   so every one of them is converted to a class here.                           */

.ax-topbar-title {
    font-size: var(--ax-text-md);
    font-weight: var(--ax-weight-semibold);
    color: var(--ax-shell-topbar-fg);
    margin: 0;
}

/* The tenant chip. The sidebar brand also names the tenant, but the sidebar
   COLLAPSES — and when it does, the only indication of which dealership you are
   working in disappears. In a multi-tenant CRM that is how someone posts a
   receipt against the wrong company's books. This chip lives in the topbar,
   which never collapses. */
.ax-tenant-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--ax-space-3);
    padding: var(--ax-space-2) var(--ax-space-5);
    border-radius: var(--ax-radius-pill);
    background: var(--ax-primary-subtle);
    color: var(--ax-primary);
    border: var(--ax-border-width) solid transparent;
    font-size: var(--ax-text-xs);
    font-weight: var(--ax-weight-semibold);
    white-space: nowrap;
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ax-tenant-chip .bi { flex: none; }

/* Business-type badge (Batch 17) — sits beside the tenant chip.
   It uses the standard .ax-badge, so it inherits the design system's neutral
   tone; this wrapper only handles placement and the master_admin link state.
   Deliberately quieter than .ax-tenant-chip: which dealership you are in
   matters more than how that dealership trades, and two pill-shaped chips of
   equal weight side by side read as one broken control. */
.ax-biz-badge-wrap {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    margin-left: var(--ax-space-2);
}
.ax-biz-badge-wrap--link { cursor: pointer; }
.ax-biz-badge-wrap--link:hover .ax-badge,
.ax-biz-badge-wrap--link:focus-visible .ax-badge {
    border-color: var(--ax-primary);
    color: var(--ax-primary);
}
.ax-biz-badge-wrap--link:focus-visible {
    outline: var(--ax-focus-width, 2px) solid var(--ax-primary);
    outline-offset: 2px;
    border-radius: var(--ax-radius-pill);
}

/* FY switcher — was an inline style block with four hard-coded hexes. */
.ax-fy-btn {
    background: var(--ax-primary-subtle);
    border: var(--ax-border-width) solid var(--ax-primary-subtle);
    color: var(--ax-primary);
    font-size: var(--ax-text-xs);
    border-radius: var(--ax-radius-md);
    font-weight: var(--ax-weight-semibold);
    line-height: var(--ax-leading-snug);
    display: flex;
    align-items: center;
    gap: var(--ax-space-2);
    padding: var(--ax-space-2) var(--ax-space-4);
}
.ax-fy-btn:hover { background: var(--ax-primary); color: var(--ax-primary-on); border-color: var(--ax-primary); }
.ax-fy-menu { min-width: 170px; font-size: var(--ax-text-sm); }

/* Impersonation banner — was inline with a hard-coded amber. It is a warning
   the user must not miss, so it keeps its full-strength treatment, but it now
   comes from the warning token and therefore stays correct in dark mode. */
.ax-impersonation-banner {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: var(--ax-z-banner);
    background: var(--ax-warning);
    color: var(--ax-neutral-0);
    padding: var(--ax-space-3) var(--ax-space-7);
    display: flex;
    align-items: center;
    gap: var(--ax-space-6);
    font-size: var(--ax-text-sm);
    font-weight: var(--ax-weight-semibold);
}
.ax-impersonation-spacer { height: var(--ax-space-11); }


/* ── Sidebar icon accents on a dark rail ────────────────────────────────────
   Each menu item carries its own icon colour via an inline `--ic` custom
   property — 40-odd distinct hues that genuinely help scanning a long menu, and
   worth keeping.

   They were chosen against a WHITE sidebar. On the charcoal rail of Direction C
   (and in dark mode) several of them — the dark blues especially, #1d4ed8 on
   #1a1d23 is about 1.7:1 — become close to invisible. Rather than restyle 40
   inline values or drop the colour system, the icons defer to the sidebar
   foreground wherever the rail is dark. Light mode keeps its colours untouched.

   This is why `--ic` may stay as an inline style: it is data, one value per menu
   item, not a design decision repeated across screens.                         */
:root[data-ax-direction="c"] .sb-link i,
:root[data-bs-theme="dark"] .sb-link i {
    color: var(--ax-shell-sidebar-fg);
}
:root[data-ax-direction="c"] .sb-link.active i,
:root[data-bs-theme="dark"] .sb-link.active i {
    color: var(--ax-shell-sidebar-active-fg);
}


/* ── DataTables ─────────────────────────────────────────────────────────────
   DataTables 1.13 drives the app's long lists and is NOT being replaced — it
   provides server-side paging, sorting and search that the sales list depends
   on, and swapping it would be a functional change, not a restyle.

   It ships its own greys, its own pixel sizes and its own control chrome. These
   rules bring its furniture onto the tokens so a DataTables list is
   indistinguishable from an .ax-table. The selectors are DataTables' own.       */

.dataTables_wrapper { font-size: var(--ax-text-sm); color: var(--ax-text-primary); }
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate { font-size: var(--ax-text-sm); color: var(--ax-text-muted); }

.dataTables_wrapper .dataTables_length select,
.dataTables_wrapper .dataTables_filter input {
    min-height: var(--ax-control-h-sm);
    padding: var(--ax-space-2) var(--ax-space-5);
    font-size: var(--ax-text-sm);
    color: var(--ax-text-primary);
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border-strong);
    border-radius: var(--ax-radius-md);
}
.dataTables_wrapper .dataTables_filter input:focus,
.dataTables_wrapper .dataTables_length select:focus {
    border-color: var(--ax-primary);
    box-shadow: 0 0 0 3px rgba(var(--ax-primary-rgb), 0.18);
    outline: none;
}

/* Header — matches .ax-table's thead exactly, so the two look like one system. */
table.dataTable > thead > tr > th {
    background: var(--ax-surface-sunken);
    color: var(--ax-text-muted);
    font-size: var(--ax-text-2xs);
    font-weight: var(--ax-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: var(--ax-space-3) var(--ax-table-cell-px);
    border-bottom: var(--ax-border-width) solid var(--ax-border);
    white-space: nowrap;
}
table.dataTable > tbody > tr > td {
    padding: var(--ax-table-cell-py) var(--ax-table-cell-px);
    border-bottom: var(--ax-border-width) solid var(--ax-border-subtle);
    vertical-align: middle;
    font-size: var(--ax-table-font);
}
table.dataTable.table-hover > tbody > tr:hover > * { background: var(--ax-surface-hover); }

/* The sort arrows are DataTables' own pseudo-elements; tint them to the theme
   so the active sort column is obvious rather than a faint grey chevron. */
table.dataTable > thead .sorting_asc,
table.dataTable > thead .sorting_desc { color: var(--ax-primary); }

/* Paging buttons — Bootstrap 5 integration renders .page-link, already themed
   by the pagination block above; this only fixes DataTables' own spacing. */
.dataTables_wrapper .dataTables_paginate .paginate_button { padding: 0; }
.dataTables_wrapper .dataTables_processing {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-md);
    box-shadow: var(--ax-shadow-lg);
    color: var(--ax-text-primary);
    font-size: var(--ax-text-sm);
}

/* Money columns in a DataTable. DataTables writes cells as raw strings, so the
   alignment has to come from the column class rather than from a span. */
table.dataTable td.ax-num, table.dataTable th.ax-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
    white-space: nowrap;
}

/* ── Wide-table responsiveness ──────────────────────────────────────────────
   HORIZONTAL SCROLL, not column collapse — a deliberate choice.

   The sales list is 16 columns. Collapsing into stacked cards or a "+" expander
   is friendlier on a phone for a SHORT row, but this row is a financial record:
   a dealer checking a sale on site is comparing values ACROSS rows (which sale
   is unpaid, which balance is largest) and collapsing destroys exactly that
   comparison — every row becomes a separate card you must open one at a time.

   Scrolling keeps the grid intact and keeps the comparison possible. The cost
   is a horizontal swipe, which is a familiar gesture; the cost of collapsing is
   that the screen stops answering the question it exists to answer.

   What makes scroll workable is pinning the identifying column, so you never
   lose track of WHICH sale you are reading while scrolled right.               */
.ax-table-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

@media (max-width: 900px) {
    .ax-sticky-col-1 > thead > tr > th:nth-child(2),
    .ax-sticky-col-1 > tbody > tr > td:nth-child(2) {
        position: sticky;
        left: 0;
        z-index: 2;
        background: var(--ax-surface-raised);
        box-shadow: 1px 0 0 var(--ax-border);
    }
    .ax-sticky-col-1 > thead > tr > th:nth-child(2) { background: var(--ax-surface-sunken); }
}


/* ── Sticky form action bar ─────────────────────────────────────────────────
   The vehicle wizard is five accordion sections of a long form. With the Save
   button pinned to the very bottom, a user who edits a field in section two has
   to scroll past three unrelated sections to save — and the commonest outcome
   of that is navigating away with unsaved changes.

   The bar sticks to the bottom of the viewport while the form is open. It is
   NOT `position: fixed`: sticky keeps it inside the form's flow, so it stops at
   the form's end rather than floating over the page footer, and it does not
   need a body padding hack to avoid covering content.                          */

.ax-form-actions {
    position: sticky;
    bottom: 0;
    z-index: var(--ax-z-sticky);
    display: flex;
    align-items: center;
    gap: var(--ax-space-4);
    padding: var(--ax-space-5) var(--ax-space-6);
    margin-top: var(--ax-space-9);
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    box-shadow: var(--ax-shadow-lg);
    flex-wrap: wrap;
}

/* On a short viewport a sticky bar eats scarce vertical space for a control the
   user is not reaching for yet, so below 560px tall it returns to the flow. */
@media (max-height: 560px) {
    .ax-form-actions { position: static; box-shadow: var(--ax-shadow-sm); }
}


/* ═══ ACCESSIBILITY & RESPONSIVE — Batch 7, Part 8 ═══════════════════════════ */

/* ── Focus ──────────────────────────────────────────────────────────────────
   Bootstrap removes the native outline on several controls and replaces it with
   a box-shadow ring, which disappears entirely against a coloured surface. Every
   interactive element gets a real outline, on :focus-visible only, so a mouse
   click does not leave a ring behind but a Tab press always shows one.

   The accordion in the vehicle wizard explicitly set `box-shadow: none` on
   focus, which left keyboard users with NO focus indicator at all on a five-step
   form. That is fixed at its source in the wizard styles; this is the safety
   net for everything else. */
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
    outline: 2px solid var(--ax-focus-ring);
    outline-offset: var(--ax-focus-offset);
}
/* Inside a dark rail the standard ring can sit too close to the ground. */
.ax-shell__sidebar a:focus-visible,
.ax-shell__sidebar button:focus-visible,
.sidebar a:focus-visible,
.sidebar button:focus-visible {
    outline-color: var(--ax-shell-sidebar-active-marker);
}

/* Skip link — the first thing a keyboard user meets. Without it, reaching the
   page content means tabbing through ~40 sidebar links on every single screen. */
.ax-skip-link {
    position: absolute;
    left: var(--ax-space-4);
    top: calc(-1 * var(--ax-space-12));
    z-index: calc(var(--ax-z-banner) + 1);
    padding: var(--ax-space-4) var(--ax-space-7);
    background: var(--ax-primary);
    color: var(--ax-primary-on);
    border-radius: var(--ax-radius-md);
    font-size: var(--ax-text-md);
    font-weight: var(--ax-weight-semibold);
    text-decoration: none;
    transition: top var(--ax-transition-fast);
}
.ax-skip-link:focus {
    top: var(--ax-space-4);
    color: var(--ax-primary-on);
}

/* Screen-reader-only text, for labels that must exist without being drawn. */
.ax-sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Minimum touch target. A 28px icon button is comfortable with a mouse and
   genuinely hard with a thumb; WCAG 2.2 asks for 24px minimum and 44px is the
   practical target on a phone. Applied only on coarse pointers so the desktop
   density is not sacrificed for it. */
@media (pointer: coarse) {
    .btn-icon,
    .ax-table__actions .btn,
    .dash-export-btn {
        min-width: 44px;
        min-height: 44px;
    }
    /* Row actions never fade on touch — there is no hover to reveal them. */
    .ax-table__actions .btn { opacity: 1; }
}

/* ── Responsive ─────────────────────────────────────────────────────────────
   Dealers use phones. The shell and the tables have to degrade sensibly rather
   than merely shrink.                                                          */

/* Tablet and below: the sidebar becomes an overlay instead of holding 255px of
   a 768px viewport. The existing .sb-collapsed toggle already drives it; this
   makes the collapsed state the DEFAULT below 992px and floats the open state
   over the content rather than pushing it off-screen. */
@media (max-width: 991.98px) {
    .sidebar { box-shadow: var(--ax-shadow-lg); z-index: var(--ax-z-sidebar); }
    .topbar { left: 0; }
    .main-content { margin-left: 0; padding: var(--ax-space-6); }
    body:not(.sb-collapsed) .sidebar { width: var(--ax-shell-sidebar-w); }
}

/* Phone: the topbar has to keep the tenant name and lose everything optional.
   Knowing WHICH dealership you are in is not optional — it is the one thing
   that must survive every breakpoint. */
@media (max-width: 575.98px) {
    .main-content { padding: var(--ax-space-5); }
    .ax-topbar-title { display: none; }
    /* Batch 17: the badge STAYS on a phone — it is the explanation for missing
       tabs, and a phone is exactly where a user cannot go hunting for one. Room
       is made by trimming the tenant chip a little further and dropping the
       badge's decorative dot, not by hiding either of them. The chip still wins
       the space: knowing which dealership you are in is the one thing that must
       survive every breakpoint. */
    .ax-tenant-chip { max-width: 120px; }
    .ax-biz-badge-wrap { margin-left: var(--ax-space-1); }
    .ax-biz-badge-wrap .ax-badge { padding-left: var(--ax-space-3); padding-right: var(--ax-space-3); }
    .ax-biz-badge-wrap .ax-badge__dot { display: none; }
    .ax-stat-row { grid-template-columns: 1fr; }
    .ax-page-head { flex-direction: column; align-items: stretch; }
    .ax-filters__field { flex-basis: 100%; }
    .ax-filters__actions { width: 100%; }
    .ax-filters__actions .btn { flex: 1; }
    /* A sticky action bar on a phone should span the width — a half-width Save
       button at the bottom of a small screen is a small target. */
    .ax-form-actions { border-radius: var(--ax-radius-md); }
    .ax-form-actions .btn { flex: 1 1 auto; }
}

/* Print. Dealers print sale lists and delivery notes, and a printed status badge
   whose meaning is its colour is unreadable in greyscale — which is exactly why
   every badge carries a text label. Drop the chrome, keep the data. */
@media print {
    .sidebar, .topbar, .ax-form-actions, .ax-filters,
    .ax-table__actions, .dash-export-btn { display: none !important; }
    .main-content { margin: 0 !important; padding: 0 !important; }
    .ax-table-wrap, .card { border: 1px solid #999 !important; box-shadow: none !important; }
    .ax-badge { border: 1px solid #999 !important; }
}


/* ── Inline affordances ─────────────────────────────────────────────────────
   Batch 8. The sale payment screen repeated the same inline style four times to
   mark "this label is clickable, click to change it". Four copies of a colour is
   four chances to miss one, and an inline hex cannot follow dark mode. */

.ax-inline-action {
    cursor: pointer;
    text-decoration: underline dotted;
    color: var(--ax-text-link);
}
.ax-inline-action:hover { color: var(--ax-text-link-hover); }

/* A short inline note inside a form or a table — smaller and quieter than an
   .alert, which is a page-level element and too heavy inside a row. */
.ax-note {
    display: inline-block;
    padding: var(--ax-space-2) var(--ax-space-5);
    border-radius: var(--ax-radius-md);
    font-size: var(--ax-text-xs);
    border: var(--ax-border-width) solid transparent;
}
.ax-note--warning { background: var(--ax-warning-subtle); color: var(--ax-warning); }
.ax-note--info    { background: var(--ax-info-subtle);    color: var(--ax-info); }

/* A derived row that needs to stand out from the other derived rows — the
   headline computed figure among the intermediate ones. */
.pricing-table .auto-row--emphasis td {
    background: var(--ax-primary-subtle);
    color: var(--ax-text-heading);
}


/* ── Inline field panel ─────────────────────────────────────────────────────
   Batch 8. A tinted box wrapping ONE field that needs to stand out from the
   fields around it — a conditional requirement, or an optional extra. Replaces
   ad-hoc inline background/border/colour trios in the sale detail screen.

   Deliberately not an .alert: an alert is a page-level announcement, and using
   one around a single input makes routine data entry look like a problem. */

.ax-field-panel {
    padding: var(--ax-space-5);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-md);
    background: var(--ax-surface-sunken);
}
.ax-field-panel > .form-label { color: var(--ax-text-primary); }

.ax-field-panel--required { background: var(--ax-warning-subtle); border-color: var(--ax-warning-subtle); }
.ax-field-panel--required > .form-label { color: var(--ax-warning); }
.ax-field-panel--optional { background: var(--ax-success-subtle); border-color: var(--ax-success-subtle); }
.ax-field-panel--optional > .form-label { color: var(--ax-success); }

/* An extra line added to a pricing table — visibly not one of the standard
   particulars. */
#saleWizardForm tr.extra-amount-row > td { background-color: var(--ax-success-subtle); }


/* ── Centred confirm / result overlay ───────────────────────────────────────
   Batch 8. The model-price confirm and result dialogs were built entirely from
   inline styles — two near-identical copies, six hard-coded hexes between them,
   none of which could follow dark mode. Same markup, now token-driven.

   Not a Bootstrap .modal because these are driven by their own opacity/transform
   JS that predates the design system, and rewriting that JS would be a
   behavioural change inside a presentation batch. The classes take over the
   appearance; the existing script keeps driving visibility. */

.ax-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--ax-z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ax-transition-base);
}
.ax-overlay__card {
    background: var(--ax-surface-raised);
    color: var(--ax-text-primary);
    border-radius: var(--ax-radius-lg);
    padding: var(--ax-space-10) var(--ax-space-11);
    text-align: center;
    min-width: 300px;
    max-width: 430px;
    box-shadow: var(--ax-shadow-modal);
    transform: scale(0.88);
    transition: transform var(--ax-transition-base);
}
.ax-overlay__icon    { font-size: var(--ax-text-3xl); line-height: 1; margin-bottom: var(--ax-space-5); }
.ax-overlay__msg     { font-weight: var(--ax-weight-semibold); font-size: var(--ax-text-lg); color: var(--ax-text-heading); line-height: var(--ax-leading-snug); }
.ax-overlay__actions { display: flex; gap: var(--ax-space-5); justify-content: center; margin-top: var(--ax-space-8); }
.ax-overlay__actions .btn { min-width: 112px; }
/* The destructive word inside a confirm sentence — the one place dark red
   belongs on this screen. */
.ax-overlay__danger  { color: var(--ax-danger); }
.ax-overlay__progress{ margin-top: var(--ax-space-7); height: 3px; border-radius: var(--ax-radius-pill); background: var(--ax-surface-sunken); overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
    .ax-overlay, .ax-overlay__card { transition: none; }
}


/* ── Emphasised card ────────────────────────────────────────────────────────
   Batch 9. A card that needs to stand out from the cards around it — a
   conditional section, or one carrying a caution. Replaces an inline
   `border-color` on the insurance form, which could not follow dark mode. */

.ax-card--warning { border-color: var(--ax-warning) !important; }
.ax-card--danger  { border-color: var(--ax-danger)  !important; }
.ax-card--primary { border-color: var(--ax-primary) !important; }


/* ── Totals row ─────────────────────────────────────────────────────────────
   Batch 9, for Accounts. A reconciliation table's last row is a SUM, not a
   record, and it must not be mistaken for one — you cannot audit a column whose
   total looks like another line item.

   Three signals, none of them colour alone: a sunken ground, a heavier top
   border closing the column, and semibold weight. Tabular figures come from
   .ax-num on the cell, exactly as they do for the rows above, so the total sits
   on the same decimal.                                                         */

.ax-table tfoot > tr > td,
.table tfoot > tr > td,
tr.ax-total > td {
    background: var(--ax-surface-sunken);
    border-top: var(--ax-border-width-marker) solid var(--ax-border-strong);
    font-weight: var(--ax-weight-semibold);
    color: var(--ax-text-heading);
}

/* An opening/closing balance row sits at the TOP or bottom of a book and is
   likewise not a transaction. */
tr.ax-balance > td {
    background: var(--ax-surface-sunken);
    font-weight: var(--ax-weight-semibold);
}

/* ── Manual review flag ─────────────────────────────────────────────────────
   A commission held back for a human to look at. It must be visible in a list
   of 100, not buried — so it marks the whole row, and the badge carries a
   label as every status badge does. */
tr.ax-flagged > td { background: var(--ax-warning-subtle); }
tr.ax-flagged:hover > td { background: var(--ax-warning-subtle); }

/* A badge used as a headline figure rather than a row status. */
.ax-badge--lg { font-size: var(--ax-text-md); padding: var(--ax-space-3) var(--ax-space-6); }

/* A card that is itself a link. Batch 9: the Accounts reports hub used a
   `.hover-shadow` class that is DEFINED NOWHERE — the cards looked clickable and
   gave no feedback when hovered, and no focus ring at all when tabbed to. */
.ax-card-link {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    box-shadow: var(--ax-shadow-sm);
    transition: box-shadow var(--ax-transition-fast), border-color var(--ax-transition-fast);
}
a:hover > .ax-card-link { box-shadow: var(--ax-shadow-md); border-color: var(--ax-border-strong); }
a:focus-visible > .ax-card-link { outline: 2px solid var(--ax-focus-ring); outline-offset: var(--ax-focus-offset); }

.ax-select-wide { min-width: 180px; }

/* A small square thumbnail in a table cell — logos and icons on master lists.
   Batch 10: replaces an inline style that hard-coded a size and a background. */
.ax-thumb {
    height: 40px; width: 40px;
    object-fit: contain;
    background: var(--ax-surface-sunken);
    padding: var(--ax-space-1);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-sm);
}

/* A short form does not want the full page width — a 90-character-wide input is
   harder to scan, not easier. Batch 10: replaces an inline max-width. */
.ax-form-narrow { max-width: 680px; }
.ax-thumb--lg { height: auto; width: auto; max-height: 80px; max-width: 160px; }


/* ── Small layout utilities ─────────────────────────────────────────────────
   Batch 11. Each replaces an inline style that appeared in the Admin module.
   An inline style is the one place the token system cannot reach — it cannot
   follow dark mode and cannot follow a direction change.                      */

/* A definition-list style key column in a detail table. */
.ax-key-col { width: 40%; }

/* A cell that must truncate rather than widen the table. Two widths because a
   title needs more room than a name, and inventing a third is how ad-hoc
   pixel values start. */
.ax-truncate    { max-width: 220px; }
.ax-truncate-sm { max-width: 130px; }

/* Preserve the author's line breaks — a push-notification body is written with
   deliberate wrapping and collapsing it misrepresents what was sent. */
.ax-prewrap { white-space: pre-wrap; }

/* Image previews. --cover crops to fill; the default fits inside. */
.ax-preview      { height: 120px; object-fit: cover; }
.ax-thumb--cover { object-fit: cover; }

/* A slim progress bar used as a read-rate indicator. */
.ax-progress-slim { height: 6px; }

/* A drag handle. Cursor is the whole affordance, so it must be a real one. */
.ax-drag-handle          { cursor: grab; }
.ax-drag-handle:active   { cursor: grabbing; }
.ax-drag-handle.is-locked{ cursor: not-allowed; }
.ax-preview-lg { max-height: 180px; }

.ax-truncate-lg { max-width: 280px; }
.ax-scroll-380  { max-height: 380px; overflow-y: auto; }

/* A JSON / diff payload shown in a modal. Monospace, scrollable, and it must
   wrap rather than force the modal wide. */
.ax-code-block {
    background: var(--ax-surface-sunken);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-md);
    padding: var(--ax-space-5);
    font-family: var(--ax-font-mono);
    font-size: var(--ax-text-xs);
    color: var(--ax-text-primary);
    max-height: 350px;
    overflow: auto;
    word-break: break-word;
    white-space: pre-wrap;
}

.ax-col-wide   { min-width: 220px; }
.ax-spacer-16  { width: 16px; }
.ax-switch-cell{ min-height: 0; }
.ax-nowrap     { white-space: nowrap; }
.ax-col-med { min-width: 110px; }

.ax-select-med  { min-width: 140px; }
.ax-preview-xl  { max-height: 340px; object-fit: contain; }
.ax-icon-lg     { font-size: var(--ax-text-3xl); }
.al-field-col { width: 25%; }


/* ── Forms module utilities ─────────────────────────────────────────────────
   Batch 11. Each replaces an inline style that appeared across the Forms
   screens. The recurring one was `style="font-size:.72rem"` — a hand-rolled
   caption size that already exists as --ax-text-xs.                           */

.ax-page-narrow   { max-width: 900px; }
.ax-page-medium   { max-width: 1100px; }
.ax-modal-narrow  { max-width: 380px; }
.ax-modal-medium  { max-width: 480px; }
.ax-input-240     { width: 240px; }
.ax-input-180     { width: 180px; }
.ax-input-140     { width: 140px; }
.ax-input-320     { max-width: 320px; }
.ax-toast-layer   { z-index: var(--ax-z-toast); }
.ax-caption       { font-size: var(--ax-text-xs); }
.ax-tracked       { letter-spacing: .04em; }

/* The OTP entry box. Wide tracking and a monospace face so six digits are
   read as six separate characters rather than a number. */
.ax-otp-input {
    font-size: var(--ax-text-2xl);
    letter-spacing: .5rem;
    font-family: var(--ax-font-mono);
    text-align: center;
}
.ax-dot-icon { font-size: .5rem; }
/* The OTP screen's status banner, in its informational rather than success
   state — verifying is not yet succeeding. */
.pf-success--info { background: var(--ax-primary-subtle); color: var(--ax-primary); }
.ax-input-200 { width: 200px; }


/* ═══ REPORTS CHROME ═════════════════════════════════════════════════════════
   Batch 12. The Reports module is 8 bespoke views, and five of these blocks were
   COPY-PASTED into four or five of them each — same class names, same values,
   maintained in parallel. That duplication is the shared component; extracting
   it here deletes ~200 lines of near-identical CSS from the views and means the
   next report screen inherits the chrome instead of re-declaring it.

   `.flt-chip .chip-x` was #b3b3b3 — 2.10:1 — on the ONLY control that removes a
   filter. If you cannot see the × you cannot clear the filter, and the screen
   looks like it is returning no data.                                          */

/* ── Report picker (left rail) ───────────────────────────────────────────── */
.rpt-menu { list-style: none; padding: 0; margin: 0; }
.rpt-menu li { margin-bottom: var(--ax-space-1); }
.rpt-menu-btn {
    display: flex; align-items: center; gap: var(--ax-space-4);
    width: 100%; text-align: left;
    padding: var(--ax-space-4) var(--ax-space-5);
    border: none; border-left: var(--ax-border-width-accent) solid transparent;
    border-radius: var(--ax-radius-md);
    background: transparent;
    color: var(--ax-text-primary);
    font-size: var(--ax-text-sm);
    cursor: pointer;
    transition: background var(--ax-transition-fast), color var(--ax-transition-fast);
}
.rpt-menu-btn:hover  { background: var(--ax-surface-hover); color: var(--ax-text-link); }
.rpt-menu-btn:focus-visible { outline: 2px solid var(--ax-focus-ring); outline-offset: var(--ax-focus-offset); }
.rpt-menu-btn.active {
    background: var(--ax-primary-subtle);
    color: var(--ax-primary);
    border-left-color: var(--ax-primary);
    font-weight: var(--ax-weight-semibold);
}
.rpt-menu-btn .bi { font-size: var(--ax-text-md); width: 18px; text-align: center; flex-shrink: 0; }

/* ── Filter panel + applied-filter chips ─────────────────────────────────── */
.filter-panel { display: none; }
.filter-panel.show { display: block; }

.flt-chip {
    display: inline-flex; align-items: center; gap: var(--ax-space-3);
    background: var(--ax-primary-subtle);
    color: var(--ax-primary);
    border: var(--ax-border-width) solid transparent;
    border-radius: var(--ax-radius-pill);
    padding: var(--ax-space-1) var(--ax-space-5);
    font-size: var(--ax-text-xs);
}
/* The remove-filter affordance. It must be legible: this is the only way to
   clear a filter, and an invisible × looks like a report with no data. */
.flt-chip .chip-x {
    cursor: pointer;
    font-size: var(--ax-text-sm);
    color: var(--ax-primary);
    line-height: 1;
    margin-left: var(--ax-space-1);
    transition: color var(--ax-transition-fast);
}
.flt-chip .chip-x:hover { color: var(--ax-danger); }

/* ── Loading ring ───────────────────────────────────────────────────────── */
.loader-ring {
    width: 44px; height: 44px;
    border: 4px solid var(--ax-surface-sunken);
    border-top-color: var(--ax-primary);
    border-radius: var(--ax-radius-pill);
    animation: spin .75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .loader-ring { animation-duration: 3s; } }

/* ── Report summary cards ───────────────────────────────────────────────── */
.rr-card, .sc-card, .summary-mini {
    background: var(--ax-surface-raised);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-lg);
    padding: var(--ax-card-padding);
    box-shadow: var(--ax-shadow-sm);
}

/* ── Report status pills ────────────────────────────────────────────────────
   Reports carry their own status vocabulary. Mapped onto the same tinted-pill
   treatment as ax_badge() so a report and a list screen agree. Pending is amber
   and NOT red: pending is the normal state of recent business, and a report half
   red teaches people to ignore red. */
.badge-pending, .badge-partial, .badge-upcoming, .badge-stage,
.badge-received, .badge-paid, .badge-expired, .badge-done,
.badge-complete, .badge-active {
    display: inline-flex; align-items: center; gap: var(--ax-space-2);
    padding: var(--ax-space-1) var(--ax-space-4);
    border-radius: var(--ax-radius-pill);
    font-size: var(--ax-text-2xs);
    font-weight: var(--ax-weight-semibold);
    line-height: 1.5; white-space: nowrap;
    border: var(--ax-border-width) solid transparent;
}
.badge-paid, .badge-received, .badge-done, .badge-complete, .badge-active
                 { background: var(--ax-success-subtle); color: var(--ax-success); }
.badge-pending, .badge-partial, .badge-upcoming, .badge-stage
                 { background: var(--ax-warning-subtle); color: var(--ax-warning); }
.badge-expired   { background: var(--ax-danger-subtle);  color: var(--ax-danger); }

/* ── Print ──────────────────────────────────────────────────────────────────
   Reports get printed. Drop the chrome, keep the data, and keep pill borders so
   a greyscale print still reads as a status. */
@media print {
    .rpt-menu, .filter-panel, .flt-chip, .loader-ring { display: none !important; }
    .rr-card, .sc-card, .summary-mini { border: 1px solid #999 !important; box-shadow: none !important; }
    [class^="badge-"], [class*=" badge-"] { border: 1px solid #999 !important; }
}

/* A muted caption. Batch 12: the Reports module inlined this ~17 times as
   `font-size:.Xrem;color:#b3b3b3` — 2.10:1, well under AA, on loading text,
   filter hints and chip labels. One class, one legible colour. */
.ax-caption-muted { font-size: var(--ax-text-xs); color: var(--ax-text-muted); }
.ax-caption-muted--italic { font-size: var(--ax-text-xs); color: var(--ax-text-muted); font-style: italic; }

/* The applied-filters bar that sits above a report's results. */
.ax-filterbar {
    background: var(--ax-surface-sunken);
    border: var(--ax-border-width) solid var(--ax-border);
    border-radius: var(--ax-radius-md);
}
/* Per-report accent used as a solid ground with white text on it (5.70:1). */
.ax-accent-purple { background: #7c3aed; }

/* Summary-card figure and caption inside a report. Batch 12: these lived in the
   views and were removed with the rest of the duplicated chrome; the label was
   #b3b3b3 (2.10:1) — the caption under every headline figure on a report. */
.sc-val { font-size: var(--ax-text-2xl); font-weight: var(--ax-weight-bold); line-height: var(--ax-leading-tight);
          font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
.sc-lbl { font-size: var(--ax-text-xs); color: var(--ax-text-muted); font-weight: var(--ax-weight-medium); margin-top: var(--ax-space-1); }

/* ── Filter field width scale ───────────────────────────────────────────────
   Batch 12. The Reports module carried ~60 inline `width:NNNpx` declarations
   across TWELVE distinct values (110, 115, 120, 130, 140, 145, 150, 155, 160,
   165, 170, 180) for what are three sizes of filter control. Twelve arbitrary
   widths is not a layout, it is the absence of one — and they are what made the
   filter rows wrap unpredictably at narrower viewports.

   Four steps, and they shrink on small screens instead of forcing a horizontal
   scrollbar. */
.ax-w-xs { width: 90px;  max-width: 100%; }
.ax-w-sm { width: 120px; max-width: 100%; }
.ax-w-md { width: 145px; max-width: 100%; }
.ax-w-lg { width: 170px; max-width: 100%; }
.ax-w-xl { width: 200px; max-width: 100%; }
@media (max-width: 575.98px) {
    .ax-w-xs, .ax-w-sm, .ax-w-md, .ax-w-lg, .ax-w-xl { width: 100%; }
}
.ax-relative { position: relative; }

/* ── Users / Inquiries utilities · Batch 12 ─────────────────────────────── */
.ax-avatar     { width: 32px; height: 32px; font-size: var(--ax-text-xs); }
.ax-avatar--lg { width: 80px; height: 80px; }
.ax-mono       { font-family: var(--ax-font-mono); }
.ax-cell-clip  { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ax-col-200    { min-width: 200px; }
.ax-pointer { cursor: pointer; }
/* The connector down an inquiry's action timeline. */
.ax-timeline-line { border-width: 2px !important; }

/* ── Finance · detail wizard (08-02) · Batch 12 ──────────────────────────────
   Extracted verbatim from finance/detail.php's embedded <style>. Two changes
   from the original: the failing #b3b3b3 step greys (2.10:1) become
   --ax-text-muted, and the eight per-section accents are darkened to their
   AA-safe values so the white-on-accent step badges and the accent-coloured
   section labels/icons clear AA — the light tint backgrounds are unchanged.
   The per-section accent now lives ONLY on .acc-fN and .fin-step's --sc data;
   the inline background:/color: hex the sbadges and icons used to carry is
   gone. */

/* Shared document-card look — identical to the Sales / Vehicle doc cards. */
.doc-card { border: 1px solid #dee2e6; border-radius: 6px; padding: 8px 12px; background: #fafafa; }
.doc-card > .flex-grow-1 { min-width: 0; }
.doc-card .text-truncate {
    white-space: normal; overflow: visible; text-overflow: clip;
    overflow-wrap: anywhere; word-break: break-word;
}

/* Step navigator. --sc / --sc-bg are per-step accent DATA set inline on each
   .fin-step (documented accent-data exception). */
.fin-steps { display: flex; background: #fff; border-radius: 14px; box-shadow: 0 2px 10px rgba(0,0,0,.08); overflow: hidden; margin-bottom: 1.5rem; }
.fin-step { flex: 1; display: flex; flex-direction: column; align-items: center; padding: .7rem .2rem .6rem; cursor: pointer; border: none; background: transparent; border-right: 1px solid #f1f5f9; gap: .2rem; transition: background .18s; }
.fin-step:last-child { border-right: none; }
.fin-step:hover { background: #f8fafc; }
.fin-step.s-active { background: var(--sc-bg); }
.fin-step .sn { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: var(--ax-text-2xs); background: #e5e7eb; color: var(--ax-text-muted); transition: all .2s; }
.fin-step.s-active .sn { background: var(--sc); color: #fff; }
.fin-step .sl { font-size: var(--ax-text-2xs); color: var(--ax-text-muted); font-weight: 500; text-align: center; line-height: 1.2; }
.fin-step.s-active .sl { color: var(--sc); font-weight: 600; }
.fin-step i { font-size: var(--ax-text-md); color: #6b7480; transition: color .18s; }
.fin-step.s-active i { color: var(--sc); }

/* Colored accordion borders + the matching numbered badge and header icon.
   One AA-safe accent per section; badge is white on that accent, header icon
   is that accent on white. */
.acc-f1 { border-left: 4px solid #1d4ed8 !important; }
.acc-f2 { border-left: 4px solid #1a7f4b !important; }
.acc-f3 { border-left: 4px solid #0b6b8f !important; }
.acc-f4 { border-left: 4px solid #b4460a !important; }
.acc-f5 { border-left: 4px solid #a35a00 !important; }
.acc-f6 { border-left: 4px solid #6d28d9 !important; }
.acc-f7 { border-left: 4px solid #9b1c2e !important; }
.acc-f8 { border-left: 4px solid #047857 !important; }
.acc-f1 .sbadge { background: #1d4ed8; } .acc-f1 .accordion-button > i { color: #1d4ed8; }
.acc-f2 .sbadge { background: #1a7f4b; } .acc-f2 .accordion-button > i { color: #1a7f4b; }
.acc-f3 .sbadge { background: #0b6b8f; } .acc-f3 .accordion-button > i { color: #0b6b8f; }
.acc-f4 .sbadge { background: #b4460a; } .acc-f4 .accordion-button > i { color: #b4460a; }
.acc-f5 .sbadge { background: #a35a00; } .acc-f5 .accordion-button > i { color: #a35a00; }
.acc-f6 .sbadge { background: #6d28d9; } .acc-f6 .accordion-button > i { color: #6d28d9; }
.acc-f7 .sbadge { background: #9b1c2e; } .acc-f7 .accordion-button > i { color: #9b1c2e; }
.acc-f8 .sbadge { background: #047857; } .acc-f8 .accordion-button > i { color: #047857; }
.acc-f1 .accordion-button:not(.collapsed) { background: #eff6ff; color: #1e40af; }
.acc-f2 .accordion-button:not(.collapsed) { background: #f0fdf4; color: #14532d; }
.acc-f3 .accordion-button:not(.collapsed) { background: #ecfeff; color: #164e63; }
.acc-f4 .accordion-button:not(.collapsed) { background: #fffbeb; color: #92400e; }
.acc-f5 .accordion-button:not(.collapsed) { background: #fffbeb; color: #92400e; }
.acc-f6 .accordion-button:not(.collapsed) { background: #faf5ff; color: #4c1d95; }
.acc-f7 .accordion-button:not(.collapsed) { background: #fef2f2; color: #991b1b; }
.acc-f8 .accordion-button:not(.collapsed) { background: #ecfdf5; color: #065f46; }
.accordion-button:not(.collapsed) { font-weight: 600; }
.accordion-button:focus { box-shadow: none; }
.sbadge { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; font-size: var(--ax-text-2xs); font-weight: 700; color: #fff; flex-shrink: 0; margin-right: .5rem; }
.req { color: var(--ax-danger); }

/* The dense finance form normalises nine ad-hoc rem sizes onto the token scale. */
.ax-fs-2xs { font-size: var(--ax-text-2xs); }
.ax-fs-xs  { font-size: var(--ax-text-xs); }
.ax-fs-sm  { font-size: var(--ax-text-sm); }
.ax-fs-md  { font-size: var(--ax-text-md); }

/* Payout header (08-02) keeps its soft-green tint in BOTH states — the other
   sections tint only when expanded. Was an unconditional inline style. */
.acc-f8 .accordion-button { background: #ecfdf5; color: #065f46; }
.ax-progress-thin { height: 5px; }

/* ── Finance / Ledger list utilities (08-01, 11-xx) · Batch 12 ────────────── */
.ax-minh-52   { min-height: 52px; }
.ax-minw-180  { min-width: 180px; }
.ax-text-purple { color: #6d28d9 !important; }   /* AA-safe purple text (was #7c3aed inline) */
.ax-btn-pill  { padding: 2px 10px; border-radius: 20px; vertical-align: middle; }

/* ── Part 4 (bookings / customer / settings / FY) utilities · Batch 12 ─────── */
.ax-avatar--md { width: 48px; height: 48px; font-size: 1.4rem; }
.ax-w-half     { width: 50%; max-width: 100%; }

/* ── Settings · index image previews (18-01) · Batch 12 ──────────────────────
   Logo / favicon / stamp preview boxes. Was duplicated inline on each <img>
   AND re-applied as JS cssText on upload; both now use these classes. */
.ax-img-preview          { object-fit: contain; border: 1px solid #dee2e6; border-radius: 4px; }
.ax-img-preview--logo    { height: 48px; max-width: 160px; padding: 3px; }
.ax-img-preview--stamp   { height: 48px; max-width: 120px; padding: 3px; }
.ax-img-preview--favicon { width: 32px; height: 32px; }
.ax-color-swatch { width: 46px; height: 31px; padding: 1px 3px; }
.ax-input-90     { max-width: 90px; }

/* ── Tab panel (Vehicle Add list) · Batch 18 ──────────────────────────────────
   Batch 18 moved the "Add Vehicle" button and the Filter control INSIDE the
   New/Used tab area, so the controls read as belonging to the tab they act on
   rather than to the page. This gives that grouping a visible edge: a subtle
   rule along the top ties the panel to the tab strip directly above it, which
   is what makes "these controls are part of this tab" legible at a glance.

   Tokens only (no literals) so it follows both themes automatically. */
.ax-tab-panel {
    border-top: var(--ax-border-width) solid var(--ax-border-subtle);
    padding-top: 0.75rem;
}

/* ── Showcase token chip (top bar) ───────────────────────────────────────────
   The dealer's spend meter. It sat beside the FY switcher in the same quiet
   subtle-grey treatment as everything else in the top bar and was read as
   furniture; the owner asked for it to be noticed.

   So it is the one SOLID-FILLED thing in that row. That is the whole design:
   the FY button and the role badge are subtle fills, and a single saturated
   pill among them draws the eye without adding size, weight or another row.

   Three states, driven by TokenService::balanceState() — one classifier, one
   threshold constant, no second opinion in the markup:

       .ax-token-chip              healthy  — primary, solid
       .ax-token-chip--low         1..LOW   — warning, solid
       .ax-token-chip--empty       0        — danger, solid + a steady pulse

   All colour comes from --ax-* tokens, so dark mode follows automatically and
   a11y:contrast keeps checking the pairs (each fill uses its own -on colour).
   No new library, no new framework class: every selector here is defined in
   this file, which is what ui:class-audit reads.                             */
.ax-token-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--ax-space-2);
    padding: var(--ax-space-2) var(--ax-space-4);
    border-radius: var(--ax-radius-pill);
    background: var(--ax-primary);
    color: var(--ax-primary-on);
    border: var(--ax-border-width) solid var(--ax-primary);
    font-size: var(--ax-text-xs);
    font-weight: var(--ax-weight-bold);
    line-height: var(--ax-leading-snug);
    /* NEVER let it wrap or be squeezed: it shares a flex row with the FY
       switcher and the user menu, and a chip that reflows moves the whole
       top bar on a narrow screen. */
    white-space: nowrap;
    flex-shrink: 0;
}
.ax-token-chip .ax-token-chip__n {
    font-size: var(--ax-text-sm);
    font-variant-numeric: tabular-nums;
}
.ax-token-chip--low {
    background: var(--ax-warning);
    border-color: var(--ax-warning);
    color: var(--ax-warning-on);
}
.ax-token-chip--empty {
    background: var(--ax-danger);
    border-color: var(--ax-danger);
    color: var(--ax-danger-on);
    animation: ax-token-pulse 2.4s var(--ax-easing-standard) infinite;
}
/* Slow and low-amplitude on purpose — enough to catch the eye on a screen the
   dealer is already looking at, not enough to nag while they work. */
@keyframes ax-token-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(var(--ax-danger-rgb), 0.55); }
    50%      { box-shadow: 0 0 0 var(--ax-space-2) rgba(var(--ax-danger-rgb), 0); }
}
@media (prefers-reduced-motion: reduce) {
    .ax-token-chip--empty { animation: none; }
}

/* ═══ THE HEADER BELL ══════════════════════════════════════════════════════
   Between the FY switcher and the token chip.

   DELIBERATELY QUIETER THAN THE TOKEN CHIP. That chip is the one solid-filled
   thing in the top bar by design (see its note above), and a second saturated
   pill beside it would cancel the first — two things shouting is the same as
   neither shouting. So the bell is a bare icon button at rest and earns colour
   only when there is something unread, and then only on a small badge.

   All colour from --ax-* tokens, so dark mode follows and a11y:contrast keeps
   checking the pairs. Every selector used by the markup is defined here, which
   is what ui:class-audit reads.                                             */
.ax-bell-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--ax-space-2);
    border: var(--ax-border-width) solid transparent;
    border-radius: var(--ax-radius-pill);
    background: transparent;
    color: var(--ax-text-muted);
    line-height: 1;
    /* Shares a flex row with the FY switcher and the chip — it must not
       reflow the top bar on a narrow screen. */
    flex-shrink: 0;
}
.ax-bell-btn:hover,
.ax-bell-btn:focus-visible {
    color: var(--ax-text);
    background: var(--ax-surface-2);
}
.ax-bell-btn i { font-size: var(--ax-text-base); }

/* Unread: the icon itself goes solid and takes the text colour, so the state
   survives for anyone who cannot distinguish the badge's fill. */
.ax-bell-btn--unread { color: var(--ax-text); }

.ax-bell-badge {
    position: absolute;
    top: 0;
    inset-inline-end: 0;
    transform: translate(35%, -35%);
    min-width: var(--ax-space-6);
    padding: 0 var(--ax-space-1);
    border-radius: var(--ax-radius-pill);
    background: var(--ax-danger);
    color: var(--ax-danger-on);
    font-size: var(--ax-text-2xs, 0.625rem);
    font-weight: var(--ax-weight-bold);
    font-variant-numeric: tabular-nums;
    line-height: var(--ax-space-6);
    text-align: center;
}

.ax-bell-menu {
    width: min(22rem, calc(100vw - var(--ax-space-8)));
    padding: 0;
    overflow: hidden;
}
/* The list scrolls; the header with "Mark all read" does not, so the action
   stays reachable however long the feed is. */
.ax-bell-list {
    max-height: 60vh;
    overflow-y: auto;
}
.ax-bell-item {
    white-space: normal;
    padding: var(--ax-space-3) var(--ax-space-4);
    border-bottom: var(--ax-border-width) solid var(--ax-border);
}
.ax-bell-item:last-child { border-bottom: 0; }
.ax-bell-item--unread { background: var(--ax-surface-2); }

/* Two lines of body, then ellipsis: the dropdown must stay a glance, not a
   reading task. The full text is on the screen the item links to. */
.ax-bell-body {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.ax-bell-dot {
    flex-shrink: 0;
    width: var(--ax-space-2);
    height: var(--ax-space-2);
    margin-top: var(--ax-space-2);
    border-radius: var(--ax-radius-pill);
    background: var(--ax-danger);
}
.ax-bell-dot--read { background: transparent; }
