/* =============================================================================
   Stockaroo - Modern Design System with Light/Dark Theme Support
   ============================================================================= */

/* -----------------------------------------------------------------------------
   Brand Fonts - self-hosted (frontend/fonts/, Fontshare FFL license)
   ----------------------------------------------------------------------------- */
@font-face {
    font-family: "Satoshi";
    src: url("/fonts/Satoshi-Variable.woff2") format("woff2");
    font-weight: 300 900;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Satoshi";
    src: url("/fonts/Satoshi-VariableItalic.woff2") format("woff2");
    font-weight: 300 900;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: "Clash Display";
    src: url("/fonts/ClashDisplay-Variable.woff2") format("woff2");
    font-weight: 200 700;
    font-style: normal;
    font-display: swap;
}

/* -----------------------------------------------------------------------------
   CSS Variables - Design Tokens (Light Theme - Default)
   ----------------------------------------------------------------------------- */
:root {
    /* Brand Fonts (Stockaroo) */
    --font-display: "Clash Display", "Satoshi", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --font-body: "Satoshi", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

    /* Type scale (theme-independent) */
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --text-2xl: 1.5rem;
    --text-3xl: 1.875rem;
    --text-4xl: 2.25rem;

    /* Primary Colors — Stockaroo forest green (sampled from the logo) */
    --color-primary: #25863a;
    --color-primary-hover: #1b6a2c;
    --color-primary-light: #28873c;
    --color-primary-dark: #134d20;
    /* Ink used on top of --color-primary fills (buttons, active nav, avatars) */
    --color-on-primary: #ffffff;

    /* Accent Colors — folded into the green family (brand is green-only) */
    --color-accent: #1b6a2c;
    --color-accent-hover: #134d20;

    /* Background Colors - Light Theme (warm paper, matches brand) */
    --bg-primary: #fbfcfa;
    --bg-secondary: #ffffff;
    --bg-tertiary: #f2f7f1;
    --bg-card: #ffffff;
    --bg-card-hover: #f7faf6;
    --bg-input: #ffffff;
    --bg-input-focus: #f7faf6;

    /* Border Colors - Light Theme */
    --border-primary: #e4ebe2;
    --border-secondary: #8f978d;
    --border-focus: #25863a;
    /* WCAG 1.4.11 requires 3:1 for the boundary of an interactive control.
       --border-primary is 1.22:1 on white — fine for decorative dividers and
       card edges, which the criterion does not cover, but not for inputs,
       selects, textareas or checkboxes. Kept as a separate token so raising it
       does not darken every card and table rule in the app. 4.63:1 on #ffffff. */
    --border-control: #6b7a6f;

    /* Text Colors - Light Theme (warm ink, matches brand) */
    --text-primary: #15241a;
    --text-secondary: #3c4a41;
    --text-tertiary: #5f6f64;
    --text-muted: #68726a;

    /* Status Colors */
    --color-success: #117e3a;
    --color-success-bg: rgba(22, 163, 74, 0.1);
    --color-warning: #a65b05;
    --color-warning-bg: rgba(217, 119, 6, 0.1);
    --color-error: #cf2424;
    --color-error-bg: rgba(220, 38, 38, 0.1);
    --color-info: #2562e9;
    --color-info-bg: rgba(37, 99, 235, 0.1);

    /* Platform Colors — accessible-as-text variants; logos/swatches use imagery */
    --color-ebay: #e03137;
    --color-amazon: #aa6600;
    --color-tiktok: #00807d;
    --color-etsy: #c85200;
    --color-royal-mail: #e60012;
    --color-dpd: #dc0032;
    /* UPS brand is brown + gold. The gold (#ffb500) fails badly as text on
       white, so the brown carries the wordmark and the gold is left to imagery. */
    --color-ups: #4a2c17;

    /* Shadows - Light Theme */
    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
    --shadow-xl: 0 20px 25px -5px rgba(19, 77, 32, 0.1), 0 10px 10px -5px rgba(19, 77, 32, 0.04);

    /* Border Radius */
    --radius-sm: 0.375rem;
    --radius-md: 0.5rem;
    --radius-lg: 0.75rem;
    --radius-xl: 1rem;
    --radius-full: 9999px;

    /* Transitions */
    --transition-fast: 150ms ease;
    --transition-normal: 250ms ease;
    --transition-slow: 350ms ease;

    /* Spacing */
    --sidebar-width: 260px;
    --header-height: 64px;
}

/* -----------------------------------------------------------------------------
   Dark Theme Override
   ----------------------------------------------------------------------------- */
[data-theme="dark"],
.dark-mode {
    /* Background Colors - Dark Theme (deep forest-tinted charcoal) */
    --bg-primary: #0d1410;
    --bg-secondary: #131c16;
    --bg-tertiary: #1c281f;
    --bg-card: #16211a;
    --bg-card-hover: #1f2c23;
    --bg-input: #131c16;
    --bg-input-focus: #1c281f;

    /* Border Colors - Dark Theme */
    --border-primary: #26362a;
    --border-secondary: #596a5d;
    /* See the light-theme note. 3.34:1 on --bg-input #131c16. */
    --border-control: #74856f;

    /* Text Colors - Dark Theme */
    --text-primary: #f1f6f1;
    --text-secondary: #a7b6aa;
    --text-tertiary: #808f85;
    --text-muted: #838e86;

    /* Primary Colors - Dark Theme (brighter green for contrast on dark) */
    --color-primary: #35b04f;
    --color-primary-hover: #43c25e;
    --color-primary-light: #5cd177;
    --color-primary-dark: #1b6a2c;
    /* Bright green can't carry white text (2.81:1) — use dark ink on it instead */
    --color-on-primary: #0d1410;

    /* Accent Colors - Dark Theme */
    --color-accent: #43c25e;
    --color-accent-hover: #5cd177;

    /* Focus ring - Dark Theme */
    --border-focus: #35b04f;

    /* Status Colors - Dark Theme */
    --color-success: #22c55e;
    --color-success-bg: rgba(34, 197, 94, 0.15);
    --color-warning: #f59e0b;
    --color-warning-bg: rgba(245, 158, 11, 0.15);
    --color-error: #ff5454;
    --color-error-bg: rgba(239, 68, 68, 0.15);
    --color-info: #4a94ff;
    --color-info-bg: rgba(59, 130, 246, 0.15);

    /* Platform Colors - Dark Theme (brighter) */
    --color-ebay: #e94f54;
    --color-amazon: #ff9900;
    --color-etsy: #f56400;
    --color-royal-mail: #ed4a57;
    --color-tiktok: #00f2ea;
    /* DPD's brand red is too dark to read on the dark card background */
    --color-dpd: #ff4d6a;
    /* UPS brown is unreadable on dark; the brand gold is the right choice here
       and passes comfortably against the dark card. */
    --color-ups: #f0ad3d;

    /* Shadows - Dark Theme */
    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4);
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
}

/* -----------------------------------------------------------------------------
   Base Styles
   ----------------------------------------------------------------------------- */
* {
    box-sizing: border-box;
}

body {
    font-family: var(--font-body);
    background-color: var(--bg-primary);
    color: var(--text-primary);
    line-height: 1.6;
    margin: 0;
    padding: 0;
    min-height: 100vh;
}

/* Custom Scrollbar */
::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: var(--bg-secondary);
}

::-webkit-scrollbar-thumb {
    background: var(--border-secondary);
    border-radius: var(--radius-full);
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-tertiary);
}

/* -----------------------------------------------------------------------------
   Typography
   ----------------------------------------------------------------------------- */
h1, h2, h3, h4, h5, h6 {
    color: var(--text-primary);
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.3;
    margin: 0 0 1rem 0;
}

h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-base); }

p {
    color: var(--text-secondary);
    margin: 0 0 1rem 0;
}

a {
    color: var(--color-primary-light);
    text-decoration: none;
    transition: color var(--transition-fast);
}

a:hover {
    color: var(--color-primary);
}

/* -----------------------------------------------------------------------------
   Layout - Sidebar
   ----------------------------------------------------------------------------- */
.sidebar-bg {
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-primary);
}

.sidebar-text {
    color: var(--text-secondary);
    font-size: var(--text-sm);
    font-weight: 500;
}

.sidebar-hover:hover {
    background-color: var(--bg-card-hover);
    color: var(--text-primary);
}

.active-nav {
    background: var(--color-primary);
    color: var(--color-on-primary);
}

.nav-item {
    display: flex;
    align-items: center;
    padding: 0.75rem 1rem;
    margin: 0.25rem 0.75rem;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    transition: all var(--transition-fast);
    cursor: pointer;
}

.nav-item:hover {
    background-color: var(--bg-card-hover);
    color: var(--text-primary);
}

.nav-item.active {
    background: var(--color-primary);
    color: var(--color-on-primary);
}

/* ---- Travelling active indicator (§7) ----
   One shared element inside the scrollable nav content, positioned by
   js/nav-indicator.js via translateY + scaleY (compositor-friendly). */
.sidebar-nav-scroll {
    position: relative;
}

/* iOS Safari 26.5 UI-process crash (see below). This element is the highest-
   exposure instance in the app: js/nav-indicator.js arms a transform transition
   ~50ms after every page load, unprompted, on every authenticated page. */
