/**
 * ADA Systems - the components.
 *
 * Every repeated object on the site is defined here, once, in terms of the
 * tokens from theme.css. Nothing in this file carries a hex code, a raw pixel
 * radius or a hard-coded spacing: if a value is missing, it belongs in the
 * theme, named for what it means.
 *
 * The point is that "I do not like this button any more" is one edit here, or
 * one token in the theme - not a hunt through sixty-six stylesheets. Before
 * this file, `.btn` was defined in eleven of them and `.btn-primary` in nine.
 *
 * Loaded after theme.css and before everything else.
 */

/* ==========================================
   BASE

   The reset. It was written three times - app.css, login.css and the ADA test
   layout - so whether a box measured border-box depended on which layout you
   were under. components.css is loaded by every layout, so it belongs here and
   only here.
   ========================================== */

*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* ==========================================
   KEYFRAMES

   Every animation the site shares, defined once.

   This was not only duplication. components.css itself ran `float` and
   `rotate` on the public page orbs, and neither was defined anywhere except
   landing.css and landing-page.css - so on the other 74 pages those two
   animations simply did not exist and the declaration was dropped silently.
   fleet-dispatch.css spun its optimise button with `spin`, which no stylesheet
   defined at all.

   `float` also had two versions under one name - four stops on the landing
   pages, two on legal and the app download - and `pulse` had three. The
   richer one wins; the landing page's scaling dot keeps its own name.
   ========================================== */

@keyframes float {
    0%, 100% { transform: translate(0, 0) scale(1); }
    25% { transform: translate(30px, -30px) scale(1.05); }
    50% { transform: translate(-20px, 20px) scale(0.95); }
    75% { transform: translate(20px, 30px) scale(1.02); }
}

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

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

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

@keyframes pulse-scale {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.5; transform: scale(1.2); }
}

@keyframes pulse-glow {
    0%, 100% { box-shadow: 0 0 20px color-mix(in srgb, var(--public-accent) 20%, transparent); }
    50% { box-shadow: 0 0 30px color-mix(in srgb, var(--public-accent) 40%, transparent); }
}

@keyframes gradient-shift {
    0%, 100% { background-position: 0% center; }
    50% { background-position: 100% center; }
}

/* ==========================================
   BUTTON

   One element, one definition. A button is an inline-flex box of a fixed
   control height so it lines up with the input next to it, a variant for the
   colour, and a size for the height. Nothing else.

       <button class="btn btn-primary">Save</button>
       <a class="btn btn-secondary btn-sm" href="...">Cancel</a>
       <button class="btn btn-ghost btn-icon"><i class="fas fa-times"></i></button>

   In Blade this is <x-button variant="primary">, which renders exactly these
   classes and picks <a> or <button> from whether it was given an href.
   ========================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--control-height-md);
    padding: var(--control-pad-md);
    border: 1px solid transparent;
    border-radius: var(--control-radius);
    background: none;
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition:
        background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition-fast);
}

.btn i {
    font-size: 1em;
}

/* One focus signal for the whole site, and only for keyboard users - a mouse
   click on a button should not leave a ring behind it. */
.btn:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.btn:disabled,
.btn.is-disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

/* --- variants ------------------------------------------------------------ */

.btn-primary {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--text-on-brand);
    box-shadow: var(--shadow-primary);
}

.btn-primary:hover:not(:disabled) {
    background: var(--primary-dark);
    border-color: var(--primary-dark);
    box-shadow: var(--shadow-primary-hover);
    transform: translateY(-1px);
}

.btn-secondary {
    background: var(--bg-white);
    border-color: var(--border);
    color: var(--text-dark);
}

.btn-secondary:hover:not(:disabled) {
    background: var(--bg-light);
    border-color: var(--border-strong);
}

.btn-ghost {
    background: transparent;
    color: var(--text-gray);
}

.btn-ghost:hover:not(:disabled) {
    background: var(--bg-hover);
    color: var(--text-dark);
}

.btn-outline {
    background: transparent;
    border-color: var(--border);
    color: var(--text-dark);
}

.btn-outline:hover:not(:disabled) {
    background: var(--bg-light);
    border-color: var(--border-strong);
}

.btn-light {
    background: var(--bg-light);
    border-color: var(--border);
    color: var(--text-dark);
}

.btn-light:hover:not(:disabled) {
    background: var(--bg-hover);
}

.btn-success {
    background: var(--success-strong);
    border-color: var(--success-strong);
    color: var(--text-on-brand);
}

.btn-success:hover:not(:disabled) {
    filter: brightness(0.93);
}

/* Warning and danger are the quiet, tinted form - which is what the admin
   pages use, because a table row full of shouting red buttons reads as an
   error page. The loud one is .btn-danger-solid, for a confirm that deletes. */
.btn-warning {
    background: var(--warning-surface);
    border-color: var(--warning-line);
    color: var(--on-warning-surface);
}

.btn-warning:hover:not(:disabled) {
    background: var(--warning-line);
}

.btn-danger,
.btn-danger-outline {
    background: transparent;
    border-color: var(--danger-line);
    color: var(--on-danger-surface);
}

.btn-danger:hover:not(:disabled),
.btn-danger-outline:hover:not(:disabled) {
    background: var(--danger-surface);
}

.btn-danger:focus-visible,
.btn-danger-outline:focus-visible,
.btn-danger-solid:focus-visible {
    box-shadow: var(--focus-ring-danger);
}

.btn-danger-solid {
    background: var(--danger-strong);
    border-color: var(--danger-strong);
    color: var(--text-on-brand);
}

.btn-danger-solid:hover:not(:disabled) {
    filter: brightness(0.93);
}

/* --- sizes --------------------------------------------------------------- */

.btn-xs {
    min-height: var(--control-height-xs);
    padding: var(--control-pad-xs);
    font-size: var(--text-xs);
    gap: var(--space-1);
    border-radius: var(--radius-sm);
}

.btn-sm {
    min-height: var(--control-height-sm);
    padding: var(--control-pad-sm);
    font-size: var(--text-sm);
    gap: var(--space-1);
}

.btn-lg {
    min-height: var(--control-height-lg);
    padding: var(--control-pad-lg);
    font-size: var(--text-md);
}

/* --- shapes -------------------------------------------------------------- */

/* Icon only: square, so a row of them lines up whatever glyph is inside. */
.btn-icon {
    width: var(--control-height-md);
    min-width: var(--control-height-md);
    padding: 0;
}

.btn-xs.btn-icon {
    width: var(--control-height-xs);
    min-width: var(--control-height-xs);
}

.btn-sm.btn-icon {
    width: var(--control-height-sm);
    min-width: var(--control-height-sm);
}

.btn-lg.btn-icon {
    width: var(--control-height-lg);
    min-width: var(--control-height-lg);
}

.btn-block {
    width: 100%;
}

/* The action buttons in a management table's last column. This is the same
   thing as .btn.btn-secondary.btn-xs and the call sites collapse into that at
   point 7, when the table row becomes a component; until then it is at least
   defined once here instead of six times across the mgmt-* stylesheets. */
.btn-icon-small {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--control-height-xs);
    padding: var(--control-pad-xs);
    border: var(--control-border);
    border-radius: var(--radius-sm);
    background: var(--bg-white);
    color: var(--text-dark);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition);
}

.btn-icon-small:hover:not(:disabled) {
    background: var(--bg-light);
    border-color: var(--border-strong);
}

.btn-icon-small.btn-danger {
    border-color: var(--danger-line);
    color: var(--on-danger-surface);
    background: transparent;
}

.btn-icon-small.btn-danger:hover:not(:disabled) {
    background: var(--danger-surface);
}

/* A row of buttons, evenly spaced - the thing every page was writing out as a
   flex div with its own gap. */
.btn-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.btn-row-end {
    justify-content: flex-end;
}

/* On a phone a button gives back some of its side padding, so a row of two or
   three still fits across the screen. */
@media (max-width: 768px) {
    .btn {
        padding: 0 var(--space-3);
    }

    .btn-lg {
        padding: 0 var(--space-4);
    }

    .page-wrapper {
        padding: var(--space-4) var(--space-3) var(--space-8);
    }

    .header-card {
        padding: var(--space-3) var(--space-4);
    }

    .page-header {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-3);
    }

    .page-title {
        font-size: var(--text-lg);
    }
    .modal-content {
        max-width: calc(100% - var(--space-8));
        border-radius: var(--radius-lg);
    }

    .modal-header {
        padding: var(--space-5) var(--space-5) var(--space-4);
    }

    .modal-body,
    .modal-body-scroll {
        padding: var(--space-6) var(--space-5) var(--space-5);
    }

    .modal-footer {
        padding: var(--space-3) var(--space-5);
    }

    .modal-title {
        font-size: var(--text-lg);
    }
    .form-row {
        grid-template-columns: 1fr;
    }
    .report-filters form {
        flex-direction: column;
        align-items: stretch;
    }
}

/* ==========================================
   HUB TILE

   The launcher tile both hubs are built from - 8 on Fleet Operations, 13 on ADA
   Administration. The whole tile is the link, so it is the action: it lifts on
   hover and the cursor changes, which is the only click cue it needs.

   It carried two more things and no longer does. An "Open Map" pill at the
   bottom, which was a button-shaped box that could not be clicked on its own and
   which repeated the title in other words. And a round gradient icon bubble.
   Both are gone at Vlad's call, 11.09.2026.

   What remains of the colour coding is the halo under the tile, computed from
   one accent hue instead of the five hex codes each colour used to carry.
   ========================================== */

.hub-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: var(--space-4);
}

.hub-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-height: 150px;
    padding: var(--space-5);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-xl);
    text-decoration: none;
    position: relative;
    transition: transform var(--transition-fast), box-shadow var(--transition-fast);

    /* The tile's own colour, set by .hub-card-<accent> below. A tile with no
       accent class still looks right rather than losing its shadow. */
    --accent: var(--primary);

    box-shadow:
        0 18px 45px color-mix(in srgb, var(--accent) 16%, transparent),
        0 0 0 1px var(--hub-ring);
}

.hub-card:hover {
    transform: translateY(-3px);
    box-shadow:
        0 22px 55px color-mix(in srgb, var(--accent) 26%, transparent),
        0 0 0 1px var(--hub-ring-hover);
}

html.dark .hub-card {
    box-shadow:
        0 18px 45px color-mix(in srgb, var(--accent) 25%, transparent),
        0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}

html.dark .hub-card:hover {
    box-shadow:
        0 22px 55px color-mix(in srgb, var(--accent) 35%, transparent),
        0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
}

/* Twelve hues as plain modifiers, for anything that reads var(--accent) - the
   hub tiles below and the dispatch board's stat chips, which set
   .accent-purple and friends and had nothing behind them, so four differently
   coloured counts all came out green. */
.accent-blue { --accent: var(--accent-blue); }
.accent-purple { --accent: var(--accent-purple); }
.accent-cyan { --accent: var(--accent-cyan); }
.accent-orange { --accent: var(--accent-orange); }
.accent-green { --accent: var(--accent-green); }
.accent-pink { --accent: var(--accent-pink); }
.accent-emerald { --accent: var(--accent-emerald); }
.accent-slate { --accent: var(--accent-slate); }
.accent-indigo { --accent: var(--accent-indigo); }
.accent-amber { --accent: var(--accent-amber); }
.accent-red { --accent: var(--accent-red); }
.accent-teal { --accent: var(--accent-teal); }

.hub-card-blue { --accent: var(--accent-blue); }
.hub-card-purple { --accent: var(--accent-purple); }
.hub-card-cyan { --accent: var(--accent-cyan); }
.hub-card-orange { --accent: var(--accent-orange); }
.hub-card-green { --accent: var(--accent-green); }
.hub-card-pink { --accent: var(--accent-pink); }
.hub-card-emerald { --accent: var(--accent-emerald); }
.hub-card-slate { --accent: var(--accent-slate); }
.hub-card-indigo { --accent: var(--accent-indigo); }
.hub-card-amber { --accent: var(--accent-amber); }
.hub-card-red { --accent: var(--accent-red); }
.hub-card-teal { --accent: var(--accent-teal); }

/* The title carries the tile's colour. It used to be plain dark text and the
   colour lived in a round gradient icon bubble; with the bubble gone the tile
   had no colour left at all, so the coding moved onto the one thing on the card
   that is always there and always read. Pulled towards the text colour far
   enough to stay legible - see --accent-ink-mix. */
.hub-card-title {
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    color: color-mix(in srgb, var(--accent) var(--accent-ink-mix), var(--text-dark));
}

.hub-card-subtitle {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

.hub-card-desc {
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--text-gray);
}

/* The tile that is not available yet: no link, no lift, no pointer. */
.hub-card.disabled-card {
    opacity: 0.55;
    cursor: default;
}

.hub-card.disabled-card:hover {
    transform: none;
}

/* --- reordering (SortableJS) --------------------------------------------- */

.hub-card-drag-handle {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    color: var(--text-light);
    font-size: var(--text-sm);
    opacity: 0;
    cursor: grab;
    transition: opacity var(--transition-fast), background var(--transition-fast);
}

.hub-card:hover .hub-card-drag-handle {
    opacity: 1;
}

.hub-card-drag-handle:hover {
    background: var(--bg-hover);
    color: var(--text-gray);
}

.hub-card-ghost {
    opacity: 0.4;
}

.hub-card-chosen {
    cursor: grabbing;
}

.hub-card-drag {
    transform: rotate(1.5deg);
}

/* ==========================================
   STAT CHIP

   The small labelled figure that sits in a page header - "6/6 vehicles",
   "4 overdue". Eleven of them across the two hubs and the dispatch board.

   It was `.stat-pill`, defined twice: a 999px capsule in fleet-hub.css and a
   6px rectangle in fleet-dispatch.css. The same class was a different shape
   depending on which page you were on, and the capsule sat directly above a
   grid of rectangular tiles. One definition, rectangular, and the name no
   longer says pill.

       <div class="stat-chip"><i class="fas fa-truck"></i> 6/6 vehicles</div>
       <div class="stat-chip stat-chip-success">1 online</div>
   ========================================== */

.stat-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--bg-light);
    color: var(--text-gray);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    white-space: nowrap;
}

.stat-chip i {
    font-size: 0.9em;
}

/* The status forms. A chip states a fact, so it is tinted rather than filled -
   a header full of solid colour reads as a row of alerts. */
.stat-chip-success {
    background: var(--success-surface);
    color: var(--on-success-surface);
}

.stat-chip-warning {
    background: var(--warning-surface);
    color: var(--on-warning-surface);
}

.stat-chip-danger {
    background: var(--danger-surface);
    color: var(--on-danger-surface);
}

.stat-chip-info {
    background: var(--info-surface);
    color: var(--on-info-surface);
}

/* Dispatch counts its jobs by colour alone, on the plain background. */
.stat-chip-accent {
    color: color-mix(in srgb, var(--accent) var(--accent-ink-mix), var(--text-dark));
}

.stat-chip-row {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* ==========================================
   PAGE FURNITURE

   The shell every page is built from: the centred column, the card the title
   sits in, the title itself, and the row of actions beside it.

   Six stylesheets defined .page-wrapper, five defined .header-card and TEN
   defined .page-title - at 24px, 22px, 1.75rem and 1.25rem, in three
   hand-written colours, with the header card rounded to 18px in some and 16px
   in others. Which one you got depended on which page you were standing on.
   ========================================== */

.page-wrapper {
    max-width: 1400px;
    margin: 0 auto;
    padding: var(--space-6) var(--space-4) var(--space-10);
}

/* The two pages that genuinely want a different measure: a dashboard full of
   live panels, and a single form. */
.page-wrapper-wide {
    max-width: 1600px;
}

.page-wrapper-narrow {
    max-width: 900px;
}

.header-card {
    padding: var(--space-4) var(--space-5);
    margin-bottom: var(--space-5);
    background: var(--bg-white);
    border-radius: var(--radius-xl);
    box-shadow: var(--elevation-3), 0 0 0 1px var(--border);
}

.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
}

/* A title is a flex row, so a page that puts an icon or a registration plate
   beside its heading needs no rule of its own - three pages had one. */
.page-title {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: 0;
    font-size: var(--text-2xl);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.page-subtitle {
    margin-top: var(--space-1);
    font-size: var(--text-base);
    color: var(--text-gray);
}

.page-header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* The compact heading in a toolbar - the map and geofencing, where the title
   shares a strip with the controls rather than sitting in a card. It used to
   be .page-title too, which is why that class had to mean two sizes. */
.toolbar-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

/* The two ends of a toolbar strip. These were called .header-left and
   .header-right on the map, geofencing and dispatch - the same names the
   application shell uses for its top bar in app.css. Since every one of those
   pages also loads app.css, each was quietly restyling the global navigation's
   spacing for as long as it was open. Renamed so the shell keeps its own. */
.toolbar-left {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.toolbar-right {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.portal-switch {
    margin-top: var(--space-6);
    text-align: center;
}

/* On a phone the header stacks and gives back some of its padding. Four
   stylesheets each had their own version of this, with four sets of numbers. */
/* The status row some pages use instead of a subtitle: a badge, a last-seen
   time, a sync note. Three stylesheets defined it identically. */
.page-meta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-gray);
    flex-wrap: wrap;
}

/* ==========================================
   MODAL

   One shell, one state class. There were three: .modal-overlay opened with
   .active in modal.css and with .show in geofencing.css, and .modal-box in
   management.css and report-fuel-consumption.css was a second shell with its
   own sizes. The state is .active everywhere now.

       <div class="modal-overlay" id="addThing">
           <div class="modal-content modal-medium"> ... </div>
       </div>

   In Blade this is <x-modal id="addThing">. The header's gradient used to be
   indigo (var(--accent-indigo) to var(--accent-violet)) while the rest of the site is blue, so a modal
   announced itself in a colour that appears nowhere else; it is the brand
   gradient now.
   ========================================== */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-slow), visibility var(--transition-slow);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
    .modal-overlay {
        background: var(--scrim-blurred);
        backdrop-filter: blur(4px);
        -webkit-backdrop-filter: blur(4px);
    }
}