.nav-indicator {
    position: absolute;
    top: 0;
    left: 0;
    width: 3px;
    height: 20px;
    border-radius: 0 4px 4px 0;
    background: var(--color-primary);
    opacity: 0;
    transform-origin: center top;
    pointer-events: none;

    /* MUST match the function list js/nav-indicator.js writes
       ('translateY(Npx) scaleY(S)'), in the same order and arity.
       Without it the computed base is `transform: none`, so the first
       transition asks WebKit to blend `none` against a two-function list.
       That is the blend that segfaults MobileSafari — WebCore::blend() over
       TransformOperations, running as an accelerated effect in the UI process,
       null-derefs on the operation that has no counterpart. Keep the two in
       step: changing the JS to emit a different list means changing this. */
    transform: translateY(0) scaleY(1);

    /* `view-transition-name` was removed 2026-09-18. It was dead once the
       @view-transition opt-in below was commented out, but it still forced this
       element onto its own compositing layer, which is what made its animation
       eligible for the accelerated path that crashes. */
}

.nav-indicator--visible {
    opacity: 1;
}

/* Transitions arm only after the entrance position is painted */
.nav-indicator--armed {
    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms ease;
}

/* Active page (class applied by js/navbar.js) — the indicator is decorative,
   so the item itself must also change (SC 1.4.1) */
.nav-item.nav-item--active,
.nav-subitem.nav-item--active {
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
    color: var(--color-primary);
}

.nav-item.nav-item--active svg,
.nav-item.nav-item--active i,
.nav-subitem.nav-item--active svg {
    color: var(--color-primary);
}

/* Keyboard focus must stay distinct from the active indicator (§7.4) */
.nav-item:focus-visible,
.nav-subitem:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: -2px;
}

/* Cross-document travel (§7.2 layer 4) — Chromium animates the named indicator
   between page loads. The root crossfade is disabled so only the indicator moves.

   DISABLED 2026-09-12 while investigating iOS Safari crashes. The original note
   here said "other browsers ignore this at no cost" — that was true when written,
   but Safari 18.2 shipped cross-document view transitions, so this stopped being
   a no-op on iOS and started firing on every same-origin navigation. That timing
   matches a reproducible crash-on-every-page-load report.

   The feature is decorative (a sliding sidebar indicator between page loads);
   losing it costs nothing functional. Re-enable by uncommenting, but retest on
   a real iOS device first — and note there is no clean CSS capability test that
   distinguishes "supports this" from "supports this without crashing", so
   gating it by engine is not straightforward.

@view-transition {
    navigation: auto;
}
*/

::view-transition-group(root) {
    animation-duration: 0s;
}

/* This block used to cover ::view-transition-* only, which meant Reduce Motion
   silenced a feature that is now switched off anyway while leaving every real
   animation in the app running. Widened 2026-09-18 — it is the correct WCAG
   2.3.3 behaviour, and it doubles as the escape hatch for the iOS Safari 26.5
   accelerated-transform crash: a user who turns on Reduce Motion stops
   generating the animations that trigger it. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation-duration: 0.01ms !important;
    }
}

.nav-item svg, .nav-item i {
    margin-right: 0.75rem;
    width: 20px;
    height: 20px;
}

/* Sidebar Logo */
.sidebar-logo {
    padding: 1.5rem;
    border-bottom: 1px solid var(--border-primary);
    margin-bottom: 1rem;
}

.sidebar-logo h1 {
    font-size: var(--text-xl);
    font-weight: 700;
    color: var(--color-primary);
    margin: 0;
}

/* -----------------------------------------------------------------------------
   Cards
   ----------------------------------------------------------------------------- */
.card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    transition: all var(--transition-normal);
}

.card:hover {
    border-color: var(--border-secondary);
    box-shadow: var(--shadow-md);
}

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

.card-title {
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.card-body {
    padding: 0;
}

/* Stat Cards */
.stat-card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    position: relative;
    overflow: hidden;
    transition: all var(--transition-normal);
}

.stat-card .stat-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 1rem;
}

.stat-card .stat-value {
    font-size: 2rem;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.2;
}

.stat-card .stat-label {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: 0.25rem;
}

.stat-positive {
    color: var(--color-success);
}

.stat-negative {
    color: var(--color-error);
}

.stat-change {
    display: inline-flex;
    align-items: center;
    font-size: var(--text-sm);
    font-weight: 500;
    margin-top: 0.5rem;
}

.stat-change.positive {
    color: var(--color-success);
}

.stat-change.negative {
    color: var(--color-error);
}

/* -----------------------------------------------------------------------------
   Buttons
   ----------------------------------------------------------------------------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.625rem 1.25rem;
    font-size: var(--text-sm);
    font-weight: 500;
    border-radius: var(--radius-md);
    border: none;
    cursor: pointer;
    /* Identity base matching .btn-primary:hover's translateY(-1px). `transition:
       all` sweeps transform in, and iOS fires sticky :hover on tap, so without
       this EVERY button tap runs the `none` -> list blend that segfaults
       MobileSafari. Set on .btn so all variants inherit it. See .nav-indicator. */
    transform: translateY(0);
    transition: all var(--transition-fast);
    gap: 0.5rem;
}

.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

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

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

.btn-secondary {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    border: 1px solid var(--border-control);
}

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

.btn-outline {
    background: transparent;
    color: var(--color-primary-light);
    border: 1px solid var(--color-primary);
}

.btn-outline:hover:not(:disabled) {
    background: var(--color-primary);
    color: var(--color-on-primary);
}

.btn-danger {
    background: var(--color-error);
    color: #ffffff;
}

.btn-danger:hover:not(:disabled) {
    background: #dc2626;
}

.btn-success {
    background: var(--color-success);
    color: #ffffff;
}

.btn-success:hover:not(:disabled) {
    background: #16a34a;
}

.btn-sm {
    padding: 0.375rem 0.75rem;
    font-size: var(--text-xs);
}

.btn-lg {
    padding: 0.875rem 1.75rem;
    font-size: var(--text-base);
}

.btn-icon {
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: var(--radius-md);
}

/* -----------------------------------------------------------------------------
   Forms
   ----------------------------------------------------------------------------- */
.form-group {
    margin-bottom: 1.25rem;
}

.form-label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
    margin-bottom: 0.5rem;
}

.form-input,
.form-select,
.form-textarea {
    width: 100%;
    padding: 0.75rem 1rem;
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--bg-input);
    /* --border-control, not --border-primary: WCAG 1.4.11 needs 3:1 for a
       control's boundary and the decorative token is 1.22:1. */
    border: 1px solid var(--border-control);
    border-radius: var(--radius-md);
    transition: all var(--transition-fast);
}

/* Native checkbox/radio boundaries are author-styled via accent-color in places,
   so pin their contrast explicitly too (1.4.11). */
input[type="checkbox"],
input[type="radio"] {
    border-color: var(--border-control);
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus {
    outline: none;
    background: var(--bg-input-focus);
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(37, 134, 58, 0.12);
}

.form-input::placeholder {
    color: var(--text-muted);
}

.form-input.error {
    border-color: var(--color-error);
}

.form-error {
    font-size: var(--text-xs);
    color: var(--color-error);
    margin-top: 0.375rem;
}

.form-hint {
    font-size: var(--text-xs);
    color: var(--text-tertiary);
    margin-top: 0.375rem;
}

/* Checkbox & Radio */
.form-checkbox,
.form-radio {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
}

.form-checkbox input,
.form-radio input {
    width: 18px;
    height: 18px;
    margin-right: 0.5rem;
    accent-color: var(--color-primary);
}

/* -----------------------------------------------------------------------------
   Tables
   ----------------------------------------------------------------------------- */
.table-container {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    /* clip (not hidden) keeps the radius clipping without becoming a
       scrollport, so the sticky header can engage against the page.
       Browsers without clip support fall back to hidden = previous look. */
    overflow: hidden;
    overflow: clip;
}

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

.table th {
    background: var(--bg-tertiary);
    padding: 0.625rem 1rem;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-tertiary);
    text-align: left;
    border-bottom: 1px solid var(--border-primary);
    position: sticky;
    top: 0;
    z-index: 1;
}

.table td {
    padding: 0.625rem 1rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

/* Subtle zebra replaces per-row borders */
.table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--bg-tertiary) 45%, transparent);
}

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

/* Sticky header must not obscure a row scrolled into view (SC 2.4.11) */
.table tbody tr {
    scroll-margin-top: 3rem;
}

.table-actions {
    display: flex;
    gap: 0.5rem;
}

/* Figures align in columns and stat tiles (P1 item 11) */
.table th,
.table td,
.stat-value {
    font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   Badges & Tags
   ----------------------------------------------------------------------------- */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.75rem;
    font-size: var(--text-xs);
    font-weight: 500;
    border-radius: var(--radius-full);
}

.badge-primary {
    background: rgba(37, 134, 58, 0.15);
    color: var(--color-primary-light);
}

.badge-success {
    background: var(--color-success-bg);
    color: var(--color-success);
}