.modal-overlay.active {
    opacity: 1;
    visibility: visible;
}

.modal-content {
    width: 100%;
    max-width: 480px;
    margin: var(--space-4);
    overflow: hidden;
    background: var(--bg-white);
    border-radius: var(--radius-xl);
    box-shadow: var(--elevation-3), 0 0 0 1px var(--border);
    transform: scale(0.9) translateY(20px);
    transition: transform var(--transition-slow);
}

.modal-overlay.active .modal-content {
    transform: scale(1) translateY(0);
}

.modal-content.modal-small { max-width: 400px; }
.modal-content.modal-medium { max-width: 600px; }
.modal-content.modal-wide { max-width: 720px; }

/* --- header ------------------------------------------------------------- */

.modal-header {
    position: relative;
    padding: var(--space-6) var(--space-6) var(--space-5);
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
}

/* The curved skirt under the header. It reads as part of the header, so it
   inherits the same gradient rather than repeating it. */
.modal-header::after {
    content: '';
    position: absolute;
    bottom: -20px;
    left: 0;
    right: 0;
    height: 40px;
    background: inherit;
    border-radius: 0 0 50% 50% / 0 0 100% 100%;
}

.modal-header-content {
    position: relative;
    z-index: 1;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.modal-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    margin-bottom: var(--space-3);
    border-radius: var(--radius-lg);
    background: var(--on-brand-wash);
}

.modal-icon i {
    font-size: var(--text-xl);
    color: var(--text-on-brand);
}

.modal-title {
    margin: 0;
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    color: var(--text-on-brand);
}

.modal-subtitle {
    margin-top: var(--space-1);
    font-size: var(--text-base);
    color: var(--on-brand-muted);
}

.modal-close {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--control-height-sm);
    height: var(--control-height-sm);
    border: none;
    border-radius: var(--radius-md);
    background: var(--on-brand-wash);
    color: var(--text-on-brand);
    cursor: pointer;
    transition: background var(--transition), transform var(--transition);
}

.modal-close:hover {
    background: var(--on-brand-wash-strong);
    transform: rotate(90deg);
}

.modal-close i {
    font-size: var(--text-base);
}

/* --- body and footer ----------------------------------------------------- */

.modal-body {
    padding: var(--space-8) var(--space-6) var(--space-6);
}

.modal-body-scroll {
    padding: var(--space-8) var(--space-6) var(--space-6);
    max-height: 60vh;
    overflow-y: auto;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-6);
    border-top: 1px solid var(--border);
}

/* --- toggle switch ------------------------------------------------------- */

.toggle-switch {
    position: relative;
    display: inline-block;
    width: 44px;
    height: var(--control-height-xs);
}

.toggle-switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

.toggle-slider {
    position: absolute;
    inset: 0;
    border-radius: var(--control-height-xs);
    background: var(--border);
    cursor: pointer;
    transition: background var(--transition-slow);
}

.toggle-slider:before {
    content: "";
    position: absolute;
    left: 3px;
    bottom: 3px;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-round);
    background: var(--bg-white);
    box-shadow: var(--elevation-1);
    transition: transform var(--transition-slow);
}

.toggle-switch input:checked + .toggle-slider {
    background: var(--primary);
}

.toggle-switch input:checked + .toggle-slider:before {
    transform: translateX(20px);
}

.toggle-switch input:focus-visible + .toggle-slider {
    box-shadow: var(--focus-ring);
}

/* ==========================================
   FORM CONTROLS

   Seven stylesheets defined .form-group - margins of 1.25rem, 20px, 12px, 16px
   and 1rem - and the field itself was written eight ways: some on .form-input,
   some as a descendant of .form-group, with 8px, 10px and 12px corners and a
   focus ring in three different colours. modal.css added a ninth, scoped to
   the modal body, so the same field looked different inside a dialog.

   Both forms are supported on purpose: the explicit class, and the bare element
   inside a .form-group. Two hundred fields use the second and are not worth
   touching to prove a point.

       <div class="form-group">
           <label class="form-label">Registration</label>
           <input type="text" name="reg">
       </div>
   ========================================== */

.form-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
    position: relative;
}

.form-group:last-child {
    margin-bottom: 0;
}

.form-label,
.form-group > label {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-dark);
}

.input,
.select,
.form-input,
.form-select,
.form-textarea,
.form-group > input,
.form-group > select,
.form-group > textarea {
    width: 100%;
    min-height: var(--control-height-md);
    padding: var(--space-2) var(--space-3);
    border: var(--control-border);
    border-radius: var(--control-radius);
    background: var(--bg-white);
    color: var(--text-dark);
    font-family: inherit;
    font-size: var(--text-base);
    transition: border-color var(--transition), box-shadow var(--transition);
}

.input:focus,
.select:focus,
.form-input:focus,
.form-select:focus,
.form-textarea:focus,
.form-group > input:focus,
.form-group > select:focus,
.form-group > textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.input:disabled,
.select:disabled,
.form-input:disabled,
.form-select:disabled,
.form-textarea:disabled,
.form-group > input:disabled,
.form-group > select:disabled,
.form-group > textarea:disabled {
    background: var(--bg-light);
    color: var(--text-light);
    cursor: not-allowed;
}

.form-input::placeholder,
.form-textarea::placeholder,
.form-group > input::placeholder,
.form-group > textarea::placeholder {
    color: var(--text-light);
}

.form-input[readonly],
.form-group > input[readonly] {
    background: var(--bg-light);
    color: var(--text-gray);
}

.form-textarea,
.form-group > textarea {
    min-height: 80px;
    resize: vertical;
}

.form-select,
.form-group > select {
    cursor: pointer;
}

/* --- layout ------------------------------------------------------------- */

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3);
}

.form-row > .form-group.flex-1 { flex: 1; }
.form-row > .form-group.flex-2 { flex: 2; }

.form-group-narrow {
    width: 80px;
    flex-shrink: 0;
}

.form-group-half {
    flex: 1;
}

.form-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
}

/* --- help and errors ---------------------------------------------------- */

.form-hint,
.help-text {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

/* The browser draws the date field's calendar icon nearly black, which
   disappears on a dark field. Written in management.css and again on the
   vehicle edit page. */
html.dark input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(1);
}

/* The validation message under a field. Written as .field-error in
   management.css and again on the device create page, in the same two hex
   codes, next to a .form-error here that meant the same thing. */
.field-error {
    margin: 0;
    color: var(--on-danger-surface);
    font-size: var(--text-sm);
}

/* A field that failed validation. It was written inside a media query in
   mgmt-device-create.css, so the driver create page marked nothing red. */
.input-error {
    border-color: var(--danger);
}

.form-error {
    font-size: var(--text-sm);
    color: var(--on-danger-surface);
}

/* The asterisk after a field's name. It was written four times - app.css,
   management.css, contact.css and here - and here it was scoped to
   .form-group, which half the forms do not use. */
.required {
    color: var(--danger);
    font-weight: var(--weight-semibold);
}

/* A settings row: a label on the left, a control on the right. The map and
   device dashboards use it inside their settings modals. */
.settings-group {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--border);
}

.settings-group:last-child {
    border-bottom: none;
}

.settings-label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    color: var(--text-dark);
}

.settings-label i {
    width: 18px;
    text-align: center;
    color: var(--text-gray);
}

.settings-select {
    min-height: var(--control-height-sm);
    padding: var(--space-1) var(--space-3);
    border: var(--control-border);
    border-radius: var(--control-radius);
    background: var(--bg-white);
    color: var(--text-dark);
    font-size: var(--text-base);
}

/* Two fields side by side, and the row of buttons under a form. Both were
   written twice, in management.css and again on the device create page. */
.form-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-5);
}

.form-footer {
    grid-column: 1 / -1;
    display: flex;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}

/* A checkbox with its wording beside it. */
.checkbox-label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--primary);
    cursor: pointer;
}

/* A field with a button welded to its right - a lookup, a copy. */
.input-with-button .input,
.input-lookup-row .input,
.input-with-badge .input {
    flex: 1;
}

.input.textarea {
    min-height: 80px;
    resize: vertical;
}

/* ==========================================
   CARD

   The white panel most pages are built out of - 517 uses. Three stylesheets
   defined it, with 16px, 14px and 18px corners, two of them with a
   hand-written var(--text-on-brand) and var(--border).
   ========================================== */

.card {
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-2);
    overflow: hidden;
}

/* A card that holds prose or a form takes its own padding; one holding a table
   leaves it to the cells. */
.card-padded {
    padding: var(--space-6);
}

.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border);
}

.card-header h3 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.card-body {
    padding: var(--space-5);
}

.card-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border);
}

/* ==========================================
   CARD INNARDS

   The header strip, the footer with the pager, and the small grey line beside
   a section title. The markup on the devices list, the activity log, the
   pending devices and the subscriptions page has used these names all along
   and no stylesheet ever defined them, so a card title sat flush against the
   card edge with no padding and no rule under it. One page had noticed and
   added a second class, .devices-card-header-padded, to put the padding back -
   while .card-header, which is exactly this, was already in this file.
   ========================================== */

.card-header-warning {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    background: var(--warning-surface);
    border-bottom: 1px solid var(--warning-line);
    color: var(--on-warning-surface);
}

/* A card that is asking to be looked at. */
.attention-card {
    border-color: var(--warning-line);
}

/* The count beside a section title - "Showing 1-25 of 340 devices". */
.section-meta {
    color: var(--text-gray);
    font-size: var(--text-sm);
}

/* The pager at the foot of a card. */
.card-pagination {
    display: flex;
    justify-content: center;
    padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border);
}

/* The buttons under a filter panel. They are full width, so they stack. */
.filters-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

/* A row that is there for context rather than for action - a cancelled
   subscription listed under the live ones. */
.table-row-muted {
    color: var(--text-gray);
    opacity: 0.7;
}

/* The second line in a table cell: how many vehicles on a package, a driver's
   licence class. */
.package-meta,
.license-meta {
    color: var(--text-gray);
    font-size: var(--text-sm);
}

.price-gbp {
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

/* ==========================================
   BADGE

   A state in one word: ONLINE, ACTIVE, EXPIRED. 122 uses, four definitions -
   20px corners in two of them, 999px in another - and nineteen variants that
   between them repeated the same four status colours.
   ========================================== */

.badge {
    min-width: var(--badge-min-width);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--bg-light);
    color: var(--text-gray);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    line-height: var(--leading-tight);
    white-space: nowrap;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

.badge-lg {
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-sm);
}

.badge-muted,
.badge-secondary {
    background: var(--bg-light);
    color: var(--text-gray);
}

.badge-primary,
.badge-info,
.badge-updated {
    background: var(--info-surface);
    border-color: var(--info-line);
    color: var(--on-info-surface);
}

.badge-success,
.badge-online,
.badge-created,
.badge-vehicle-parked {
    background: var(--success-surface);
    border-color: var(--success-line);
    color: var(--on-success-surface);
}

.badge-warning,
.badge-pending,
.badge-vehicle-idle {
    background: var(--warning-surface);
    border-color: var(--warning-line);
    color: var(--on-warning-surface);
}

.badge-danger,
.badge-error,
.badge-offline,
.badge-deleted,
.badge-refused {
    background: var(--danger-surface);
    border-color: var(--danger-line);
    color: var(--on-danger-surface);
}

/* The status badge is the same object under an older name - 15 uses. */
.status-badge {
    min-width: var(--badge-min-width);
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--bg-light);
    color: var(--text-gray);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
}

/* ==========================================
   STAT CARD

   The figure-and-label tile in a row under a page header. Five definitions,
   41 uses.
   ========================================== */

.stats-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.stat-card {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    /* A grid item refuses to be narrower than its own content unless told
       otherwise, and the retention card's content is wide - a label that does
       not wrap plus a select sized by "6 months". Without this the card grew
       past its column and out of the row: 293px of card in a 195px track,
       hanging 31px off the end of the page. Measured at 430px. */
    min-width: 0;
    flex-wrap: wrap;
    row-gap: var(--space-2);
    padding: var(--space-4);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-1);
}

.stat-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background: var(--primary-soft);
    color: var(--primary);
    font-size: var(--text-md);
}

/* The packages page colour-codes its four stat icons, but the modifiers only
   ever existed as .report-icon.blue and friends in other files, so all four
   came out the same default. */
.stat-icon.blue { background: color-mix(in srgb, var(--accent-blue) 12%, transparent); color: var(--accent-blue); }
.stat-icon.green { background: color-mix(in srgb, var(--accent-emerald) 12%, transparent); color: var(--accent-emerald); }
.stat-icon.purple { background: color-mix(in srgb, var(--accent-purple) 12%, transparent); color: var(--accent-purple); }
.stat-icon.cyan { background: color-mix(in srgb, var(--accent-cyan) 12%, transparent); color: var(--accent-cyan); }
.stat-icon.orange { background: color-mix(in srgb, var(--accent-orange) 12%, transparent); color: var(--accent-orange); }

/* The text column beside a device's avatar. `min-width: 0` is what lets a long
   name truncate instead of pushing the row wider than its cell - it lived in
   devices.css, and the three management pages with the same markup do not load
   that file, so their rows could blow out. */
.device-main {
    min-width: 0;
}

/* When a device last spoke. Written in device-dashboard.css and again on the
   settings page, at two sizes and two greys. */
.last-seen {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

/* The value and label of a stat, stacked. management.css had it; the device
   dashboard uses it and does not load management.css. */
.stat-content {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.stat-value {
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    color: var(--text-dark);
    line-height: var(--leading-tight);
}

.stat-label {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

/* ==========================================
   ALERT

   A message about what just happened. Four stylesheets defined the four
   variants, each with its own rgba wash and its own text colour.
   ========================================== */

.alert,
.alert-success,
.alert-error,
.alert-warning,
.alert-info {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    border: 1px solid;
    border-radius: var(--radius-md);
    font-size: var(--text-base);
}

.alert-success {
    background: var(--success-surface);
    border-color: var(--success-line);
    color: var(--on-success-surface);
}

.alert-error,
.alert-danger {
    background: var(--danger-surface);
    border-color: var(--danger-line);
    color: var(--on-danger-surface);
}

.alert-warning {
    background: var(--warning-surface);
    border-color: var(--warning-line);
    color: var(--on-warning-surface);
}

.alert-info {
    background: var(--info-surface);
    border-color: var(--info-line);
    color: var(--on-info-surface);
}

/* ==========================================
   EMPTY STATE

   Nothing to show yet. Six definitions, with padding of 40px and 60px, and one
   of them reaching for !important.
   ========================================== */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-12) var(--space-5);
    text-align: center;
    color: var(--text-gray);
}

.empty-state i {
    font-size: var(--text-2xl);
    color: var(--text-light);
}

.empty-state p {
    margin: 0;
}

.empty-state-full {
    grid-column: 1 / -1;
}

/* ==========================================
   TABLE
   ========================================== */

.table,
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-base);
}

.table th,
.table td,
.data-table th,
.data-table td {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    border-bottom: 1px solid var(--border);
}

.table th,
.data-table th {
    background: var(--bg-light);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-gray);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    white-space: nowrap;
}

.table tbody tr:last-child td,
.data-table tbody tr:last-child td {
    border-bottom: none;
}

.table tbody tr:hover,
.data-table tbody tr:hover {
    background: var(--bg-light);
}

/* A table wider than its column scrolls inside its own box, never the page. */
.table-container {
    overflow-x: auto;
}

/* ==========================================
   SECTION DIVIDER

   A heading that separates two blocks on one page. Three definitions.
   ========================================== */

.section-divider {
    margin: var(--space-10) 0 var(--space-5);
    padding-bottom: var(--space-3);
    border-bottom: 2px solid var(--border);
}

.section-divider h2,
.section-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

/* The status badge states. They were spread across five stylesheets - active,
   online and valid all meaning the same green, inactive, offline and expired
   the same red - each with its own pair of hex codes. */
.status-badge.active,
.status-badge.online,
.status-badge.valid,
.status-badge.connected {
    background: var(--success-surface);
    color: var(--on-success-surface);
}

.status-badge.inactive,
.status-badge.offline,
.status-badge.expired,
.status-badge.suspended {
    background: var(--danger-surface);
    color: var(--on-danger-surface);
}

.status-badge.pending,
.status-badge.warning {
    background: var(--warning-surface);
    color: var(--on-warning-surface);
}

/* Who someone is. The users list has always written .badge-role and
   .badge-admin and the rest, and nothing styled any of them, so every role
   came out the same grey. */
.badge-role {
    background: var(--bg-sunken);
    border-color: var(--border);
    color: var(--text-gray);
}

.badge-superadmin,
.badge-superuser {
    background: color-mix(in srgb, var(--accent-purple) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent-purple) 30%, transparent);
    color: color-mix(in srgb, var(--accent-purple) var(--accent-ink-mix), var(--text-dark));
}

.badge-admin {
    background: var(--info-surface);
    border-color: var(--info-line);
    color: var(--on-info-surface);
}

.badge-driver {
    background: var(--success-surface);
    border-color: var(--success-line);
    color: var(--on-success-surface);
}

/* The quiet form of a danger badge - a warning that is not an alarm. */
.badge-danger-soft {
    background: var(--danger-surface);
    border-color: transparent;
    color: var(--on-danger-surface);
}

/* A vehicle badge: the accent that is not a status. */
.badge-vehicle {
    background: color-mix(in srgb, var(--accent-indigo) 12%, transparent);
    border-color: var(--info-line);
    color: var(--on-info-surface);
}