.badge-warning {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.badge-error {
    background: var(--color-error-bg);
    color: var(--color-error);
}

.badge-info {
    background: var(--color-info-bg);
    color: var(--color-info);
}

/* Platform Badges */
.badge-ebay {
    background: rgba(229, 50, 56, 0.15);
    color: var(--color-ebay);
}

.badge-amazon {
    background: rgba(255, 153, 0, 0.15);
    color: var(--color-amazon);
}

.badge-tiktok {
    background: rgba(0, 242, 234, 0.15);
    color: var(--color-tiktok);
}

.badge-etsy {
    background: rgba(245, 100, 0, 0.15);
    color: var(--color-etsy);
}

.badge-royal-mail {
    background: rgba(230, 0, 18, 0.15);
    color: var(--color-royal-mail);
}

.badge-dpd {
    background: rgba(220, 0, 50, 0.15);
    color: var(--color-dpd);
}

.badge-ups {
    background: rgba(255, 181, 0, 0.18);
    color: var(--color-ups);
}

/* Status Badges */
.status-active {
    background: var(--color-success-bg);
    color: var(--color-success);
}

.status-pending {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.status-inactive {
    background: var(--color-error-bg);
    color: var(--color-error);
}

/* -----------------------------------------------------------------------------
   Modals
   ----------------------------------------------------------------------------- */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.7);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-normal);
}

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

.modal {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-xl);
    width: 100%;
    max-width: 500px;
    max-height: 90vh;
    overflow: auto;
    box-shadow: var(--shadow-xl);
    transform: scale(0.95) translateY(20px);
    transition: all var(--transition-normal);
}

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

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

.modal-title {
    font-size: var(--text-xl);
    font-weight: 600;
    margin: 0;
}

.modal-close {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-tertiary);
    border: none;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.modal-close:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.modal-body {
    padding: 1.5rem;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 0.75rem;
    padding: 1.5rem;
    border-top: 1px solid var(--border-primary);
}

/* Set while a confirm dialog is open, so the page behind cannot scroll. */
body.modal-open {
    overflow: hidden;
}

/* -----------------------------------------------------------------------------
   Confirm Dialog (js/confirm-dialog.js — window.showConfirm)
   Narrower than a form modal, and led by a tinted tone icon instead of a
   title bar: the question is the content, so it gets no separate header rule.
   ----------------------------------------------------------------------------- */
.modal--confirm {
    max-width: 460px;
}

.confirm-dialog__header {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.5rem 1.5rem 0.25rem;
}

.confirm-dialog__icon {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-lg);
}

.confirm-dialog__icon svg {
    width: 22px;
    height: 22px;
}

.confirm-dialog__icon--danger {
    background: var(--color-error-bg);
    color: var(--color-error);
}

.confirm-dialog__icon--warning {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.confirm-dialog__icon--info {
    background: var(--color-info-bg);
    color: var(--color-info);
}

.confirm-dialog__icon--primary {
    background: var(--color-success-bg);
    color: var(--color-primary);
}

.confirm-dialog__heading {
    min-width: 0;
}

.confirm-dialog__message {
    margin: 0.375rem 0 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.confirm-dialog__body {
    padding: 1rem 1.5rem 0.5rem;
    /* Align the detail panel under the copy, not under the icon. */
    padding-left: calc(1.5rem + 44px + 1rem);
}

.confirm-dialog__detail {
    margin: 0;
    padding: 0.75rem 0.875rem;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--text-tertiary);
}

.modal--confirm .modal-footer {
    padding-top: 1.25rem;
    border-top: none;
}

@media (max-width: 480px) {
    .confirm-dialog__body {
        padding-left: 1.5rem;
    }
}

/* View Modal Detail Rows */
.view-detail-row {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}

.view-detail-label {
    font-size: 0.6875rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

.view-detail-value {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-primary);
}

/* Action Buttons in Table Rows */
.action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    background: transparent;
    cursor: pointer;
    transition: all var(--transition-fast);
    color: var(--text-tertiary);
}

.action-btn:hover {
    color: var(--text-primary);
    background: var(--bg-tertiary);
    border-color: var(--border-primary);
}

.action-btn-view:hover {
    color: var(--color-primary);
    background: rgba(37, 134, 58, 0.08);
    border-color: rgba(37, 134, 58, 0.2);
}

.action-btn-edit:hover {
    color: var(--color-warning);
    background: var(--color-warning-bg);
    border-color: rgba(217, 119, 6, 0.2);
}

.action-btn-delete:hover {
    color: var(--color-error);
    background: var(--color-error-bg);
    border-color: rgba(220, 38, 38, 0.2);
}

/* -----------------------------------------------------------------------------
   Alerts & Notifications
   ----------------------------------------------------------------------------- */
.alert {
    display: flex;
    align-items: flex-start;
    padding: 1rem 1.25rem;
    border-radius: var(--radius-md);
    margin-bottom: 1rem;
    gap: 0.75rem;
}

.alert-icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
}

.alert-content {
    flex: 1;
}

.alert-title {
    font-weight: 600;
    margin-bottom: 0.25rem;
}

.alert-message {
    font-size: var(--text-sm);
    opacity: 0.9;
}

.alert-success {
    background: var(--color-success-bg);
    border: 1px solid rgba(34, 197, 94, 0.3);
    color: var(--color-success);
}

.alert-warning {
    background: var(--color-warning-bg);
    border: 1px solid rgba(245, 158, 11, 0.3);
    color: var(--color-warning);
}

.alert-error {
    background: var(--color-error-bg);
    border: 1px solid rgba(239, 68, 68, 0.3);
    color: var(--color-error);
}

.alert-info {
    background: var(--color-info-bg);
    border: 1px solid rgba(59, 130, 246, 0.3);
    color: var(--color-info);
}

/* Toast Notifications */
.toast-container {
    position: fixed;
    top: 1rem;
    right: 1rem;
    z-index: 1100;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.toast {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    padding: 1rem 1.25rem;
    min-width: 300px;
    max-width: 400px;
    box-shadow: var(--shadow-lg);
    animation: slideIn var(--transition-normal) ease;
}

@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* -----------------------------------------------------------------------------
   Loading States
   ----------------------------------------------------------------------------- */
.spinner {
    width: 24px;
    height: 24px;
    border: 3px solid var(--border-primary);
    border-top-color: var(--color-primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

.spinner-sm {
    width: 16px;
    height: 16px;
    border-width: 2px;
}

.spinner-lg {
    width: 40px;
    height: 40px;
    border-width: 4px;
}

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

/* Skeleton placeholders for initial loads (P2 item 14) — spinners remain for
   user-triggered actions (uploads, modal fetches). */
.skeleton-stack {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    padding: 0.75rem 0;
    text-align: left;
}

.skeleton-line {
    height: 0.875rem;
    border-radius: var(--radius-sm);
}

.skeleton-stack .skeleton-line:nth-child(2) { width: 85%; }
.skeleton-stack .skeleton-line:nth-child(3) { width: 70%; }
.skeleton-stack .skeleton-line:nth-child(4) { width: 90%; }

.skeleton {
    background: linear-gradient(
        90deg,
        var(--bg-tertiary) 0%,
        var(--bg-card-hover) 50%,
        var(--bg-tertiary) 100%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
    border-radius: var(--radius-sm);
}

@keyframes shimmer {
    0% {
        background-position: 200% 0;
    }
    100% {
        background-position: -200% 0;
    }
}

/* -----------------------------------------------------------------------------
   Empty States
   ----------------------------------------------------------------------------- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 4rem 2rem;
    text-align: center;
}

.empty-state-icon {
    width: 64px;
    height: 64px;
    margin-bottom: 1.5rem;
    color: var(--text-tertiary);
}

.empty-state-title {
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 0.5rem;
}

.empty-state-description {
    color: var(--text-secondary);
    max-width: 400px;
    margin-bottom: 1.5rem;
}

/* -----------------------------------------------------------------------------
   Search & Filters
   ----------------------------------------------------------------------------- */
.search-bar {
    position: relative;
    max-width: 400px;
}

.search-bar input {
    width: 100%;
    padding: 0.75rem 1rem 0.75rem 2.75rem;
    background: var(--bg-input);
    border: 1px solid var(--border-control);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    font-size: var(--text-sm);
    transition: all var(--transition-fast);
}

.search-bar input:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(37, 134, 58, 0.12);
}

.search-bar svg {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    width: 18px;
    height: 18px;
    color: var(--text-tertiary);
}

.filter-bar {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

/* -----------------------------------------------------------------------------
   Pagination
   ----------------------------------------------------------------------------- */
.pagination {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.pagination-btn {
    min-width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-control);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.pagination-btn:hover:not(:disabled) {
    background: var(--bg-card-hover);
    border-color: var(--border-secondary);
    color: var(--text-primary);
}

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

.pagination-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* -----------------------------------------------------------------------------
   Dropdown Menu
   ----------------------------------------------------------------------------- */
.dropdown {
    position: relative;
    display: inline-block;
}

.dropdown-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    min-width: 180px;
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: all var(--transition-fast);
    z-index: 100;
}

.dropdown.active .dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.dropdown-item:first-child {
    border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.dropdown-item:last-child {
    border-radius: 0 0 var(--radius-md) var(--radius-md);
}

.dropdown-item:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.dropdown-divider {
    height: 1px;
    background: var(--border-primary);
    margin: 0.5rem 0;
}

/* -----------------------------------------------------------------------------
   Tabs
   ----------------------------------------------------------------------------- */
.tabs {
    display: flex;
    border-bottom: 1px solid var(--border-primary);
    margin-bottom: 1.5rem;
    gap: 0.5rem;
}

.tab {
    padding: 0.75rem 1.25rem;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-tertiary);
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.tab:hover {
    color: var(--text-secondary);
}

.tab.active {
    color: var(--color-primary-light);
    border-bottom-color: var(--color-primary);
}

/* -----------------------------------------------------------------------------
   Progress Bars
   ----------------------------------------------------------------------------- */
.progress {
    width: 100%;
    height: 8px;
    background: var(--bg-tertiary);
    border-radius: var(--radius-full);
    overflow: hidden;
}

.progress-bar {
    height: 100%;
    background: var(--color-primary);
    border-radius: var(--radius-full);
    transition: width var(--transition-slow);
}

.progress-sm {
    height: 4px;
}

.progress-lg {
    height: 12px;
}

/* -----------------------------------------------------------------------------
   Avatars
   ----------------------------------------------------------------------------- */
.avatar {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-full);
    object-fit: cover;
    border: 2px solid var(--border-primary);
}

.avatar-sm {
    width: 32px;
    height: 32px;
}

.avatar-lg {
    width: 56px;
    height: 56px;
}

.avatar-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-primary);
    color: var(--color-on-primary);
    font-weight: 600;
    font-size: var(--text-sm);
}

/* Avatar Group */
.avatar-group {
    display: flex;
}

.avatar-group .avatar {
    margin-left: -0.75rem;
}

.avatar-group .avatar:first-child {
    margin-left: 0;
}

/* -----------------------------------------------------------------------------
   Tooltips
   ----------------------------------------------------------------------------- */
[data-tooltip] {
    position: relative;
}

[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 0.5rem);
    left: 50%;
    transform: translateX(-50%);
    padding: 0.5rem 0.75rem;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    color: var(--text-primary);
    white-space: nowrap;
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-fast);
    z-index: 200;
}