/* ==========================================
   PROSE

   Long-form text: the legal documents and the API reference. Both had their own
   typography - 23 rules under .legal-content, 14 under .api-docs - for the same
   job, with different heading sizes, different line heights and two sets of
   list markers.

   It sits on the public palette, because both places it is used are public
   pages. The decorative parts of the legal documents - the gradient bar beside
   a heading, the numbered chips down an ordered list - stay in legal.css, where
   they belong: that is styling, not structure.

       <div class="prose"> ... </div>
   ========================================== */

.prose {
    color: var(--public-text-secondary);
    font-size: var(--text-base);
    line-height: var(--leading-relaxed);
}

.prose h2 {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-10) 0 var(--space-5);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--public-border);
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    color: var(--public-text);
}

.prose h2:first-child {
    margin-top: 0;
}

.prose h3 {
    margin: var(--space-6) 0 var(--space-3);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--public-text);
}

.prose p {
    margin-bottom: var(--space-4);
}

.prose a {
    color: var(--public-accent);
    text-decoration: none;
    transition: color var(--transition);
}

.prose a:hover {
    color: var(--public-accent-secondary);
}

.prose strong {
    color: var(--public-text);
    font-weight: var(--weight-semibold);
}

.prose ul,
.prose ol {
    margin: 0 0 var(--space-5);
    padding-left: 0;
    list-style: none;
}

.prose ol {
    counter-reset: item;
}

.prose li {
    position: relative;
    padding-left: var(--space-8);
    margin-bottom: var(--space-3);
    line-height: var(--leading-relaxed);
}

.prose code {
    padding: var(--space-1) var(--space-1);
    border-radius: var(--radius-xs);
    background: var(--public-code-bg);
    color: var(--public-accent);
    font-family: var(--font-mono);
    font-size: 0.9em;
}

.prose pre {
    overflow-x: auto;
    padding: var(--space-4);
    margin-bottom: var(--space-5);
    border: 1px solid var(--public-border);
    border-radius: var(--radius-md);
    background: var(--public-code-bg);
}

.prose pre code {
    padding: 0;
    background: none;
    color: var(--public-text-secondary);
}

.prose table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-base);
}

.prose th,
.prose td {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    border-bottom: 1px solid var(--public-border);
}

.prose th {
    color: var(--public-text);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

.prose td {
    color: var(--public-text-secondary);
}

.prose tr:last-child td {
    border-bottom: none;
}

/* ==========================================
   REPORT FURNITURE

   Every report page is the same shape: a filter bar, a line saying which period
   is being shown, and a grid table. Nine stylesheets carried their own copy -
   `.report-filters`, `.report-filters form`, `.filter-group`, `.filter-group
   label` and `.report-period` were byte-identical in all nine, and the table
   rules had drifted into three versions of themselves.

   What stays per report is the column template, because that is the one thing
   each report genuinely decides for itself:

       .cost-analysis .report-table-head,
       .cost-analysis .report-table-row { grid-template-columns: 2fr 1fr 1fr; }
   ========================================== */

.report-filters {
    padding: var(--space-5);
    margin-bottom: var(--space-5);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
}

.report-filters form {
    display: flex;
    align-items: flex-end;
    gap: var(--space-4);
    flex-wrap: wrap;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.filter-group label {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-gray);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

/* The filter fields are the site's fields, at the size the bar wants. */
.filter-group > input,
.filter-group > select {
    min-height: var(--control-height-md);
    padding: var(--space-2) var(--space-3);
    border: var(--control-border);
    border-radius: var(--control-radius);
    background: var(--bg-white);
    color: var(--text-dark);
    font-family: inherit;
    font-size: var(--text-base);
    transition: border-color var(--transition), box-shadow var(--transition);
}

.filter-group > input:focus,
.filter-group > select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.report-period {
    margin-bottom: var(--space-5);
    font-size: var(--text-base);
    color: var(--text-gray);
}

/* The scorecard prints the period right under a heading that already has a
   margin, so it pulls up and pushes the table down. */
.report-period-tight {
    margin-top: -10px;
    margin-bottom: var(--space-5);
}

/* --- the grid table ------------------------------------------------------ */

.report-table {
    overflow: hidden;
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
}

.report-table-head {
    display: grid;
    padding: var(--space-3) var(--space-5);
    background: var(--bg-light);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-gray);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

.report-table-row {
    display: grid;
    align-items: center;
    padding: var(--space-3) var(--space-5);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-base);
}

.report-table-row:last-child {
    border-bottom: none;
}

.report-table-row:hover {
    background: var(--bg-light);
}

/* Three reports - device health, driver activity, vehicle activity - want the
   same six columns and the same four when the window narrows, and each had
   written it out. It is a modifier on the table now, so a fourth report that
   wants those columns asks for them by name. */
.report-table--6 .report-table-head,
.report-table--6 .report-table-row {
    grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1fr;
}

@media (max-width: 1024px) {
    .report-table--6 .report-table-head,
    .report-table--6 .report-table-row {
        grid-template-columns: 2fr 1fr 1fr 1fr;
    }

    .report-table--6 .col-hide-mobile {
        display: none;
    }
}

/* --- the person in a row ------------------------------------------------- */

.driver-info {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.driver-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--radius-round);
    background: var(--primary-soft);
    color: var(--primary);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}

.driver-name {
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.driver-meta {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

/* --- the summary row above a report -------------------------------------- */

.stats-grid,
.summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.summary-card {
    padding: var(--space-4);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
}

.summary-card .label {
    font-size: var(--text-sm);
    color: var(--text-gray);
}

.summary-card .value {
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    color: var(--text-dark);
}

/* ==========================================
   PUBLIC PAGE FURNITURE

   The landing page and the feature, pricing and compliance pages are two
   stylesheets - welcome.blade.php loads landing-page.css, the rest load
   landing.css - and 52 rules were written identically in both. The hero
   grids and the breakpoints genuinely differ per page and stay where they
   are; this is what they agreed on.
   ========================================== */

/* Base Landing Styles */
.landing-wrapper {
    background: var(--public-bg);
    min-height: 100vh;
    overflow-x: hidden;
    position: relative;
}

/* Animated Grid Background */
.landing-wrapper::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-image:
        linear-gradient(var(--public-grid) 1px, transparent 1px),
        linear-gradient(90deg, var(--public-grid) 1px, transparent 1px);
    background-size: 60px 60px;
    pointer-events: none;
    z-index: 0;
}

/* Floating Gradient Orbs */
.orb {
    position: fixed;
    border-radius: 50%;
    filter: blur(80px);
    pointer-events: none;
    z-index: 0;
    animation: float 20s ease-in-out infinite;
}

.orb-1 {
    width: 600px;
    height: 600px;
    background: var(--public-glow);
    top: -200px;
    right: -200px;
    animation-delay: 0s;
}

.orb-2 {
    width: 500px;
    height: 500px;
    background: var(--public-glow-secondary);
    bottom: -150px;
    left: -150px;
    animation-delay: -10s;
}

.orb-3 {
    width: 300px;
    height: 300px;
    background: color-mix(in srgb, var(--success-strong) 30%, transparent);
    top: 50%;
    left: 50%;
    animation-delay: -5s;
}

/* Container */
.landing-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 var(--space-6);
    position: relative;
    z-index: 1;
}

/* Glass Card Base */
.glass-card {
    background: color-mix(in srgb, var(--bg-body) 85%, transparent);
    border: 1px solid var(--public-border);
    border-radius: var(--radius-3xl);
    padding: var(--space-8);
    transition: all var(--transition-slow) cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    overflow: hidden;
}

html:not(.dark) .glass-card {
    background: color-mix(in srgb, var(--text-on-brand) 85%, transparent);
}

.glass-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--text-on-brand) 10%, transparent), transparent);
}

/* Section Header */
.section-header {
    text-align: center;
    margin-bottom: var(--space-12);
}

.section-header p {
    font-size: var(--text-lg);
    color: var(--public-text-secondary);
    max-width: 600px;
    margin: 0 auto;
}

/* Buttons */
.btn-glow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-8);
    border-radius: var(--radius-lg);
    font-weight: var(--weight-semibold);
    font-size: var(--text-base);
    text-decoration: none;
    transition: all var(--transition-slow) ease;
    position: relative;
    overflow: hidden;
}

.btn-primary-glow {
    background: linear-gradient(135deg, var(--public-accent), var(--public-accent-secondary));
    color: white;
    box-shadow: 0 4px 20px var(--public-glow);
}

.btn-primary-glow:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 30px var(--public-glow);
}

.btn-secondary-glow {
    background: var(--public-glass);
    border: 1px solid var(--public-border);
    color: var(--public-text);
}

.btn-secondary-glow:hover {
    background: var(--public-glass-hover);
    border-color: var(--public-accent);
}

/* Feature Icon Wrapper */
.feature-icon-wrap {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-xl);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-2xl);
    margin-bottom: var(--space-5);
    position: relative;
}