[data-tooltip]:hover::after {
    opacity: 1;
    visibility: visible;
}

/* -----------------------------------------------------------------------------
   Auth Pages (Login/Signup)
   ----------------------------------------------------------------------------- */
.auth-container {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-primary);
    padding: 2rem;
}

.auth-card {
    width: 100%;
    max-width: 420px;
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-xl);
    padding: 2.5rem;
    box-shadow: var(--shadow-xl);
}

.auth-header {
    text-align: center;
    margin-bottom: 2rem;
}

.auth-logo {
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--color-primary);
    margin-bottom: 0.5rem;
}

.auth-subtitle {
    color: var(--text-secondary);
    font-size: var(--text-sm);
}

.auth-divider {
    display: flex;
    align-items: center;
    margin: 1.5rem 0;
    color: var(--text-tertiary);
    font-size: var(--text-xs);
}

.auth-divider::before,
.auth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border-primary);
}

.auth-divider span {
    padding: 0 1rem;
}

.auth-footer {
    text-align: center;
    margin-top: 1.5rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.auth-footer a {
    color: var(--color-primary-light);
    font-weight: 500;
}

/* Social Login Buttons */
.social-btn {
    width: 100%;
    padding: 0.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.social-btn:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-secondary);
}

/* -----------------------------------------------------------------------------
   Dashboard Specific
   ----------------------------------------------------------------------------- */
.dashboard-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 2rem;
}

.dashboard-title {
    font-family: var(--font-display);
    font-size: 1.75rem;
    font-weight: 700;
}

.dashboard-subtitle {
    color: var(--text-secondary);
    font-size: var(--text-sm);
}

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1.5rem;
    margin-bottom: 2rem;
}

/* Dashboard hierarchy (P2 item 13): the first card is the hero metric.
   Position-based on purpose — the widgets are user-reorderable, so whichever
   card is dragged first inherits the emphasis. */
.stats-grid--hero {
    grid-template-columns: 2fr 1fr 1fr 1fr;
}

.stats-grid--hero > .stat-card:first-child .stat-value {
    font-size: var(--text-4xl);
}

/* Quick Actions */
.quick-actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* Recent Activity */
.activity-list {
    display: flex;
    flex-direction: column;
}

.activity-item {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1rem 0;
    border-bottom: 1px solid var(--border-primary);
}

.activity-item:last-child {
    border-bottom: none;
}

.activity-icon {
    width: 36px;
    height: 36px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.activity-content {
    flex: 1;
}

.activity-title {
    font-size: var(--text-sm);
    color: var(--text-primary);
    margin-bottom: 0.25rem;
}

.activity-time {
    font-size: var(--text-xs);
    color: var(--text-tertiary);
}

/* -----------------------------------------------------------------------------
   Inventory Specific
   ----------------------------------------------------------------------------- */
.inventory-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.inventory-filters {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

.inventory-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1.5rem;
}

.product-card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    overflow: hidden;
    transition: all var(--transition-normal);
}

.product-card:hover {
    border-color: var(--border-secondary);
    box-shadow: var(--shadow-md);
}

.product-image {
    width: 100%;
    height: 200px;
    object-fit: cover;
    background: var(--bg-tertiary);
}

.product-body {
    padding: 1.25rem;
}

.product-title {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 0.5rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.product-sku {
    font-size: var(--text-xs);
    color: var(--text-tertiary);
    margin-bottom: 0.75rem;
}

.product-price {
    font-size: var(--text-xl);
    font-weight: 700;
    color: var(--color-primary-light);
}

.product-stock {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-sm);
}