.feature-icon-wrap::after {
    content: '';
    position: absolute;
    inset: -2px;
    border-radius: var(--radius-xl);
    background: linear-gradient(135deg, var(--public-accent), var(--public-accent-secondary));
    z-index: -1;
    opacity: 0;
    transition: opacity var(--transition-slow) ease;
}

.icon-cyan { background: linear-gradient(135deg, color-mix(in srgb, var(--public-accent) 20%, transparent), color-mix(in srgb, var(--public-accent) 10%, transparent)); color: var(--public-accent); }

.icon-purple { background: linear-gradient(135deg, color-mix(in srgb, var(--accent-violet) 20%, transparent), color-mix(in srgb, var(--accent-violet) 10%, transparent)); color: var(--public-accent-secondary); }

.icon-green { background: linear-gradient(135deg, color-mix(in srgb, var(--success-strong) 20%, transparent), color-mix(in srgb, var(--success-strong) 10%, transparent)); color: var(--public-accent-tertiary); }

.icon-orange { background: linear-gradient(135deg, color-mix(in srgb, var(--warning-strong) 20%, transparent), color-mix(in srgb, var(--warning-strong) 10%, transparent)); color: var(--public-accent-warning); }

.icon-red { background: linear-gradient(135deg, color-mix(in srgb, var(--danger-strong) 20%, transparent), color-mix(in srgb, var(--danger-strong) 10%, transparent)); color: var(--public-accent-danger); }

.icon-blue { background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 20%, transparent), color-mix(in srgb, var(--primary) 10%, transparent)); color: var(--primary); }

/* Feature List */
.feature-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.feature-list li:last-child {
    border-bottom: none;
}

/* Compliance Cards */
.compliance-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-5);
}

.compliance-icon {
    width: 72px;
    height: 72px;
    border-radius: var(--radius-2xl);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-3xl);
    margin: 0 auto var(--space-5);
    background: linear-gradient(135deg, color-mix(in srgb, var(--public-accent) 15%, transparent), color-mix(in srgb, var(--accent-violet) 15%, transparent));
    color: var(--public-accent);
}

.compliance-card h3 {
    font-size: var(--text-lg);
    font-weight: var(--weight-bold);
    color: var(--public-text);
    margin-bottom: var(--space-3);
}

.compliance-card p {
    font-size: var(--text-base);
    color: var(--public-text-secondary);
    line-height: var(--leading-relaxed);
}

/* CTA Section */
.cta-section {
    padding: var(--space-20) 0;
}

.cta-card {
    padding: var(--space-16);
    text-align: center;
    background: linear-gradient(135deg, color-mix(in srgb, var(--public-accent) 10%, transparent), color-mix(in srgb, var(--accent-violet) 10%, transparent));
    position: relative;
    overflow: hidden;
}

.cta-card::before {
    content: '';
    position: absolute;
    top: -50%;
    left: -50%;
    width: 200%;
    height: 200%;
    background: radial-gradient(circle, color-mix(in srgb, var(--public-accent) 10%, transparent) 0%, transparent 50%);
    animation: rotate 30s linear infinite;
}

.cta-content {
    position: relative;
    z-index: 1;
}

.cta-card h2 {
    font-size: clamp(var(--text-3xl), 4vw, var(--text-6xl));
    font-weight: var(--weight-extrabold);
    color: var(--public-text);
    margin-bottom: var(--space-4);
}

.cta-card p {
    font-size: var(--text-lg);
    color: var(--public-text-secondary);
    margin-bottom: var(--space-8);
    max-width: 500px;
    margin-left: auto;
    margin-right: auto;
}

/* ==========================================
   FOOTER

   One footer, on every page. It used to be two: app.css set the box and the
   light background, and components.css fought it back with six !important
   rules in raw hex because it loaded first. Both are here now, in tokens, and
   nothing needs !important to win an argument with itself.
   ========================================== */

.footer {
    position: relative;
    z-index: var(--z-sticky);
    margin-top: auto;
    padding: var(--space-12) 0 var(--space-6);
    background: var(--footer-bg);
    border-top: 1px solid var(--footer-border);
}

.footer-content {
    position: relative;
    z-index: var(--z-sticky);
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--space-8);
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--space-5);
}

.footer-section .footer-title {
    color: var(--text-dark);
}

.footer-section a,
.footer-section p,
.footer-link-btn {
    color: var(--text-gray);
}

.footer-section a:hover,
.footer-link-btn:hover {
    color: var(--public-accent);
}

.footer-bottom {
    max-width: 1200px;
    margin: var(--space-8) auto 0;
    padding: var(--space-8) var(--space-5) 0;
    border-top: 1px solid var(--footer-border);
    color: var(--text-gray);
    font-size: var(--text-sm);
    text-align: center;
}

/* ==========================================
   RETENTION CONTROL

   "Keep logs for N days" - a chip holding a label, a control and a tick that
   flashes when it saves. It appears twice: on the activity log as a slider
   with a save button, and on a device dashboard as a small select tucked into
   the corner of a stat card.

   Three stylesheets carried a copy. management.css and management-activity-log
   were identical to the byte; the device dashboard's was the same object drawn
   smaller, in its own hex codes. The small one is now a modifier rather than a
   second object, so the chip is one thing with one place to change it.
   ========================================== */

.retention-control {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    background: var(--bg-light);
    border: var(--control-border);
    border-radius: var(--radius-lg);
}

/* In the corner of a stat card, so it sits on a card rather than on the page:
   recessed instead of raised, and no line of its own. */
.retention-control--compact {
    gap: var(--space-2);
    margin-left: auto;
    /* Wraps under the figure rather than out of the card. */
    max-width: 100%;
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    background: var(--bg-sunken);
    border-color: transparent;
    border-radius: var(--radius-md);
}

.retention-label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-base);
    color: var(--text-gray);
    white-space: nowrap;
}

.retention-label i {
    color: inherit;
}

.retention-control--compact .retention-label {
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
}

/* --- the slider form (activity log) -------------------------------------- */

.retention-slider {
    -webkit-appearance: none;
    appearance: none;
    width: 120px;
    height: 6px;
    background: linear-gradient(
        to right,
        var(--text-gray) 0%,
        var(--text-gray) var(--progress, 40%),
        var(--border) var(--progress, 40%),
        var(--border) 100%
    );
    border-radius: var(--radius-xs);
    outline: none;
    cursor: pointer;
}

.retention-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    background: var(--text-gray);
    border-radius: var(--radius-round);
    cursor: pointer;
    box-shadow: 0 2px 6px color-mix(in srgb, var(--text-gray) 40%, transparent);
    transition: transform var(--transition-fast);
}

.retention-slider::-webkit-slider-thumb:hover {
    transform: scale(1.1);
}

.retention-slider::-moz-range-thumb {
    width: 18px;
    height: 18px;
    background: var(--text-gray);
    border: none;
    border-radius: var(--radius-round);
    cursor: pointer;
    box-shadow: 0 2px 6px color-mix(in srgb, var(--text-gray) 40%, transparent);
}

.retention-value {
    min-width: 65px;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-gray);
    text-align: right;
}

.retention-save-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    background: var(--bg-light);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-light);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.retention-save-btn:hover {
    background: var(--bg-hover);
    border-color: var(--text-light);
    color: var(--text-dark);
}

.retention-save-btn.changed {
    background: var(--success);
    border-color: var(--success-strong);
    color: var(--text-on-brand);
}

.retention-save-btn.changed:hover {
    background: var(--success-strong);
    border-color: var(--success-strong);
}

.retention-save-btn.saving {
    pointer-events: none;
    opacity: 0.6;
}

/* --- the select form (device dashboard) ---------------------------------- */

.retention-select {
    min-width: 0;
    padding: var(--space-1) var(--space-2);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-sm);
    color: var(--text-dark);
    font-family: inherit;
    font-size: var(--text-xs);
    cursor: pointer;
}

.retention-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

/* --- the tick ------------------------------------------------------------ */

.retention-saved {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    color: var(--success);
    visibility: hidden;
    opacity: 0;
    transition: opacity var(--transition-slow), visibility var(--transition-slow);
}

.retention-saved.show {
    visibility: visible;
    opacity: 1;
}

/* In the compact chip the tick sits in the flow next to the select, not over
   the save button it is replacing. */
.retention-control--compact .retention-saved {
    position: static;
    width: auto;
    height: auto;
    font-size: var(--text-sm);
}

/* The heading of a card. app.css said 1.25rem, management.css said 1rem, and
   which one a page got depended on whether it happened to load the monolith. */
.card-title {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

/* Two stat boxes side by side rather than as many as fit - the driver and
   vehicle indexes, where there are exactly two numbers worth showing. */
.stats-grid--pair {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}

/* The bare arrow beside a page title - what <x-back-link plain> renders. It
   was written identically in fleet-map.css, geofencing.css and, scoped to its
   own header, fleet-dispatch.css, while login.css carried a fourth version
   that nothing had used since the auth pages stopped having one. */
.back-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--control-height-md);
    height: var(--control-height-md);
    border-radius: var(--radius-md);
    color: var(--text-gray);
    text-decoration: none;
    transition: all var(--transition);
}