.product-platforms {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

/* -----------------------------------------------------------------------------
   Orders Specific
   ----------------------------------------------------------------------------- */
.order-status {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.75rem;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 500;
}

.order-status-pending {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.order-status-processing {
    background: var(--color-info-bg);
    color: var(--color-info);
}

.order-status-shipped {
    background: rgba(37, 134, 58, 0.15);
    color: var(--color-primary-light);
}

.order-status-delivered {
    background: var(--color-success-bg);
    color: var(--color-success);
}

.order-status-back-order {
    background: rgba(249, 115, 22, 0.15);
    color: #f97316;
}

.order-status-cancelled {
    background: var(--color-error-bg);
    color: var(--color-error);
}

/* -----------------------------------------------------------------------------
   Utility Classes
   -----------------------------------------------------------------------------
   PARTLY WRAPPED IN :where() ON 2026-09-10 — do not unwrap those.

   Two kinds of name live in this block and they are treated differently.

   1. LAYOUT AND SPACING (.flex, .flex-col, .hidden, .grid, .items-center,
      .gap-*, .m*-*, .p*-*, .rounded*, .relative ...) duplicate Tailwind's (.flex, .flex-col, .hidden, .grid,
   .items-center, .gap-4, .p-4 ...). site.css loads AFTER tailwind.css, and a
   media query adds no specificity, so a plain `.flex-col` here beat
   `@media(min-width:640px){.sm\:flex-row}` and every other responsive variant
   of a shadowed name. Symptom: `class="flex flex-col sm:flex-row"` stayed a
   column at every width. index.pug had already worked around two instances by
   hand (.sk-desktop-only, .sk-cta-row) before the cause was found.

      :where() gives these zero specificity, so ANY Tailwind rule — base or
      responsive — now wins, while the declarations still apply on their own.
      That keeps the fallback these were presumably written for (the app
      rendering sanely if the Tailwind build is missing) without them fighting
      it when it is present. Deleting the block would have removed the fallback;
      demoting it does not.

   2. BRAND COLOUR AND TYPE (.text-primary, .text-secondary, .bg-card,
      .font-bold ...) are Stockaroo's own names. Tailwind's default theme does
      not generate them, so they never collided and they are NOT demoted. They
      must stay at normal specificity: site.css sets colour on `p`, `a` and
      `h1-h6` at element level, and a zero-specificity `.text-tertiary` would
      lose to those — `<p class="text-tertiary">` would silently render as
      --text-secondary.

   Which names fall in group 1 was decided by checking each against the built
   tailwind.css rather than by eye. If you add a utility here, do the same:
   demote it only if Tailwind also emits that class.
   ----------------------------------------------------------------------------- */
:where(.text-center) { text-align: center; }
:where(.text-right) { text-align: right; }
:where(.text-left) { text-align: left; }

.text-primary { color: var(--text-primary); }
.text-secondary { color: var(--text-secondary); }
.text-tertiary { color: var(--text-tertiary); }
.text-muted { color: var(--text-muted); }

.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-error { color: var(--color-error); }
.text-info { color: var(--color-info); }

.bg-primary { background-color: var(--bg-primary); }
.bg-secondary { background-color: var(--bg-secondary); }
.bg-card { background-color: var(--bg-card); }

:where(.flex) { display: flex; }
:where(.flex-col) { flex-direction: column; }
:where(.flex-wrap) { flex-wrap: wrap; }
:where(.items-center) { align-items: center; }
:where(.items-start) { align-items: flex-start; }
:where(.items-end) { align-items: flex-end; }
:where(.justify-center) { justify-content: center; }
:where(.justify-between) { justify-content: space-between; }
:where(.justify-end) { justify-content: flex-end; }
:where(.gap-1) { gap: 0.25rem; }
:where(.gap-2) { gap: 0.5rem; }
:where(.gap-3) { gap: 0.75rem; }
:where(.gap-4) { gap: 1rem; }
:where(.gap-6) { gap: 1.5rem; }

:where(.w-full) { width: 100%; }
:where(.h-full) { height: 100%; }
:where(.min-h-screen) { min-height: 100vh; }

:where(.hidden) { display: none; }
:where(.block) { display: block; }
.inline-block { display: inline-block; }
:where(.inline-flex) { display: inline-flex; }
:where(.grid) { display: grid; }

:where(.overflow-hidden) { overflow: hidden; }
.overflow-auto { overflow: auto; }

:where(.relative) { position: relative; }
:where(.absolute) { position: absolute; }
:where(.fixed) { position: fixed; }
.sticky { position: sticky; }

:where(.rounded) { border-radius: var(--radius-md); }
:where(.rounded-lg) { border-radius: var(--radius-lg); }
:where(.rounded-xl) { border-radius: var(--radius-xl); }
:where(.rounded-full) { border-radius: var(--radius-full); }

:where(.shadow) { box-shadow: var(--shadow-md); }
:where(.shadow-lg) { box-shadow: var(--shadow-lg); }
.shadow-xl { box-shadow: var(--shadow-xl); }

:where(.cursor-pointer) { cursor: pointer; }
.select-none { user-select: none; }
:where(.truncate) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Spacing */
:where(.m-0) { margin: 0; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-3 { margin: 0.75rem; }
.m-4 { margin: 1rem; }
.m-6 { margin: 1.5rem; }
.m-8 { margin: 2rem; }

.mt-0 { margin-top: 0; }
:where(.mt-1) { margin-top: 0.25rem; }
:where(.mt-2) { margin-top: 0.5rem; }
:where(.mt-3) { margin-top: 0.75rem; }
:where(.mt-4) { margin-top: 1rem; }
:where(.mt-6) { margin-top: 1.5rem; }
:where(.mt-8) { margin-top: 2rem; }

.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 0.25rem; }
:where(.mb-2) { margin-bottom: 0.5rem; }
:where(.mb-3) { margin-bottom: 0.75rem; }
:where(.mb-4) { margin-bottom: 1rem; }
:where(.mb-6) { margin-bottom: 1.5rem; }
:where(.mb-8) { margin-bottom: 2rem; }

.ml-0 { margin-left: 0; }
:where(.ml-1) { margin-left: 0.25rem; }
:where(.ml-2) { margin-left: 0.5rem; }
:where(.ml-3) { margin-left: 0.75rem; }
.ml-4 { margin-left: 1rem; }

.mr-0 { margin-right: 0; }
:where(.mr-1) { margin-right: 0.25rem; }
:where(.mr-2) { margin-right: 0.5rem; }
:where(.mr-3) { margin-right: 0.75rem; }
:where(.mr-4) { margin-right: 1rem; }

:where(.p-0) { padding: 0; }
:where(.p-1) { padding: 0.25rem; }
:where(.p-2) { padding: 0.5rem; }
:where(.p-3) { padding: 0.75rem; }
:where(.p-4) { padding: 1rem; }
:where(.p-6) { padding: 1.5rem; }
:where(.p-8) { padding: 2rem; }

.px-1 { padding-left: 0.25rem; padding-right: 0.25rem; }
:where(.px-2) { padding-left: 0.5rem; padding-right: 0.5rem; }
:where(.px-3) { padding-left: 0.75rem; padding-right: 0.75rem; }
:where(.px-4) { padding-left: 1rem; padding-right: 1rem; }
:where(.px-6) { padding-left: 1.5rem; padding-right: 1.5rem; }

:where(.py-1) { padding-top: 0.25rem; padding-bottom: 0.25rem; }
:where(.py-2) { padding-top: 0.5rem; padding-bottom: 0.5rem; }
:where(.py-3) { padding-top: 0.75rem; padding-bottom: 0.75rem; }
:where(.py-4) { padding-top: 1rem; padding-bottom: 1rem; }
:where(.py-6) { padding-top: 1.5rem; padding-bottom: 1.5rem; }

/* -----------------------------------------------------------------------------
   Animations
   ----------------------------------------------------------------------------- */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

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

.animate-fade-in {
    animation: fadeIn var(--transition-normal) ease;
}

.animate-fade-in-up {
    animation: fadeInUp var(--transition-normal) ease;
}

.animate-pulse {
    animation: pulse 2s infinite;
}

/* -----------------------------------------------------------------------------
   Responsive Design
   ----------------------------------------------------------------------------- */
@media (max-width: 1024px) {
    .stats-grid,
    .stats-grid--hero {
        grid-template-columns: repeat(2, 1fr);
    }

    .stats-grid--hero > .stat-card:first-child {
        grid-column: span 2;
    }

    .inventory-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 768px) {
    :root {
        --sidebar-width: 100%;
    }

    .stats-grid,
    .stats-grid--hero {
        grid-template-columns: 1fr;
    }

    .stats-grid--hero > .stat-card:first-child {
        grid-column: auto;
    }

    .inventory-grid {
        grid-template-columns: 1fr;
    }

    .inventory-header {
        flex-direction: column;
        align-items: stretch;
    }

    .dashboard-header {
        flex-direction: column;
        align-items: flex-start;
        gap: 1rem;
    }

    .table-container {
        overflow-x: auto;
    }

    .modal {
        margin: 1rem;
        max-width: calc(100% - 2rem);
    }

    .auth-card {
        padding: 1.5rem;
    }
}

@media (max-width: 480px) {
    h1 { font-size: 1.75rem; }
    h2 { font-size: var(--text-2xl); }
    h3 { font-size: var(--text-xl); }

    .btn {
        width: 100%;
    }

    .quick-actions {
        flex-direction: column;
    }

    .filter-bar {
        flex-direction: column;
        width: 100%;
    }

    .filter-bar > * {
        width: 100%;
    }
}

/* -----------------------------------------------------------------------------
   Theme Toggle Component
   ----------------------------------------------------------------------------- */
.theme-toggle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    cursor: pointer;
    /* Base transform matching the inline `scale(0.95)` that js/theme.js and
       js/navbar.js set on click — `transition: all` sweeps transform in, so
       without this the click animates `none` -> `scale()`. See .nav-indicator. */
    transform: scale(1);
    transition: all var(--transition-fast);
}

.theme-toggle:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-secondary);
}

.theme-toggle-switch {
    position: relative;
    width: 44px;
    height: 24px;
    background: var(--border-secondary);
    border-radius: var(--radius-full);
    transition: all var(--transition-fast);
}

.theme-toggle-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    background: white;
    border-radius: 50%;
    transition: all var(--transition-fast);
    box-shadow: var(--shadow-sm);
}

[data-theme="dark"] .theme-toggle-switch,
.dark-mode .theme-toggle-switch {
    background: var(--color-primary);
}

[data-theme="dark"] .theme-toggle-switch::after,
.dark-mode .theme-toggle-switch::after {
    left: 22px;
}

.theme-toggle-icon {
    width: 18px;
    height: 18px;
    color: var(--text-secondary);
    transition: all var(--transition-fast);
}

.theme-toggle-icon.sun {
    display: block;
}

.theme-toggle-icon.moon {
    display: none;
}

[data-theme="dark"] .theme-toggle-icon.sun,
.dark-mode .theme-toggle-icon.sun {
    display: none;
}

[data-theme="dark"] .theme-toggle-icon.moon,
.dark-mode .theme-toggle-icon.moon {
    display: block;
}

.theme-toggle-label {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
}

/* Compact theme toggle for navbar */
.theme-toggle-compact {
    width: 36px;
    height: 36px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-control);
    border-radius: var(--radius-md);
    cursor: pointer;
    /* Matches the inline scale(0.95) set on click — see .theme-toggle above. */
    transform: scale(1);
    transition: all var(--transition-fast);
}

.theme-toggle-compact:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-secondary);
}

.theme-toggle-compact svg {
    width: 20px;
    height: 20px;
    color: var(--text-secondary);
}

/* Light mode: show sun, hide moon */
.theme-toggle-compact .sun-icon { display: block; }
.theme-toggle-compact .moon-icon { display: none; }

/* Dark mode: show moon, hide sun */
[data-theme="dark"] .theme-toggle-compact .sun-icon { display: none; }
[data-theme="dark"] .theme-toggle-compact .moon-icon { display: block; }

/* -----------------------------------------------------------------------------
   eBay Profit Calculator (Sidebar Widget)
   ----------------------------------------------------------------------------- */
.ebay-calc-field {
    margin-bottom: 0.5rem;
}

.ebay-calc-label {
    display: block;
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--text-tertiary);
    margin-bottom: 0.2rem;
}

.ebay-calc-input-group {
    display: flex;
    align-items: center;
}

.ebay-calc-prefix {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-tertiary);
    padding: 0.35rem 0.5rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-primary);
    border-right: none;
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    line-height: 1;
}

.ebay-calc-input {
    width: 100%;
    padding: 0.35rem 0.5rem;
    font-size: var(--text-xs);
    color: var(--text-primary);
    background: var(--bg-input);
    border: 1px solid var(--border-control);
    border-radius: var(--radius-sm);
    transition: border-color var(--transition-fast);
}

.ebay-calc-input:focus {
    outline: none;
    border-color: var(--border-focus);
}

.ebay-calc-input-group .ebay-calc-input {
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.ebay-calc-reset {
    display: block;
    background: none;
    border: none;
    font-size: 0.6875rem;
    color: var(--text-tertiary);
    cursor: pointer;
    padding: 0.25rem 0;
    margin-bottom: 0.5rem;
}

.ebay-calc-reset:hover {
    text-decoration: underline;
    color: var(--text-secondary);
}

.ebay-calc-results {
    border-top: 1px solid var(--border-primary);
    padding-top: 0.5rem;
}

.ebay-calc-result-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: var(--text-xs);
    color: var(--text-secondary);
    padding: 0.2rem 0;
}

.ebay-calc-result-value {
    font-weight: 600;
    color: var(--text-primary);
}

/* ---------------------------------------------------------------------------
   Onboarding lock — hide the sidebar chrome so the wizard is the only surface
   while a new tenant still has no marketplace connected. Applied via the
   `nav-hidden` body class (set from `hideNav` in template-auth.pug). The
   sidebar reappears once a marketplace is connected.
   --------------------------------------------------------------------------- */
.nav-hidden #sidebar,
.nav-hidden #sidebarToggle,
.nav-hidden #sidebarOverlay {
    display: none !important;
}

.nav-hidden main.ml-64 {
    margin-left: 0 !important;
}

/* WCAG 1.4.10 Reflow. `ml-64` is a fixed 16rem offset clearing the desktop
   sidebar. The sidebar itself already collapses behind #sidebarToggle below
   this breakpoint, but the offset on <main> was never wired to it, so at 320px
   the content sat 256px off-screen. Matches the lg: breakpoint the toggle and
   overlay use, so the offset disappears exactly when the sidebar does. */
@media (max-width: 1023px) {
    main.ml-64 {
        margin-left: 0;
    }
}

/* -----------------------------------------------------------------------------
   Skip link (WCAG 2.4.1)
   Off-screen until focused, then pinned top-left over the sidebar.
   ----------------------------------------------------------------------------- */
.skip-link {
    position: absolute;
    top: -100px;
    left: 1rem;
    z-index: 1100;
    padding: 0.75rem 1.25rem;
    background: var(--bg-card);
    color: var(--text-primary);
    border: 2px solid var(--color-primary);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    font-weight: 600;
    text-decoration: none;
    box-shadow: var(--shadow-lg);
    transition: top var(--transition-fast);
}

.skip-link:focus {
    top: 1rem;
}

/* <main> takes focus from the skip link but is not a control — suppress the
   focus ring there only, so 2.4.7 is unaffected for real interactive elements. */
main:focus {
    outline: none;
}

/* -----------------------------------------------------------------------------
   Auth split-panel (login ⇄ signup) — see docs/UI_REVAMP_PLAN.md §6.
   Base styles are the no-JS / mobile fallback: one plain card at a time,
   toggle links navigate. js/auth-split.js adds .js-split, and ≥768px that
   enables the two-pane layout with the sliding overlay.
   ----------------------------------------------------------------------------- */
.auth-bg {
    position: fixed;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: -1;
}

.auth-bg__blob {
    position: absolute;
    border-radius: var(--radius-full);
}

.auth-bg__blob--a {
    top: -10rem;
    right: -10rem;
    width: 20rem;
    height: 20rem;
    background: radial-gradient(circle, rgba(37, 134, 58, 0.16) 0%, transparent 70%);
}

.auth-bg__blob--b {
    bottom: -10rem;
    left: -10rem;
    width: 24rem;
    height: 24rem;
    background: radial-gradient(circle, rgba(27, 106, 44, 0.12) 0%, transparent 70%);
}

.auth-title {
    font-size: var(--text-2xl);
    margin: 0 0 0.375rem 0;
}

.auth-split {
    position: relative;
    width: 100%;
    max-width: 26.25rem;
    margin: 0 auto;
}

.auth-split.is-signup {
    max-width: 32rem;
}

/* One pane at a time in the fallback layout */
.auth-split__pane {
    display: none;
}

.auth-split:not(.is-signup) .auth-split__pane--signin,
.auth-split.is-signup .auth-split__pane--signup {
    display: block;
}

.auth-split__card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-xl);
    padding: 2.5rem;
    box-shadow: var(--shadow-xl);
}

.auth-split__overlay {
    display: none;
}

/* -----------------------------------------------------------------------------
   Second factor (sign-in step 2)

   This step interrupts someone who is trying to get on with their day, so it is
   built to be finished in one glance: one instruction, one field, and the ways
   out kept small and underneath. The field is sized to six digits rather than to
   the card, both because a full-width box invites the wrong input and because
   the shape of the field is itself a hint about what belongs in it.
   -------------------------------------------------------------------------- */

.auth-code__lead {
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-secondary);
}

.auth-code__input {
    width: 100%;
    max-width: 11rem;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 1.5rem;
    letter-spacing: 0.4em;
    /* letter-spacing adds a trailing gap after the last glyph, which reads as
       off-centre unless it is compensated for. */
    text-indent: 0.4em;
    text-align: center;
}

.auth-code__input::placeholder {
    color: var(--text-muted);
    opacity: 0.5;
    letter-spacing: 0.4em;
}

.auth-code__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
    font-size: 13px;
}

/* The escape hatches. Deliberately quieter than the primary action: they are
   there for the minority who need them, not choices to weigh up. */
.auth-code__link {
    background: none;
    border: 0;
    padding: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-primary);
    cursor: pointer;
}

.auth-code__link:hover {
    text-decoration: underline;
}

.auth-code__link[hidden],
.auth-code__wait[hidden] {
    display: none;
}

.auth-code__wait {
    font-size: 13px;
    color: var(--text-tertiary);
}

@media (min-width: 768px) {
    .auth-split.js-split {
        max-width: 64rem;
        min-height: 30rem;
        background: var(--bg-card);
        border: 1px solid var(--border-primary);
        border-radius: var(--radius-xl);
        box-shadow: var(--shadow-xl);
        overflow: hidden;
        transition: height 450ms cubic-bezier(0.4, 0, 0.2, 1);
    }

    .auth-split.js-split.is-signup {
        max-width: 64rem;
    }

    .js-split .auth-split__pane {
        display: block;
        position: absolute;
        top: 0;
        width: 50%;
        padding: 2.5rem;
        opacity: 1;
        transition: opacity 300ms ease;
    }

    .js-split .auth-split__pane--signin { left: 0; }
    .js-split .auth-split__pane--signup { left: 50%; }

    /* The hidden pane cross-fades out; visibility also removes it from the
       tab order as a belt-and-braces alongside the inert attribute (§6.3). */
    .js-split:not(.is-signup) .auth-split__pane--signup,
    .js-split.is-signup .auth-split__pane--signin {
        opacity: 0;
        visibility: hidden;
        transition: opacity 300ms ease, visibility 0s linear 300ms;
    }

    /* The container is the card in split mode; the inner card chrome goes flat */
    .js-split .auth-split__card {
        background: transparent;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
    }

    .js-split .auth-split__overlay {
        display: block;
        position: absolute;
        top: 0;
        left: 50%;
        width: 50%;
        height: 100%;
        z-index: 2;
        background: var(--color-primary);
        color: var(--color-on-primary);
        /* Matches the translateX(-100%) in .is-signup below — see
           .nav-indicator for why a base transform is required here. */
        transform: translateX(0);
        transition: transform 450ms cubic-bezier(0.4, 0, 0.2, 1);
    }

    .js-split.is-signup .auth-split__overlay {
        transform: translateX(-100%);
    }

    .auth-split__overlay-panel {
        position: absolute;
        inset: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        text-align: center;
        padding: 2.5rem;
        opacity: 0;
        visibility: hidden;
        transition: opacity 300ms ease, visibility 0s linear 300ms;
    }

    .js-split:not(.is-signup) .auth-split__overlay-panel--to-signup,
    .js-split.is-signup .auth-split__overlay-panel--to-signin {
        opacity: 1;
        visibility: visible;
        transition: opacity 300ms ease;
    }
}

.auth-overlay-title {
    color: inherit;
    font-size: var(--text-2xl);
    margin: 0 0 0.75rem 0;
}

.auth-overlay-copy {
    color: inherit;
    opacity: 0.85;
    font-size: 0.9375rem;
    max-width: 20rem;
    margin: 0 0 1.75rem 0;
}

.auth-overlay-btn {
    display: inline-block;
    padding: 0.625rem 1.75rem;
    border: 1.5px solid currentColor;
    border-radius: var(--radius-md);
    color: inherit;
    font-weight: 600;
    font-size: 0.9375rem;
    transition: background-color var(--transition-fast);
}

.auth-overlay-btn:hover {
    color: inherit;
    background-color: color-mix(in srgb, var(--color-on-primary) 14%, transparent);
}

.auth-overlay-btn:focus-visible {
    outline: 2px solid var(--color-on-primary);
    outline-offset: 3px;
}

/* -----------------------------------------------------------------------------
   Token utilities (P1 item 9) — named single-purpose classes that replace the
   inline style="…" attributes the views accumulated. One token per class so a
   token change propagates. Platform tints keep their exact literal values:
   swatches are decorative (§5.2) and must not shift with the accessible text
   variants of the platform tokens.
   ----------------------------------------------------------------------------- */
.tk-text-primary { color: var(--text-primary); }
.tk-text-secondary { color: var(--text-secondary); }
.tk-text-tertiary { color: var(--text-tertiary); }
.tk-text-muted { color: var(--text-muted); }
.tk-text-success { color: var(--color-success); }
.tk-text-error { color: var(--color-error); }
.tk-text-warning { color: var(--color-warning); }
.tk-text-info { color: var(--color-info); }
.tk-text-brand { color: var(--color-primary-light); }
.tk-text-accent { color: var(--color-accent); }