.back-link:hover {
    background: var(--bg-hover);
    color: var(--text-dark);
}

/* Two numbers beside each other in a status bar - "12 vehicles", "3 zones".
   The map and geofencing each had their own copy.

   Scoped to .status-stats on purpose: .stat-item is also a padded box in
   management.css and a centred block on the landing pages, and those pages
   load this file too. Same name, three objects - the scope keeps them apart
   until they are renamed. */
.status-stats .stat-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-base);
    color: var(--text-gray);
}

.status-stats .stat-item i {
    color: var(--text-light);
}

/* ==========================================
   STATUS DOT

   The small round light next to a name. Four stylesheets had one, at 8px or
   10px, each with its own set of state colours in raw hex - the same green
   three times over. One dot now, one size, states named for what they mean.
   ========================================== */

.status-dot {
    width: 10px;
    height: 10px;
    flex-shrink: 0;
    border-radius: var(--radius-round);
    background: var(--text-light);
}

.status-dot.online,
.status-dot.active { background: var(--success); }
.status-dot.idle { background: var(--warning); }
.status-dot.parked { background: var(--primary); }
.status-dot.inactive { background: var(--text-gray); }
.status-dot.error { background: var(--danger); }

/* A vehicle in motion, and anything else that wants the light to breathe. */
.status-dot.moving,
.status-dot--pulse { animation: status-pulse 2s infinite; }

@keyframes status-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* ==========================================
   COUNT BADGE

   A number beside a heading - pending requests, how many times a log line
   repeated. Written three times: amber in management and in the monolith it
   was extracted from, indigo on the device logs, each with its own dark-mode
   override. The three status surfaces carry both themes on their own, so the
   overrides go with them.
   ========================================== */

.count-badge {
    display: inline-block;
    padding: var(--space-1) var(--space-2);
    background: var(--warning-surface);
    border-radius: var(--radius-sm);
    color: var(--on-warning-surface);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
}

.count-badge.count-info {
    background: var(--info-surface);
    color: var(--on-info-surface);
}

.count-badge.count-high {
    background: var(--danger-surface);
    color: var(--on-danger-surface);
}

/* A quiet note inside a form or a report - "this is what this page does with
   your data", "rest periods are calculated to EU 561/2006". Four stylesheets
   had one: two reports agreed on a blue wash, the user form used the info
   surface, and login used the plain grey panel. One box now, on the theme's
   info surface, which carries its own dark mode. */
.info-box {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
    padding: var(--space-4);
    background: var(--info-surface);
    border: 1px solid var(--info-line);
    border-radius: var(--radius-md);
    color: var(--on-info-surface);
    font-size: var(--text-base);
}

.info-box i,
.info-box svg {
    flex-shrink: 0;
    margin-top: var(--space-1);
    color: var(--info);
}

.info-box p {
    margin: 0;
}

/* --- odds shared by exactly two pages each --------------------------------

   The sidebar search on the map and geofencing, the section header and "no
   data" block on the cost and fuel reports, the note body on the two
   tachograph reports. Each pair was written twice because the second page was
   made from the first. */

.sidebar-header h3 {
    margin: 0;
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.sidebar-search input {
    width: 100%;
    padding: var(--space-2) var(--space-3) var(--space-2) var(--space-9);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-md);
    color: var(--text-dark);
    font-size: var(--text-base);
}

.sidebar-search input:focus {
    outline: none;
    border-color: var(--primary);
}

.sidebar-search i {
    position: absolute;
    top: 50%;
    left: 1.75rem;
    transform: translateY(-50%);
    color: var(--text-light);
    font-size: var(--text-base);
}

.report-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border);
}

.report-section-header h3 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.no-data i {
    margin-bottom: var(--space-4);
    font-size: var(--text-6xl);
    opacity: 0.3;
}

.no-data p {
    margin-bottom: var(--space-4);
}

.summary-card .value small {
    color: var(--text-gray);
    font-size: var(--text-base);
    font-weight: var(--weight-normal);
}

.info-box-content h4 {
    margin-bottom: var(--space-1);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
}

.info-box-content p {
    margin: 0;
    color: var(--text-gray);
    font-size: var(--text-sm);
    line-height: var(--leading-base);
}

/* The heading and table inside a report's summary panel, and the role list on
   the user forms. Each was written twice - the geofence-activity and
   trip-history reports, the user create and edit pages - because the second
   page was made by copying the first. */

.summary-section h3 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.summary-section h3 i {
    color: var(--text-gray);
}

.summary-table th {
    padding: var(--space-2);
    border-bottom: 1px solid var(--border);
    color: var(--text-gray);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-align: left;
    text-transform: uppercase;
}

.summary-table td {
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-base);
}

.summary-table tr:last-child td {
    border-bottom: none;
}

.summary-table tr:hover td {
    background: var(--bg-light);
}

.empty-state h4 {
    margin-bottom: var(--space-2);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

/* ADA staff roles read purple, client roles green - the same pair the rest of
   the admin uses to tell the two sides apart. */
.role-select optgroup {
    padding: var(--space-2) 0 var(--space-1);
    color: var(--text-gray);
    font-style: normal;
    font-weight: var(--weight-semibold);
}

.role-select optgroup[label="ADA Staff"],
.role-select option.role-ada {
    color: var(--accent-purple);
}

.role-select optgroup[label="Client"],
.role-select option.role-client {
    color: var(--success-strong);
}

.role-select option.role-ada {
    background: color-mix(in srgb, var(--accent-purple) 8%, var(--bg-white));
}

.role-select option.role-client {
    background: var(--success-surface);
}

.role-select option:checked {
    font-weight: var(--weight-semibold);
}

/* A small toggle in a filter row - "All / Draft / Active". The map and the
   dispatch board each had one, at two radii and with two different resting
   states. */
.filter-btn {
    padding: var(--space-1) var(--space-2);
    background: transparent;
    border: var(--control-border);
    border-radius: var(--radius-sm);
    color: var(--text-gray);
    font-family: inherit;
    font-size: var(--text-sm);
    white-space: nowrap;
    cursor: pointer;
    transition: all var(--transition);
}

.filter-btn:hover {
    background: var(--bg-hover);
    color: var(--text-dark);
}

.filter-btn.active {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--text-on-brand);
}

/* The square avatar beside a name - initials, or a device icon. The gradient
   and its two live states were in management.css; the pending devices page
   added two more states of its own beside them. */
.avatar-square {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: var(--control-height-sm);
    background: radial-gradient(circle at 0 0, var(--accent-pink), var(--accent-indigo));
    border-radius: var(--radius-md);
    color: var(--text-on-brand);
    font-size: var(--text-base);
}

.avatar-square.offline {
    background: radial-gradient(circle at 0 0, var(--accent-pink), var(--accent-purple));
}

.avatar-square.online {
    background: radial-gradient(circle at 0 0, var(--success), var(--success-strong));
    animation: avatar-pulse-green 2s ease-in-out infinite;
}

.avatar-square.pending {
    background: linear-gradient(135deg, var(--warning), var(--warning-strong));
}

.avatar-square.refused {
    background: linear-gradient(135deg, var(--danger), var(--danger-strong));
}

@keyframes avatar-pulse-green {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 45%, transparent); }
    50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--success) 0%, transparent); }
}

/* The filled part of a progress bar. The device dashboard drew it as a purple
   gradient with three state gradients beside it; the tachograph report drew it
   flat with three state colours. One bar, the theme's own statuses. */
.progress-fill {
    height: 100%;
    background: var(--primary);
    border-radius: var(--radius-xs);
    transition: width var(--transition-slow);
}

.progress-fill.ok,
.progress-success .progress-fill {
    background: var(--success);
}

.progress-fill.warning,
.progress-warning .progress-fill {
    background: var(--warning);
}

.progress-fill.danger,
.progress-danger .progress-fill {
    background: var(--danger);
}

/* ==========================================
   GRID TABLE

   The same shape as the report table, under the older name the management
   pages use. It was written twice - once for the subscription create and show
   pages, once for the subscription index - in two sizes and two hover washes,
   while the only thing either page really decides is its column template.
   ========================================== */

.table-head {
    display: grid;
    align-items: center;
    padding: var(--space-3) var(--space-5);
    background: var(--bg-light);
    border-bottom: 1px solid var(--border);
    color: var(--text-gray);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.table-row {
    display: grid;
    align-items: center;
    padding: var(--space-3) var(--space-5);
    border-bottom: 1px solid var(--border);
    color: var(--text-dark);
    font-size: var(--text-base);
}

.table-row:last-child {
    border-bottom: none;
}

.table-row:hover {
    background: var(--bg-light);
}

/* ==========================================
   ROW IDENTITY

   Who or what a row is about: the name, the line of context under it, and the
   serial when there is one. The same three were written in management.css, on
   the devices list, on the device health report and on the device dashboard,
   at four sizes and three greys.
   ========================================== */

.device-name,
.vehicle-name,
.user-name,
.driver-name {
    margin-bottom: var(--space-1);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-dark);
}

.device-meta,
.vehicle-meta,
.user-meta,
.driver-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-sm);
    color: var(--text-gray);
}

.device-serial {
    margin-top: var(--space-1);
    color: var(--text-gray);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
}

/* ==========================================
   FILTER PANEL AND LIST FURNITURE

   The filter card above a list, the row of buttons in it, the pager under the
   list and the right-aligned actions cell. Each was written twice - once in
   management.css and once on the device logs, in raw hex - and the two had
   drifted apart in radius, padding and shadow.
   ========================================== */

.filters-card {
    margin-bottom: var(--space-5);
    padding: var(--space-4) var(--space-5);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-xl);
    box-shadow: 0 14px 40px color-mix(in srgb, var(--accent-indigo) 15%, transparent);
}

.filter-actions {
    display: flex;
    gap: var(--space-2);
    margin-left: auto;
}

.pagination-wrapper {
    display: flex;
    justify-content: center;
    margin-top: var(--space-3);
}

.col-actions {
    display: flex;
    gap: var(--space-1);
    justify-content: flex-end;
    flex-wrap: nowrap;
    text-align: right;
}

/* ==========================================
   COUNTRY PLATE

   The registration and the country chip on the two vehicle create pages. They
   were two stylesheets of three rules each, identical apart from the colour -
   blue for the UK, amber for the Netherlands - so adding a third country meant
   a third file. One shape, one modifier per country.
   ========================================== */

.country-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    background: var(--info-surface);
    border-radius: var(--radius-sm);
    color: var(--on-info-surface);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
}

.reg-number {
    padding: var(--space-2) var(--space-4);
    background: var(--bg-light);
    border-radius: var(--radius-md);
    font-family: var(--font-mono);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wider);
}

.country-badge--nl,
.reg-number--nl {
    background: var(--warning-surface);
    color: var(--on-warning-surface);
}

/* ==========================================
   SELECT2

   The third-party select, dressed to match the site's own controls. Two
   stylesheets carried the whole theme - device edit and vehicle edit - with
   eleven rules identical between them and the rest differing only in which
   radius they had picked. The purple was var(--accent-indigo) in the focus ring and
   var(--accent-purple-strong) in the chips, which are two different purples for one control.
   ========================================== */

/* Our rules and the vendor's use the same selectors, and the blades load
   select2.min.css after this file - so the vendor won, and its 28px selection
   sat next to our 38px inputs. Measured: 31px against 38px on the device edit
   form. Repeating the container class buys one point of specificity, which is
   enough, and does not need !important. */
.select2-container--default.select2-container--default .select2-selection--single {
    height: var(--control-height-md);
    padding: var(--space-1) var(--space-2);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--control-radius);
}

.select2-container--default.select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 28px;
    color: var(--text-dark);
}

.select2-container--default.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 36px;
}

.select2-container--default.select2-container--default .select2-selection--multiple {
    min-height: var(--control-height-md);
    padding: var(--space-1) var(--space-1);
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--control-radius);
}

.select2-container--default.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--default.select2-container--focus .select2-selection--multiple {
    border-color: var(--accent-purple);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-purple) 25%, transparent);
}

.select2-dropdown {
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--control-radius);
    box-shadow: 0 10px 25px var(--shadow);
}

.select2-container--default .select2-results__option {
    color: var(--text-dark);
}

.select2-container--default .select2-results__option--highlighted[aria-selected] {
    background: var(--accent-purple);
}

.select2-container--default .select2-selection--multiple .select2-selection__choice {
    margin: var(--space-1);
    padding: var(--space-1) var(--space-2);
    background: var(--accent-purple);
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-on-brand);
    font-size: var(--text-sm);
}

.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
    margin-right: var(--space-1);
    color: var(--text-on-brand);
}

.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
    background: transparent;
    color: var(--danger-line);
}

.select2-container--default .select2-search--inline .select2-search__field {
    margin-top: var(--space-1);
    color: var(--text-dark);
    font-size: var(--text-base);
}

/* ==========================================
   CARD SAVE BUTTON

   The small square tick that saves one section of a form without submitting
   the page. It existed three times - a 36px purple gradient on permissions, a
   28px grey-turning-purple square on device settings, and a 28px blue one in
   management.css that nothing reached, sitting below the purple one in the
   same file. Same button, three looks and one dead copy.

   Purple is kept because that is what it reads as on screen today; the states
   are the theme's own amber and green.
   ========================================== */

.btn-card-save {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--control-height-sm);
    height: var(--control-height-sm);
    background: var(--accent-purple);
    border: none;
    border-radius: var(--radius-md);
    color: var(--text-on-brand);
    cursor: pointer;
    transition: all var(--transition);
}

.btn-card-save i {
    font-size: var(--text-sm);
}

.btn-card-save:hover {
    transform: scale(1.05);
    box-shadow: 0 4px 15px color-mix(in srgb, var(--accent-purple) 40%, transparent);
}

.btn-card-save.saving {
    background: var(--warning);
    pointer-events: none;
}

.btn-card-save.saved {
    background: var(--success);
    animation: pulse-success var(--transition-slow) ease;
}

@keyframes pulse-success {
    0% { transform: scale(1); }
    50% { transform: scale(1.15); }
    100% { transform: scale(1); }
}

/* Hiding something. It lived in app.css, which the ADA test pages and the auth
   pages do not load - so `.is-hidden` did nothing there and the element it was
   meant to hide stayed on screen. The doubled class beats an inline style
   without reaching for !important, which would break the JavaScript that
   reveals these elements: an inline style beats a class, but not an important
   one. */
.is-hidden.is-hidden {
    display: none;
}

/* ==========================================
   TEXT UTILITIES

   One word, one meaning. These were written four times over with four
   different greens: .text-success was var(--accent-green) on device settings, var(--success-strong) in
   management, var(--success) in app.css. Same class, same name on screen,
   three colours - which is exactly the thing a theme is for.
   ========================================== */

.text-primary { color: var(--primary); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-danger { color: var(--danger); }
.text-muted { color: var(--text-light); }

.w-100 { width: 100%; }
.break-all { word-break: break-all; }

.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }

/* Three features across, on the landing pages and the app download page. */
.features-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-5);
}

/* A compliance panel on a public page - the same card on the landing page and
   on each feature page. */
.compliance-card {
    padding: var(--space-8);
    text-align: center;
    text-decoration: none;
}

/* One figure and its caption, centred. The landing page wraps it in a panel
   of its own; that stays there. */
.stat-item {
    text-align: center;
}

/* ==========================================
   ODDS AND ENDS

   Small objects that were written identically in two or three page
   stylesheets each - the sidebar header on the map and geofencing, the
   status words on the driver and vehicle forms, the summary table two
   reports share, a right-aligned cell.
   ========================================== */

.btn-toggle-sidebar {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--text-gray);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: all var(--transition);
}

.btn-toggle-sidebar:hover {
    background: var(--bg-hover);
}

.empty-state-icon {
    font-size: var(--text-6xl);
    margin-bottom: var(--space-4);
    opacity: 0.3;
}

/* =============================================
   Holiday Mode
   ============================================= */

.holidays-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-6);
}

.info-value {
    font-size: var(--text-md);
    font-weight: var(--weight-medium);
    color: var(--text-dark);
}

.no-data {
    padding: var(--space-15) var(--space-5);
    text-align: center;
    color: var(--text-gray);
}

.reg-display { display: flex; align-items: center; gap: var(--space-2); }

.reg-edit-input { font-size: var(--text-md); font-weight: var(--weight-semibold); font-family: var(--font-mono); letter-spacing: var(--tracking-wider); max-width: 200px; }

.report-section {
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-5);
    overflow: hidden;
}

.sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4);
    border-bottom: 1px solid var(--border);
}

.sidebar-search {
    position: relative;
    padding: var(--space-3) var(--space-4);
}

.slider-container {
        display: flex;
        align-items: center;
        gap: var(--space-2);
    }

/* Status message colors */
.status-error { color: var(--danger-strong); }

.status-muted { color: var(--text-light); }

.status-stats {
    display: flex;
    gap: var(--space-6);
}

.status-success { color: var(--success-strong); }

/* Summary Tables */
.summary-section {
    background: var(--bg-white);
    border: var(--control-border);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    margin-bottom: var(--space-6);
}

.summary-table {
    width: 100%;
    border-collapse: collapse;
}

.time-value {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
}

.time-value.danger {
    color: var(--danger-strong);
    font-weight: var(--weight-semibold);
}

.time-value.ok { color: var(--success-strong); }

.time-value.warning {
    color: var(--warning-strong);
    font-weight: var(--weight-semibold);
}

/* Grid layouts */
.two-column {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
}

html.dark .status-error { color: var(--danger); }

html.dark .status-muted { color: var(--text-gray); }

html.dark .status-success { color: var(--success); }