.tk-bg-tertiary { background: var(--bg-tertiary); }
.tk-bg-success-tint { background: var(--color-success-bg); }
.tk-bg-warning-tint { background: var(--color-warning-bg); }
.tk-bg-error-tint { background: var(--color-error-bg); }
.tk-bg-info-tint { background: var(--color-info-bg); }

.tk-panel { background: var(--bg-tertiary); border: 1px solid var(--border-primary); }
.tk-border { border: 1px solid var(--border-primary); }
.tk-border-top { border-top: 1px solid var(--border-primary); }
.tk-borderless { border: none; }

.tk-tint-primary { background: color-mix(in srgb, var(--color-primary) 15%, transparent); }
.tk-tint-accent { background: color-mix(in srgb, var(--color-accent) 15%, transparent); }
.tk-tint-success { background: rgba(34, 197, 94, 0.15); }
.tk-tint-ebay { background: rgba(229, 50, 56, 0.15); }
.tk-tint-amazon { background: rgba(255, 153, 0, 0.15); }
.tk-tint-tiktok { background: rgba(0, 242, 234, 0.15); }
.tk-tint-etsy { background: rgba(245, 100, 0, 0.15); }
.tk-tint-royal-mail { background: rgba(230, 0, 18, 0.15); }
.tk-tint-error { background: rgba(239, 68, 68, 0.15); }
.tk-tint-orange { background: rgba(249, 115, 22, 0.15); }
.tk-text-orange { color: #f97316; }

/* Cards are --bg-card, so a neutral badge tinted with the same token is an
   invisible pill. --bg-tertiary is the one neutral that separates from the card
   in both themes. */
.badge-neutral { background: var(--bg-tertiary); color: var(--text-secondary); }

.tk-callout-warning {
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning);
    border-radius: var(--radius-lg);
    padding: 1rem 1.25rem;
}

.tk-callout-error {
    background: var(--color-error-bg);
    border: 1px solid var(--color-error);
    border-radius: var(--radius-lg);
    padding: 1rem 1.25rem;
}

.tk-callout-info {
    background: var(--color-info-bg);
    border: 1px solid var(--color-info);
    border-radius: var(--radius-lg);
    padding: 1rem 1.25rem;
}

.modal-wide { max-width: 680px; }

.tk-text-brand-strong { color: var(--color-primary); }
.tk-disabled { opacity: 0.6; cursor: not-allowed; }
.tk-accent-primary { accent-color: var(--color-primary); }
.tk-border-bottom { border-bottom: 1px solid var(--border-primary); }
.tk-border-right { border-right: 1px solid var(--border-primary); }
.tk-bg-primary { background: var(--bg-primary); }
.tk-bg-border { background: var(--border-primary); }
.tk-badge-error-solid { background: var(--color-error); color: white; }
.tk-selectable-border { border: 2px solid transparent; }
.tk-flush { border: none; border-radius: 0; }
.tk-min-w-150 { min-width: 150px; }
.tk-max-w-400 { max-width: 400px; }
.tk-tint-primary-12 { background: color-mix(in srgb, var(--color-primary) 12%, transparent); }
.tk-tint-woocommerce { background: rgba(127, 84, 179, 0.15); }
.tk-tint-shopify { background: rgba(150, 191, 72, 0.15); }
.tk-tint-dpd { background: rgba(220, 0, 50, 0.15); }
.tk-tint-ups { background: rgba(255, 181, 0, 0.18); }
.tk-tint-amber { background: rgba(245, 158, 11, 0.15); }
.tk-tint-info-bright { background: rgba(59, 130, 246, 0.15); }
/* Inputs with a leading icon overlay */
.input-icon-pad { padding-left: 2.75rem; }
.auth-logo-img { height: 52px; width: auto; object-fit: contain; }

.img-contain { object-fit: contain; }
.icon-img-20 { width: 20px; height: 20px; object-fit: contain; }
.icon-img-24 { width: 24px; height: 24px; object-fit: contain; }
.icon-img-32 { width: 32px; height: 32px; object-fit: contain; }
.tk-select-inline { width: auto; min-width: 150px; }

/* -----------------------------------------------------------------------------
   Keyboard focus (P2 item 16, SC 2.4.7) — safety net for controls without a
   dedicated focus style. Inputs keep their border+ring treatment; this covers
   buttons, links and checkboxes. :focus-visible only, so mouse clicks stay
   ring-free.
   ----------------------------------------------------------------------------- */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible,
select:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
}

/* -----------------------------------------------------------------------------
   Reduced motion — global (SC 2.3.3). Loaded by both layouts via this file.
   ----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* -----------------------------------------------------------------------------
   Honeypot field
   -----------------------------------------------------------------------------
   Not `display: none` and not `visibility: hidden`: form-filling scripts check
   for both and skip those inputs, which defeats the trap. Positioned off-screen
   instead, so it is absent for people but present in the DOM.

   This is the opposite of .sr-only. Screen reader users must not reach it
   either, which is what aria-hidden and tabindex="-1" on the markup are for.
   ----------------------------------------------------------------------------- */
.visually-gone {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* -----------------------------------------------------------------------------
   Cookie consent banner
   -----------------------------------------------------------------------------
   A bar pinned to the bottom, not a modal: it must not block the page or trap
   focus for a decision this small.

   Accept and Reject are deliberately identical. UK PECR requires refusing to be
   as easy as accepting, so neither gets primary-button treatment. If you are
   about to make Accept green, read js/cookie-consent.js first.
   ----------------------------------------------------------------------------- */

.cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    background: var(--bg-card);
    border-top: 1px solid var(--border-primary);
    box-shadow: var(--shadow-lg);
    animation: cookie-banner-in 220ms ease-out;
}

@keyframes cookie-banner-in {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

.cookie-banner__inner {
    max-width: 1080px;
    margin: 0 auto;
    padding: 18px 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

@media (min-width: 768px) {
    .cookie-banner__inner {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 32px;
    }
}

.cookie-banner__copy {
    flex: 1 1 auto;
    min-width: 0;
}

.cookie-banner__title {
    font-family: var(--font-display);
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 4px;
}

.cookie-banner__text {
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-secondary);
    margin: 0;
    max-width: 68ch;
}

.cookie-banner__text a {
    color: var(--color-primary);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.cookie-banner__actions {
    display: flex;
    gap: 10px;
    flex: 0 0 auto;
}

.cookie-banner__btn {
    flex: 1 1 0;
    padding: 10px 22px;
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-primary);
    background: var(--bg-secondary);
    border: 1px solid var(--border-secondary);
    border-radius: 8px;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 120ms ease, border-color 120ms ease;
}

@media (min-width: 768px) {
    .cookie-banner__btn {
        flex: 0 0 auto;
        min-width: 112px;
    }
}

.cookie-banner__btn:hover {
    background: var(--bg-card-hover);
    border-color: var(--color-primary);
}

/* Footer control that reopens the banner. Has to be a button for assistive
   technology, so it is restyled to sit inline with the surrounding links. */
.public-footer__link-btn {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    text-decoration: none;
}

.public-footer__link-btn:hover {
    color: var(--color-primary);
    text-decoration: underline;
}

/* -----------------------------------------------------------------------------
   Connection cards
   -----------------------------------------------------------------------------
   Moved here from the inline <style> block in views/pages/connections.pug on
   2026-09-10, unchanged. They were page-local, which meant the public site
   could not render the real component and grew a lookalike instead. Shared now,
   so /connections and the marketing readouts cannot drift apart.
   ----------------------------------------------------------------------------- */
.connection-card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    transition: all var(--transition-normal);
}

.connection-card:hover {
    border-color: var(--border-secondary);
    box-shadow: var(--shadow-md);
}

.connection-card-disabled { opacity: 0.6; }

.connection-card-disabled:hover {
    box-shadow: none;
    border-color: var(--border-primary);
}

.connection-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1rem;
}

.connection-logo {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-lg);
    display: flex;
    align-items: center;
    justify-content: center;
}

.connection-name {
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 0.5rem 0;
}

.connection-description {
    font-size: 0.875rem;
    color: var(--text-secondary);
    margin: 0 0 1rem 0;
    line-height: 1.5;
}

.connection-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-top: 1rem;
    border-top: 1px solid var(--border-primary);
}

.connection-last-sync {
    font-size: 0.75rem;
    color: var(--text-tertiary);
}

.connection-card.connected { border-color: var(--color-success); }

/* -----------------------------------------------------------------------------
   Onboarding wizard steps
   -----------------------------------------------------------------------------
   Moved here from views/pages/onboarding.pug on 2026-09-10, unchanged, for the
   same reason as the connection cards above.
   ----------------------------------------------------------------------------- */
.wizard-steps {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin-bottom: 2.5rem;
    flex-wrap: wrap;
}

.wizard-step {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--text-tertiary);
    font-size: 0.875rem;
    font-weight: 500;
}

.wizard-step-number {
    width: 28px;
    height: 28px;
    border-radius: 9999px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    font-size: 0.8125rem;
    font-weight: 600;
}

.wizard-step.active { color: var(--text-primary); }

.wizard-step.active .wizard-step-number {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: #ffffff;
}

.wizard-step.done .wizard-step-number {
    background: var(--color-success);
    border-color: var(--color-success);
    color: #ffffff;
}

.wizard-step-divider {
    width: 32px;
    height: 1px;
    background: var(--border-primary);
}

/* -----------------------------------------------------------------------------
   .hidden — last word
   -----------------------------------------------------------------------------
   Must stay at the very end of this file.

   `:where(.hidden)` in the utility block has zero specificity by design, so
   Tailwind's responsive variants can beat it. The side effect is that it also
   loses to every component rule in this file that sets `display` — there are 62
   of them — because those are plain single-class selectors at normal
   specificity, and site.css loads after tailwind.css, so they beat Tailwind's
   `.hidden` too.

   The visible symptom was that `#errorMessage.hidden.alert` rendered as
   `display: flex`: every auth page showed "Invalid credentials" and its success
   banner on load, before the user had typed anything. Spinners marked
   `.hidden.spinner` were permanently visible for the same reason.

   Doubling the class gives 0-2-0, which beats those component rules while
   leaving anything more specific able to override it. `!important` would also
   work but takes away that escape hatch. Do not "simplify" this to a single
   `.hidden` — that is the bug.

   Note this deliberately beats single-class responsive variants too, so
   `class="hidden md:flex"` will stay hidden. That pattern is already avoided
   throughout this codebase (see the notes in index.pug and
   template-marketing.pug); use a purpose-named class with its own media query
   instead, as .desktop-only below does.
   ----------------------------------------------------------------------------- */
.hidden.hidden {
    display: none;
}

/* Shown only from the medium breakpoint up. Replaces the `hidden md_flex`
   pairing in template-noauth, where `md_flex` was never defined in any
   stylesheet and so left the public nav links invisible at every width. */
.desktop-only {
    display: none;
}

@media (min-width: 768px) {
    .desktop-only {
        display: flex;
    }
}

/* =============================================================================
   TWO-FACTOR AUTHENTICATION  (settings page)
   =============================================================================
   Two ideas drive the styling here.

   First, the enrolment steps are numbered and indented because the order
   genuinely matters — scan, then confirm — and a flat stack of controls would
   not say so.

   Second, and more importantly: recovery codes exist to be READ OFF A SCREEN AND
   WRITTEN DOWN, usually onto paper, often in a hurry. So they are set in
   tabular-figure monospace with wide letter-spacing and a visible group
   separator, sized well above body copy. Every choice below is in service of
   somebody transcribing ten characters without a mistake. A transcription error
   here surfaces months later as a locked-out account.
   -------------------------------------------------------------------------- */

/* ---- Card header ----
   The status badge belongs on the title's line: it is the answer to the
   question the heading asks, and reading it before the paragraph is the point.
   A plain flex row wraps it onto its own line at every width, because the text
   block's flex-basis is its max-content width. min-width lets it shrink, and
   the badge only drops below once the description is genuinely cramped. */

.mfa-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.mfa-head__text {
    flex: 1 1 18rem;
    min-width: 0;
}

.mfa-head .badge {
    flex-shrink: 0;
}

/* ---- Enrolment steps ---- */

.mfa-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 28px;
}

.mfa-step {
    display: grid;
    grid-template-columns: 32px 1fr;
    gap: 16px;
    align-items: start;
}

/* The numeral carries the sequence, so it is a deliberate object rather than a
   list-style marker — it has to stay legible against the card in both themes. */
.mfa-step__num {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-on-primary);
    background: var(--color-primary);
}

.mfa-step__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 4px 0 0;
}

.mfa-step__hint {
    font-size: 13px;
    color: var(--text-tertiary);
    margin: 4px 0 0;
}

/* ---- QR code ----
   Held on a permanently light panel. A QR needs dark-on-light contrast to scan,
   and in dark mode the generated PNG would otherwise sit on a dark card and
   fail to read on some phones. */
.mfa-qr {
    margin-top: 14px;
    width: max-content;
    padding: 12px;
    border-radius: 12px;
    background: #ffffff;
    border: 1px solid var(--border-primary);
}

.mfa-qr__img {
    display: block;
    width: 220px;
    height: 220px;
}

.mfa-manual {
    margin-top: 12px;
}

.mfa-manual__summary {
    font-size: 13px;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
}

.mfa-manual__summary:hover {
    color: var(--text-primary);
}

/* The manual-entry key is transcribed too, so it gets the same treatment as the
   recovery codes: monospace, spaced, and selectable in one go. */
.mfa-secret {
    display: inline-block;
    margin-top: 6px;
    padding: 8px 12px;
    border-radius: 8px;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: 13px;
    letter-spacing: 0.12em;
    color: var(--text-primary);
    word-break: break-all;
    user-select: all;
}

/* ---- Code entry ----
   Six digits, so the field is sized to six digits. A full-width input invites
   the wrong input and makes a typo harder to spot. */
.mfa-code-input {
    max-width: 200px;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: 22px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.3em;
    text-align: center;
    padding-left: 12px;
}

.mfa-code-input::placeholder {
    letter-spacing: 0.3em;
    color: var(--text-tertiary);
    opacity: 0.5;
}

/* ---- Enrolled state ---- */

.mfa-on-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 16px;
    border-radius: 12px;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
}

.mfa-on-row__label {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.mfa-on-row__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.mfa-on-row__hint {
    font-size: 12px;
    color: var(--text-tertiary);
}

.mfa-on-row__value {
    display: flex;
    align-items: center;
    gap: 12px;
}

.mfa-codes-count {
    font-variant-numeric: tabular-nums;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-secondary);
}

/* Running low is the only warning anyone gets before the last code is spent and
   the account becomes unreachable, so it changes colour rather than staying a
   quiet number. */
.mfa-codes-count--low {
    color: var(--color-warning);
}

.mfa-codes-count--empty {
    color: var(--color-error);
}

/* ---- Recovery codes ----
   See the note at the top of this block: these are laid out for accurate
   transcription, not for density. */

.recovery-grid {
    list-style: none;
    margin: 20px 0 0;
    padding: 16px;
    border-radius: 12px;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px 20px;
    counter-reset: recovery;
}

.recovery-grid li {
    counter-increment: recovery;
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: 15px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
    color: var(--text-primary);
    /* Selecting one code should give you that code, not the index beside it. */
    user-select: all;
}

/* The index is generated rather than typed, so copying a code never picks it
   up — and it gives somebody a way to check they have written down all ten. */
.recovery-grid li::before {
    content: counter(recovery);
    user-select: none;
    min-width: 18px;
    text-align: right;
    font-size: 11px;
    letter-spacing: 0;
    color: var(--text-tertiary);
}

.recovery-grid li.is-used {
    text-decoration: line-through;
    color: var(--text-tertiary);
}

.recovery-actions {
    display: flex;
    gap: 10px;
    margin-top: 14px;
}

/* The acknowledgement gates the only way out of the dialog. It is a full-width
   hit target because it has to be deliberately ticked, not brushed past. */
.recovery-ack {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 20px;
    padding: 12px 14px;
    border-radius: 10px;
    background: var(--color-warning-bg, var(--bg-tertiary));
    border: 1px solid var(--border-primary);
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
}

.recovery-ack input {
    margin-top: 2px;
    flex-shrink: 0;
}

@media (max-width: 560px) {
    .recovery-grid {
        grid-template-columns: 1fr;
    }

    .mfa-qr__img {
        width: 180px;
        height: 180px;
    }
}

/* Screen-reader-only label, for inputs whose purpose is carried visually by an
   adjacent step title. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* -----------------------------------------------------------------------------
   Switch — a real form control for on/off preferences
   -----------------------------------------------------------------------------
   Distinct from .theme-toggle-switch, which is painted from the [data-theme]
   attribute and has no input behind it. This one is driven by a checkbox, so it
   is focusable, announced, and operable from the keyboard for free.

   The input stays in the layout (opacity 0, sized over the track) rather than
   display:none, because a display:none input is unfocusable and would make the
   control keyboard-dead.

   Used where a change applies immediately. The track colour is the only state
   indicator, so it carries a 3:1 contrast against the card behind it in both
   themes, and the thumb's position repeats the state for anyone who cannot see
   the colour difference.
   ----------------------------------------------------------------------------- */
.switch {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    width: 44px;
    height: 24px;
}

.switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.switch-track {
    position: relative;
    width: 44px;
    height: 24px;
    background: var(--border-secondary);
    border-radius: var(--radius-full);
    transition: background var(--transition-fast);
    pointer-events: none;
}

.switch-track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    background: #ffffff;
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
    transition: left var(--transition-fast);
}

.switch input:checked + .switch-track {
    background: var(--color-primary);
}

.switch input:checked + .switch-track::after {
    left: 22px;
}

.switch input:focus-visible + .switch-track {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
}

.switch input:disabled {
    cursor: not-allowed;
}

.switch input:disabled + .switch-track {
    opacity: 0.5;
}

/* -----------------------------------------------------------------------------
   Alert preference rows
   -----------------------------------------------------------------------------
   Hairline rules rather than one card per row: these are six peers in a single
   list, and giving each its own card would imply six separate objects to manage.
   ----------------------------------------------------------------------------- */
.alert-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 1rem 0;
    border-top: 1px solid var(--border-primary);
}

.alert-row:first-child {
    border-top: none;
}

.alert-row-text {
    min-width: 0;
}

/* The firing-alert list. Only rendered when something is actually wrong. */
.alert-active {
    border-left: 3px solid var(--color-warning);
    background: var(--color-warning-bg);
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
}

@media (max-width: 480px) {
    .alert-row {
        gap: 1rem;
    }
}
