/* Wizzrobe Custom Styles */

/* ---------------------------------------------------------------------------
   Self-hosted fonts (SIL OFL, served from /static/fonts)
   Fraunces       — display serif for titles/wordmark
   Atkinson Hyperlegible — body/UI sans
   IBM Plex Mono  — match IDs, times, timestamps
--------------------------------------------------------------------------- */
@font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('/static/fonts/fraunces-latin-600-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/static/fonts/fraunces-latin-700-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Atkinson Hyperlegible';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/static/fonts/atkinson-hyperlegible-latin-400-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Atkinson Hyperlegible';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('/static/fonts/atkinson-hyperlegible-latin-400-italic.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Atkinson Hyperlegible';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/static/fonts/atkinson-hyperlegible-latin-700-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/static/fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('/static/fonts/ibm-plex-mono-latin-600-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Brand palette — "Wizzrobe 2026: The New Age" phoenix theme.
   Mode-aware aliases default to light-mode-safe (deep) values and switch to
   bright gold under .body--dark / .q-dark. Inline styles may reference these
   var()s directly so links adapt to the active mode. */
:root {
    --wiz-gold: #E0A82E;        /* brand gold — dark-mode links/titles, accents */
    --wiz-gold-deep: #9C6B12;   /* light-mode links/titles (contrast on white) */
    --wiz-ember: #E8631A;       /* secondary / ember-orange accent */
    --wiz-ember-deep: #C24E12;  /* ember for light-mode text/borders */
    --wiz-charcoal: #17120D;    /* warm near-black for header/footer surfaces */

    --wiz-link: var(--wiz-gold-deep);
    --wiz-section-title: var(--wiz-gold-deep);
    --wiz-room-header-bg: var(--wiz-charcoal);
    --wiz-room-header-text: var(--wiz-gold);
    --wiz-header-bg: var(--wiz-gold-deep);  /* light mode: amber bar (white header text stays AA-legible) */

    /* Warm phoenix-tinted dark-mode surface scale — one coherent elevation ramp
       so dark mode reads as deliberate steps, not blotchy mismatched grays.
       --wiz-charcoal (#17120D) is the darkest "bookend" (header/footer). */
    --wiz-dark-bg:        #1c1714;  /* page body — warm near-black */
    --wiz-dark-surface:   #241e19;  /* cards, filter cards, grid cards, odd rows */
    --wiz-dark-surface-2: #2c2520;  /* even rows — subtle warm zebra */
    --wiz-dark-border:    #3a322b;  /* borders */
    --wiz-dark-muted:     #b0a59a;  /* muted text — legible on dark */

    /* Typography */
    --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
    --font-body: 'Atkinson Hyperlegible', 'Segoe UI', system-ui, sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;

    /* Status colors — warm-tuned to sit with the phoenix palette (not stock
       Material). Light values hold ≥4.5:1 on white; dark variants lift under
       .body--dark / .q-dark. */
    --status-ok:        #557A1F;  /* warm olive green */
    --status-pending:   #B45309;  /* burnt amber */
    --status-live:      #0E7470;  /* deep teal — complementary pop on warm field */
    --status-neutral:   #6B6258;  /* warm gray */
    --status-cancelled: #B3362B;  /* brick red */

    /* Mode-aware surfaces: light values here, re-aliased to the dark scale
       below so a single rule serves both modes. */
    --surface: #FFFFFF;
    --surface-alt: #FAF5EC;   /* warm cream — zebra rows, filter cards, footer */
    --page-bg: #F7F2E9;

    --border-subtle: #EAE1D2;
    --border-strong: #D8CBB6;

    /* Spacing scale */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 1.5rem;
    --space-5: 2rem;
    --space-6: 3rem;

    /* Radii */
    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-pill: 999px;

    /* Height reserved for the mobile bottom nav (.wiz-bottom-nav) — the fixed
       refresh FAB and the reconnect overlay clear it by this amount. Single
       source so tuning the nav does not silently desync those offsets. */
    --wiz-bottom-nav-h: 72px;

    /* Warm umber-tinted shadows (never pure black on the light theme) */
    --shadow-1: 0 1px 2px rgba(46, 32, 12, 0.07), 0 1px 4px rgba(46, 32, 12, 0.05);
    --shadow-2: 0 2px 6px rgba(46, 32, 12, 0.10), 0 6px 16px rgba(46, 32, 12, 0.08);
    --shadow-3: 0 10px 32px rgba(46, 32, 12, 0.16);

    /* Motion */
    --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
    --dur-fast: 120ms;
    --dur-med: 220ms;
}

.body--dark,
.q-dark {
    --wiz-link: var(--wiz-gold);
    --wiz-section-title: var(--wiz-gold);
    --wiz-header-bg: var(--wiz-charcoal);  /* dark mode: keep the charcoal bar */

    /* Override Quasar's own dark tokens so every default-styled surface (cards,
       dialogs, drawer, tab panels, menus) inherits the warm scale in one place
       instead of being patched element-by-element. */
    --q-dark-page: var(--wiz-dark-bg);
    --q-dark:      var(--wiz-dark-surface);

    /* Lifted status variants for dark surfaces */
    --status-ok:        #A8C46B;
    --status-pending:   #F0A848;
    --status-live:      #52B8AE;
    --status-neutral:   var(--wiz-dark-muted);
    --status-cancelled: #E06650;

    --surface: var(--wiz-dark-surface);
    --surface-alt: var(--wiz-dark-surface-2);
    --page-bg: var(--wiz-dark-bg);

    --border-subtle: var(--wiz-dark-border);
    --border-strong: #4A4036;

    --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.35);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.45);
    --shadow-3: 0 12px 36px rgba(0, 0, 0, 0.55);
}

/* Base typography — wins over Quasar's Roboto default because this sheet loads
   after the framework CSS. Components that set their own family are re-pointed
   explicitly. */
body {
    font-family: var(--font-body);
}

.q-table,
.q-btn,
.q-field,
.q-item {
    font-family: var(--font-body);
}

/* Atmosphere: a faint warm glow at the top of the page so the canvas reads as
   lit, not flat-default. The page body paints the visible background behind
   all (transparent) layout wrappers; Quasar hardcodes dark to a cool #121212,
   so both modes are set explicitly. */
body {
    background: radial-gradient(1100px 520px at 50% -180px,
        #FCF8F0 0%, var(--page-bg) 65%) fixed;
}

body.body--dark {
    background: radial-gradient(1100px 520px at 50% -180px,
        #251d14 0%, var(--wiz-dark-bg) 65%) fixed;
}

/* Touch devices can't cheaply repaint a `fixed` background while scrolling (and
   several mobile browsers ignore fixed attachment outright), so fall back to a
   scroll-attached gradient — the glow still paints, without the jank. */
@media (hover: none) {
    body,
    body.body--dark {
        background-attachment: scroll;
    }
}

/* Brand focus ring and selection */
:focus-visible {
    outline: 2px solid var(--wiz-gold);
    outline-offset: 2px;
    border-radius: 2px;
}

::selection {
    background: rgba(224, 168, 46, 0.30);
}

.body--dark ::selection {
    background: rgba(224, 168, 46, 0.40);
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}

/* In dark mode, brighten primary/secondary *text and icons* to the vibrant
   gold/ember so they stay legible on dark surfaces. Filled .bg-primary buttons
   intentionally keep the deeper hue so their white labels retain contrast. */
.body--dark .text-primary,
.q-dark .text-primary {
    color: #E0A82E !important;
}

.body--dark .text-secondary,
.q-dark .text-secondary {
    color: #E8631A !important;
}

/* Muted text (#666/#888 stays legible on white) lifts to a warm light gray on
   dark surfaces so timestamps, form labels and filter labels remain readable. */
.body--dark .input-label,
.body--dark .required-legend,
.body--dark .match-filter-label,
.body--dark .text-muted,
.body--dark .text-italic,
.body--dark .cell-timestamp,
.q-dark .input-label,
.q-dark .required-legend,
.q-dark .match-filter-label,
.q-dark .text-muted,
.q-dark .text-italic,
.q-dark .cell-timestamp {
    color: var(--wiz-dark-muted);
}

/* Status utility classes — consumed by Vue slot templates and .classes() */
.st-ok        { color: var(--status-ok); }
.st-ok-strong { color: var(--status-ok); font-weight: 700; }
.st-pending   { color: var(--status-pending); }
.st-live      { color: var(--status-live); }
.st-neutral   { color: var(--status-neutral); }
.st-cancelled { color: var(--status-cancelled); }

/* Status chips — tinted pill replacing raw q-badge colors */
.wiz-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: color-mix(in srgb, var(--chip-c) 14%, transparent);
    color: var(--chip-c);
    border: 1px solid color-mix(in srgb, var(--chip-c) 35%, transparent);
}

.wiz-chip--ok        { --chip-c: var(--status-ok); }
.wiz-chip--pending   { --chip-c: var(--status-pending); }
.wiz-chip--live      { --chip-c: var(--status-live); }
.wiz-chip--neutral   { --chip-c: var(--status-neutral); }
.wiz-chip--cancelled { --chip-c: var(--status-cancelled); }
.wiz-chip--candidate { --chip-c: var(--wiz-gold-deep); }

.body--dark .wiz-chip--candidate,
.q-dark .wiz-chip--candidate {
    --chip-c: var(--wiz-gold);
}

/* Availability heatmap (My Availability editor, player + volunteer) — status
   fills mapped to the phoenix --status-* tokens so the bar sits with the
   palette and lifts in dark mode, replacing the old stock-Material
   red/green/blue. Segment widths stay inline (dynamic geometry); only the
   palette lives here. */
:root {
    --avail-none: var(--border-subtle);   /* "no availability" fill */
}

.body--dark,
.q-dark {
    --avail-none: var(--wiz-dark-surface-2);
}

.avail-swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
    flex: none;
}

.avail-bar {
    display: flex;
    height: 18px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    border: 1px solid var(--border-strong);
}

.avail-day-label {
    width: 96px;
    flex: none;
}

.avail-seg--preferred   { background: var(--status-ok); }
.avail-seg--available   { background: var(--status-live); }
.avail-seg--unavailable { background: var(--status-cancelled); }
.avail-seg--none        { background: var(--avail-none); }

/* Monospace data cells — match IDs, scheduled times, timestamps */
.cell-id,
.cell-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Inline timestamp spans inside table cell templates */
.cell-timestamp {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.75rem;
    color: var(--status-neutral);
}

/* Brand header — amber bar in light mode, charcoal in dark; the ember→gold
   strip along the bottom is the signature accent. */
/* No position override here: Quasar pins the header via its own fixed-top
   positioning, which the ::after strip also anchors to. */
.wiz-header {
    background-color: var(--wiz-header-bg);
    min-height: 56px;
    padding: 0 var(--space-3);
    /* Clear the status-bar / notch area when installed as a standalone PWA. */
    padding-top: env(safe-area-inset-top);
    box-shadow: var(--shadow-1);
}

.wiz-header::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: linear-gradient(90deg, var(--wiz-ember) 0%, var(--wiz-gold) 45%, var(--wiz-gold) 100%);
}

.wiz-wordmark {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.15rem;
    color: #fff;
    letter-spacing: 0.02em;
    margin-left: var(--space-2);
}

.body--dark .wiz-wordmark,
.q-dark .wiz-wordmark {
    color: var(--wiz-gold);
}

.wiz-burger .q-icon {
    font-size: 1.6rem;
}

@media (max-width: 600px) {
    .wiz-header {
        padding: 0 var(--space-2);
        padding-top: env(safe-area-inset-top);
    }

    .wiz-header .user-name {
        display: none;
    }

    /* Icon-only login button on phones: hide the label (the full-text button
       still lives in the page body), leaving the lone glyph centered. */
    .login-button-text {
        display: none;
    }

    .wiz-wordmark {
        font-size: 1rem;
        margin-left: var(--space-1);
    }
}

/* Drawer navigation */
.q-drawer {
    background: var(--surface);
    border-right: 1px solid var(--border-subtle);
}

.q-drawer .q-item {
    border-radius: var(--radius-sm);
    margin: 2px var(--space-2);
    transition: background var(--dur-fast) var(--ease-out);
}

.q-drawer .q-item:hover {
    background: color-mix(in srgb, var(--wiz-gold) 8%, transparent);
}

.q-drawer .q-item--active {
    background: color-mix(in srgb, var(--wiz-gold) 14%, transparent);
    box-shadow: inset 3px 0 0 var(--wiz-gold-deep);
    color: var(--wiz-section-title);
    font-weight: 700;
}

.body--dark .q-drawer .q-item--active,
.q-dark .q-drawer .q-item--active {
    box-shadow: inset 3px 0 0 var(--wiz-gold);
}

.q-drawer .text-h6 {
    font-family: var(--font-display);
    color: var(--wiz-section-title);
}

/* Drawer section headers (grouped admin nav): small, uppercase, gold — a quiet
   divider between destination clusters, not a clickable item. */
.q-drawer .wiz-drawer-group {
    padding: var(--space-3) var(--space-3) var(--space-1);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--wiz-section-title);
    opacity: 0.85;
}

/* Layout & Container Styles */
.page-container {
    width: 100%;
    max-width: 1400px;
    margin: 0 auto;
}

.page-container-narrow {
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
}

/* Settings-style forms (Profile). Capped far below page-container-narrow so a
   single-column form of labels/inputs reads at a comfortable line length on
   desktop instead of stretching ~1200px wide. */
.page-container-form {
    width: 100%;
    max-width: 760px;
    margin: 0 auto;
}

.page-container-wide {
    width: 100%;
    max-width: 1600px;
    margin: 0 auto;
}

.full-width {
    width: 100%;
}

.full-width-column {
    width: 100%;
    padding: 1em;
}

/* Flex utilities */
.flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Spacing utilities */
.mb-1 { margin-bottom: 1em; }
.ml-1 { margin-left: 1em; }

/* Header & Title Styles — display serif carries the brand voice */
.page-title {
    font-family: var(--font-display);
    font-weight: 600;
    /* Fluid down on narrow screens so long titles stay on one line; still caps
       at the original 2em on desktop (≥~1024px the middle term wins the max). */
    font-size: clamp(1.4em, 4.5vw + 0.6em, 2em);
    letter-spacing: 0.01em;
}

.section-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.5em;
    letter-spacing: 0.01em;
    margin-bottom: 1em;
    color: var(--wiz-section-title);
}

.subsection-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.3em;
    letter-spacing: 0.01em;
}

.large-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.8em;
    letter-spacing: 0.01em;
    margin: 0 1em;
}

.header-row {
    width: 100%;
    align-items: center;
    margin-bottom: 1.5em;
}

/* Card Styles — hairline border + warm low shadow instead of stock Quasar
   elevation */
.q-card {
    border-radius: var(--radius-md);
    border: 1px solid var(--border-subtle);
    box-shadow: var(--shadow-1);
    background: var(--surface);
}

/* Community picker (bare-host landing) cards — clickable brand tiles. */
.wiz-tenant-card {
    padding: var(--space-3) var(--space-4);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.wiz-tenant-card:hover {
    border-color: color-mix(in srgb, var(--wiz-gold) 55%, transparent);
    box-shadow: var(--shadow-2);
    transform: translateY(-1px);
}

.wiz-tenant-name {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.25rem;
    color: var(--wiz-section-title);
}

.card-padding {
    padding: 1.5em;
}

.card-padding-large {
    padding: 2em;
}

.card-centered {
    padding: 2em;
    text-align: center;
}

.card-full-width {
    width: 100%;
    margin-bottom: 1.5em;
    padding: 1.5em;
}

.card-list-item {
    width: 100%;
    margin-bottom: 0.5em;
}

/* Form & Input Styles */
.input-full-width {
    width: 100%;
}

.input-label {
    font-weight: 500;
    margin-bottom: 0.3em;
    color: var(--status-neutral);
}

.required-legend {
    font-size: 0.8em;
    color: var(--status-neutral);
    font-style: italic;
    margin-top: -0.25em;
    margin-bottom: 0.5em;
}

.form-grid {
    width: 100%;
    gap: 1em;
}

/* NiceGUI's ui.grid(columns=N) writes grid-template-columns inline, so it
   otherwise keeps its multi-column layout even on a phone — cramming two text
   inputs into a ~150px-wide column each. Collapse to a single column on narrow
   screens (!important is required to beat the inline style). */
@media (max-width: 600px) {
    .form-grid {
        grid-template-columns: 1fr !important;
    }
}

/* Stacked form column: full width up to a readable cap so a 10-char date field
   doesn't stretch a third of the screen (System Configuration, Profile). */
.wiz-form-column {
    width: 100%;
    max-width: 44rem;
}

/* Dialog Styles */
.dialog-card {
    width: min(700px, 90vw);
    max-width: 90vw;
    overflow-y: auto;
    max-height: 85vh;
}

.dialog-card-small {
    min-width: 350px;
}

@media (max-width: 768px) {
    .dialog-card-small {
        min-width: unset;
        width: 90vw;
    }
}

.q-dialog .q-card {
    border-radius: var(--radius-lg);
    border-top: 3px solid var(--wiz-gold);
    box-shadow: var(--shadow-3);
}

.q-dialog__backdrop {
    background: rgba(23, 18, 13, 0.55);
}

/* Full-screen dialog sheets on mobile. mobile_sheet() opens the dialog with
   :maximized="Quasar.Screen.lt.sm", so below 600px the centered card becomes an
   edge-to-edge sheet. Overrides the .dialog-card sizing and the .q-dialog
   .q-card brand border above (equal specificity, wins on source order). */
.q-dialog__inner--maximized > .dialog-card {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100dvh;
    border-radius: 0;
    border-top: none;
}

/* The card is align-items:flex-start, so children size to content and the sticky
   header/action bars (narrow button rows) don't span the width — leaving content
   peeking past their edge. Stretch ONLY those two bars (not arbitrary dialog
   content) so they fill the card and their negative margins bleed to the edges. */
.q-dialog__inner--maximized > .dialog-card > .dialog-header,
.q-dialog__inner--maximized > .dialog-card > .dialog-actions {
    align-self: stretch;
}

/* Sticky action bar pinned to the bottom of the scrolling .dialog-card so the
   primary Submit/Cancel row stays reachable without scrolling (fixes footers
   clipped below the fold). Negative margins of the card's own padding bleed the
   bar to the card edges; opaque --surface masks content scrolling beneath it
   and re-aliases to the dark surface on its own. */
.dialog-actions {
    position: sticky;
    bottom: calc(-1 * var(--nicegui-default-padding));
    z-index: 2;
    margin-top: 0.5rem;
    margin-left: calc(-1 * var(--nicegui-default-padding));
    margin-right: calc(-1 * var(--nicegui-default-padding));
    margin-bottom: calc(-1 * var(--nicegui-default-padding));
    padding: 0.5rem var(--nicegui-default-padding);
    padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));
    background: var(--surface);
    border-top: 1px solid var(--border-subtle);
}

/* Sticky title bar — top mirror of .dialog-actions, kept in view while the
   sheet body scrolls under it. */
.dialog-header {
    position: sticky;
    top: calc(-1 * var(--nicegui-default-padding));
    z-index: 2;
    margin-bottom: 0.5rem;
    margin-top: calc(-1 * var(--nicegui-default-padding));
    margin-left: calc(-1 * var(--nicegui-default-padding));
    margin-right: calc(-1 * var(--nicegui-default-padding));
    padding: 0.5rem var(--nicegui-default-padding);
    padding-top: calc(0.5rem + env(safe-area-inset-top));
    background: var(--surface);
    border-bottom: 1px solid var(--border-subtle);
}

/* Error Pages (40x / 50x) */
.error-page-container {
    width: 100%;
    max-width: 760px;
    margin: 0 auto;
    padding: 3rem 1rem;
    align-items: center;
}

.error-card {
    width: 100%;
    border-top: 3px solid var(--status-cancelled);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
    padding: 2rem;
    gap: 0.75rem;
}

.error-status {
    font-family: var(--font-display);
    font-size: clamp(3rem, 10vw, 5rem);
    font-weight: 600;
    line-height: 1;
    color: var(--status-cancelled);
}

.error-headline {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 600;
}

.error-message {
    font-size: 1rem;
    opacity: 0.85;
    max-width: 60ch;
}

.error-ref-row {
    margin-top: 0.5rem;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.error-ref-label {
    opacity: 0.7;
}

.error-ref-id {
    font-family: var(--font-mono);
    font-weight: 700;
    user-select: all;
}

.error-cat-fact {
    margin-top: 0.75rem;
    gap: 0.5rem;
    font-style: italic;
    opacity: 0.7;
    flex-wrap: wrap;
}

.error-actions {
    margin-top: 1rem;
}

.error-trace-title {
    margin-top: 1.5rem;
    font-weight: 700;
    opacity: 0.7;
}

.error-trace {
    width: 100%;
    max-height: 50vh;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    white-space: pre;
}

/* Button & Action Styles */
.q-btn {
    border-radius: var(--radius-sm);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: none;
    transition: box-shadow var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

/* Flatten Quasar's default raised elevation (it lives on ::before) so filled
   buttons read as quiet solids that lift on hover. */
.q-btn:not(.q-btn--flat):not(.q-btn--outline)::before {
    box-shadow: none;
}

.q-btn:not(.q-btn--flat):not(.q-btn--outline):hover {
    box-shadow: var(--shadow-1);
}

.button-row {
    width: 100%;
    justify-content: flex-end;
    margin-top: 1em;
}

.button-row-centered {
    width: 100%;
    justify-content: center;
    margin-top: 1em;
}

.action-row {
    margin-top: 1em;
}

/* Separator Styles */
.separator-spacing {
    margin-bottom: 1.5em;
}

/* Icon Styles */
.icon-large {
    color: var(--status-pending);
    margin-bottom: 0.5em;
}

.icon-error {
    color: var(--status-cancelled);
    margin-bottom: 0.5em;
}

.icon-primary {
    color: var(--wiz-link);
    margin-right: 0.5em;
}

.icon-spacing {
    margin-right: 0.5em;
}

/* Text & Label Styles */
.text-muted {
    color: var(--status-neutral);
    font-size: 1.2em;
    margin-bottom: 1em;
}

.text-italic {
    color: var(--status-neutral);
    font-size: 1.2em;
}

.text-gray {
    color: var(--status-neutral);
}

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

.text-warning {
    color: var(--status-pending);
    font-style: italic;
}

.text-error {
    color: var(--status-cancelled);
    font-weight: bold;
}

.text-link {
    color: var(--wiz-link);
    text-decoration: underline;
}

.table-link {
    color: var(--wiz-link);
    text-decoration: underline;
    cursor: pointer;
}

/* NiceGUI ui.link() defaults to blue — adopt the brand link color */
.nicegui-link {
    color: var(--wiz-link);
}

.italic-note {
    font-style: italic;
}

/* Timeline/Schedule Specific Styles */
.timeline-container {
    width: 100%;
}

.timeline-header {
    width: 100%;
    align-items: center;
    margin-bottom: 1em;
}

/* Prev/date/next kept together so the chevrons flank the date instead of
   splitting across lines when the header wraps on a phone. */
.timeline-nav {
    gap: var(--space-1);
}

.room-header {
    width: 100%;
    align-items: center;
    background-color: var(--wiz-room-header-bg);
    color: var(--wiz-room-header-text);
    padding: 0.5em;
    border-radius: var(--radius-sm);
}

.room-name {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.3em;
    letter-spacing: 0.01em;
}

.room-link {
    margin-left: auto;
    color: white;
    text-decoration: underline;
}

.room-match-count {
    font-size: 0.9em;
    opacity: 0.9;
}

.match-card {
    width: 100%;
    margin-bottom: 0.5em;
    transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}

.match-card:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-2);
}

.match-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 1.2em;
    font-weight: bold;
    min-width: 60px;
}

.match-badge {
    margin-left: 0.5em;
}

.match-tournament {
    margin-left: 1em;
    font-weight: 500;
}

.match-details {
    width: 100%;
    margin-top: 0.5em;
}

.match-details-nested {
    width: 100%;
    margin-top: 0.3em;
}

.match-players {
    font-size: 1.1em;
}

.empty-state {
    color: var(--status-neutral);
    font-style: italic;
    text-align: center;
    padding: 2em;
}

.empty-state-fact {
    margin-top: 0.5em;
    gap: 0.4em;
    font-size: 0.85em;
    opacity: 0.65;
    flex-wrap: wrap;
    justify-content: center;
}

/* Muted icon atop the branded table no-data slot (theme/empty_state.no_data_slot). */
.wiz-empty-icon {
    color: var(--status-neutral);
    opacity: 0.5;
}

/* User/Avatar Styles */
/* Right-edge alignment of the header controls is handled by the flexible
   ui.space() spacer in the header, not margin-left:auto here — that survives
   .user-name being display:none on phones (see .wiz-header rules). */
.user-avatar {
    margin-left: 8px;
    margin-right: 8px;
    max-width: 32px;
    max-height: 32px;
}

/* Fallback avatar glyph when the session has no Discord avatar URL. Sized to
   match the 32px image and tinted white to sit with the header's icon buttons. */
.user-avatar-placeholder {
    font-size: 32px;
    color: #fff;
}

/* The login button's label is a child element rather than the q-btn text prop
   (so it can be hidden on phones), which means the icon carries no automatic
   gap — restore it here. */
.login-button-text {
    margin-left: 4px;
}

/* Flex & Layout Utilities */
.flex-1 {
    flex: 1;
    min-width: 0;
}

.row-spacing {
    width: 100%;
    gap: 1em;
    margin-bottom: 0.5em;
}

.row-centered {
    align-items: center;
    margin-bottom: 1em;
}

.column-spacing {
    width: 100%;
    padding: 0.5em;
}

/* Chart & Report Styles */
.chart-container {
    width: 100%;
    min-height: 500px;
}

.chart-height {
    width: 100%;
    height: 400px;
}

.control-width {
    width: 400px;
}

/* Border Utilities — status rails on timeline cards */
.border-left-green {
    border-left: 4px solid var(--status-ok);
}

.border-left-orange {
    border-left: 4px solid var(--status-pending);
}

.border-left-gray {
    border-left: 4px solid var(--status-neutral);
}

.border-left-blue {
    border-left: 4px solid var(--status-live);
}

/* Visibility Utilities */
.hidden {
    display: none;
}

/* Refresh Button */
.refresh-button {
    position: fixed;
    bottom: max(20px, env(safe-area-inset-bottom));
    right: 20px;
}

/* ---------------------------------------------------------------------------
   Data tables (match / user / tournament families)
   Horizontal rules only — no vertical grid lines — with an uppercase caps
   header row and warm zebra. Surface tokens re-alias under dark mode, so one
   set of rules serves both modes. Grid mode (Quasar.Screen.lt.md) renders
   item slots, not <table>, and is styled via the *-grid-card classes below.
--------------------------------------------------------------------------- */
/* .wiz-table is the opt-in class for every other admin/report table (the
   hand-rolled ones that used to render as bare default Quasar tables); the four
   named families keep their classes and are listed alongside it so one rule set
   drives all of them. */
.match-table,
.user-table,
.tournament-table,
.equipment-table,
.wiz-table {
    border-collapse: collapse;
}

.match-table th,
.match-table td,
.user-table th,
.user-table td,
.tournament-table th,
.tournament-table td,
.equipment-table th,
.equipment-table td,
.wiz-table th,
.wiz-table td {
    border-right: none;
    border-bottom: 1px solid var(--border-subtle);
}

.match-table td,
.user-table td,
.tournament-table td,
.equipment-table td,
.wiz-table td {
    text-align: left;
    font-variant-numeric: tabular-nums;
}

.match-table thead th,
.user-table thead th,
.tournament-table thead th,
.equipment-table thead th,
.wiz-table thead th {
    text-align: center;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--wiz-gold-deep);
    background: var(--surface-alt);
    border-bottom: 2px solid color-mix(in srgb, var(--wiz-gold-deep) 45%, transparent);
}

.body--dark .match-table thead th,
.body--dark .user-table thead th,
.body--dark .tournament-table thead th,
.body--dark .equipment-table thead th,
.body--dark .wiz-table thead th,
.q-dark .match-table thead th,
.q-dark .user-table thead th,
.q-dark .tournament-table thead th,
.q-dark .equipment-table thead th,
.q-dark .wiz-table thead th {
    color: var(--wiz-gold);
    background: #201a15;
    border-bottom-color: color-mix(in srgb, var(--wiz-gold) 40%, transparent);
}

.match-table tbody tr:nth-child(even),
.user-table tbody tr:nth-child(even),
.tournament-table tbody tr:nth-child(even),
.equipment-table tbody tr:nth-child(even),
.wiz-table tbody tr:nth-child(even) {
    background-color: var(--surface-alt);
}

.match-table tbody tr:nth-child(odd),
.user-table tbody tr:nth-child(odd),
.tournament-table tbody tr:nth-child(odd),
.equipment-table tbody tr:nth-child(odd),
.wiz-table tbody tr:nth-child(odd) {
    background-color: var(--surface);
}

.match-table tbody tr:hover,
.user-table tbody tr:hover,
.tournament-table tbody tr:hover,
.equipment-table tbody tr:hover,
.wiz-table tbody tr:hover {
    background-color: color-mix(in srgb, var(--wiz-gold) 7%, transparent);
}

/* Allow wrapping for long strings — target the inner wrapper element placed
   inside the q-td, not the td itself */
.user-table td .wrap,
.tournament-table td .wrap {
    display: block;
    white-space: normal !important;
    word-break: normal;
    overflow-wrap: break-word;
    max-width: 320px;
}

.match-table td .wrap {
    display: block;
    white-space: normal !important;
    word-break: normal;
    overflow-wrap: break-word;
    max-width: 120px; /* reasonable default, table can expand as needed */
}

/* Ensure links inside the wrapper also wrap */
.match-table td .wrap a {
    white-space: normal !important;
    word-break: normal;
    overflow-wrap: break-word;
}

/* Force tables to fill their container width */
.user-table-container .q-table__middle,
.user-table-container table.q-table,
.tournament-table-container .q-table__middle,
.tournament-table-container table.q-table,
.equipment-table-container .q-table__middle,
.equipment-table-container table.q-table {
    width: 100%;
}

/* Transient highlight for rows/cards updated live by another user's action. */
@keyframes wizRowFlash {
    from { background-color: rgba(224, 168, 46, 0.35); }
    to   { background-color: transparent; }
}

.wiz-row-flash {
    animation: wizRowFlash 1.6s ease-out;
}

/* Mobile grid cards — token-driven surface; the vars switch per mode on
   their own. Slot templates keep only layout inline (width/box-sizing). */
.match-grid-card,
.user-grid-card,
.tournament-grid-card,
.equipment-grid-card {
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
}

/* Field labels in the grid cards use Quasar's text-grey-7, a fixed cool gray
   that goes murky on the dark surface; re-point it at the mode-aware muted token
   (!important to match Quasar's own). ``.wiz-grid-card`` is the generic class for
   the hand-rolled admin cards (platform tables, service health) — the card
   counterpart of ``.wiz-table``. */
.user-grid-card .text-grey-7,
.tournament-grid-card .text-grey-7,
.equipment-grid-card .text-grey-7,
.wiz-grid-card .text-grey-7 {
    color: var(--status-neutral) !important;
}

/* Purpose-built mobile match card (theme/tables/match_grid.py grid item slot):
   a monospace headline row (time + state chip), players line, muted caption,
   v-if-gated detail rows, and a single top-bordered actions row. All colors via
   tokens so dark mode follows automatically. */
.mgc-headline {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    font-size: 1.05em;
}

/* Keep the scheduled time intact — never let it break mid-value (e.g.
   "2026-07-02" / "18:09"); the state chip wraps to the next line instead. */
.mgc-time {
    white-space: nowrap;
    flex-shrink: 0;
}

/* State pill + muted time sit inline after the scheduled time, left-anchored
   (a right-floated chip wraps onto its own line at phone widths and looks
   detached). The pill resets to the body font — the headline itself is mono. */
.mgc-state {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    flex-shrink: 0;
    font-family: var(--font-body);
    font-weight: 400;
}

.mgc-caption {
    font-size: 0.8em;
    color: var(--status-neutral);
    margin: var(--space-1) 0;
}

/* Players line + per-player/crew rows and the #id caption link. Styling lives
   here (not inline on the template elements) so the card is fully token-driven. */
.mgc-players {
    margin-top: var(--space-1);
}

.mgc-player {
    display: flex;
    align-items: center;
    gap: 4px;
}

.mgc-crew-item {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.mgc-id-link {
    color: var(--wiz-link);
    text-decoration: underline;
}

/* Two-column grid so every detail row's value starts on the same label column,
   and a centered axis so action buttons (Sign Up, Generate, Assign Stage)
   align with their label instead of sitting on the text baseline. */
.mgc-detail {
    display: grid;
    grid-template-columns: 6.8rem 1fr;
    align-items: center;
    gap: 4px var(--space-1);
    font-size: 0.9em;
    margin-top: var(--space-1);
}

.mgc-detail-value {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    min-width: 0;
}

.mgc-label {
    font-weight: 600;
    color: var(--status-neutral);
}

.mgc-actions {
    width: 100%;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border-subtle);
}

.mgc-actions .q-btn {
    min-height: 44px;
}

/* Match filters card styling */
.match-filters-card {
    width: 100%;
    padding: 1em;
    margin-bottom: 1em;
    background-color: var(--surface-alt);
}

/* Collapsible filters on mobile. The filter card is always visible on desktop
   (≥1024px); below that it collapses behind the .wiz-filter-toggle button and
   only shows when the server adds .wiz-filters-open. */
@media (max-width: 1023.98px) {
    .match-filters-card {
        display: none;
    }

    .match-filters-card.wiz-filters-open {
        display: block;
    }
}

/* The Filters toggle button/badge row — mobile only; the filter card is pinned
   open on desktop so the toggle is redundant there. */
.wiz-filter-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    margin-bottom: var(--space-2);
}

@media (min-width: 1024px) {
    .wiz-filter-toggle {
        display: none;
    }
}

/* Match filter layout */
.match-filter-row {
    width: 100%;
    align-items: center;
    gap: 1em;
    flex-wrap: wrap;
}

.match-filter-column {
    min-width: 180px;
}

.match-filter-label {
    font-size: 0.85em;
    font-weight: 500;
    color: var(--status-neutral);
    margin-bottom: 0.3em;
}

.match-table-container {
    margin-top: 1em;
    width: 100%;
}

.user-table-container {
    margin-top: 1em;
    width: 100%;
}

.tournament-table-container {
    margin-top: 1em;
    width: 100%;
}

.equipment-table-container {
    margin-top: 1em;
    width: 100%;
}

/* Footer / app-shell bottom navigation — the footer is *only* this bar: a
   full-width tab row shown on phones and tablets (<1024px); on desktop the
   drawer carries navigation so it collapses away. The wrapping ui.footer()
   (.footer-dark-override) supplies the charcoal/cream surface + top border for
   the whole bar, so the nav itself only owns layout. */
.footer-dark-override {
    background-color: var(--surface-alt);
    color: var(--status-neutral);
    border-top: 1px solid var(--border-subtle);
    font-size: 0.78rem;
    letter-spacing: 0.02em;
}

.body--dark .footer-dark-override,
.q-dark .footer-dark-override {
    background-color: var(--wiz-charcoal) !important;
    color: var(--wiz-dark-muted) !important;
}

.wiz-bottom-nav {
    width: 100%;
    padding-bottom: env(safe-area-inset-bottom);
}

/* The footer exists solely to host the bottom nav, so collapse the whole bar
   (not just the nav) on desktop — otherwise the empty ui.footer() would render
   as a stray surface strip along the bottom edge. */
@media (min-width: 1024px) {
    .footer-dark-override {
        display: none;
    }
}

/* Copyright caption pinned to the foot of the drawer (replaces the old
   desktop-only footer meta row); muted to match the surrounding chrome. */
.wiz-drawer-copyright {
    color: var(--status-neutral);
}

.body--dark .wiz-drawer-copyright,
.q-dark .wiz-drawer-copyright {
    color: var(--wiz-dark-muted);
}

.wiz-drawer-github-link {
    color: var(--status-neutral);
    text-decoration: none;
}

.wiz-drawer-github-link:hover {
    text-decoration: underline;
}

.body--dark .wiz-drawer-github-link,
.q-dark .wiz-drawer-github-link {
    color: var(--wiz-dark-muted);
}

@media (max-width: 1023.98px) {
    /* The fixed refresh FAB would otherwise sit under the bottom nav bar; lift it
       above the nav (and the safe-area inset) so it stays tappable. */
    .refresh-button {
        bottom: calc(var(--wiz-bottom-nav-h) + env(safe-area-inset-bottom));
    }
}

/* ---------------------------------------------------------------------------
   Mobile ergonomics (≤599.98px)
   Phone-first touch sizing: 16px form text defeats iOS focus-zoom, buttons and
   tabs meet the 44px/48px tap-target floor, and the chip remove affordance
   grows to a comfortable target. Scoped tight so the desktop layout above is
   untouched.
--------------------------------------------------------------------------- */
@media (max-width: 599.98px) {
    body {
        font-size: 15px;
    }

    /* 16px is the threshold below which iOS Safari zooms the viewport on focus,
       yanking the layout around; keep every field input at or above it. */
    .q-field__native,
    .q-field input,
    .q-field textarea,
    .q-field__input {
        font-size: 16px;
    }

    .q-btn {
        min-height: 44px;
    }

    /* Dense/round buttons are the grid-card actions (Sign Up, Generate, watch,
       Assign Stage) and the toolbar refresh — lift them to the 44px touch floor
       on phones so they clear the tap-target minimum like the standard buttons. */
    .q-btn--dense,
    .q-btn--round {
        min-height: 44px;
        min-width: 44px;
    }

    /* Enlarge the chip remove hit area (the glyph is 24px but the tappable box
       stays small); pad it out so dismissing a filter/selection chip is easy. */
    .q-chip__icon--remove {
        font-size: 24px;
        padding: 10px;
        margin: -10px -4px -10px 0;
    }

    .q-tab {
        min-height: 48px;
    }

    /* On Air date heading: the full "Friday, July 03, 2026" at 1.8em wraps and
       shoves the nav around on a phone; scale it down and drop the wide side
       margin so the chevrons sit snug against it. */
    .timeline-date {
        font-size: 1.25em;
        margin: 0 0.25em;
    }
}

/* ---------------------------------------------------------------------------
   Reconnect overlay — brand NiceGUI's "Connection lost / Trying to reconnect"
   popup (#popup.nicegui-error-popup) so a dropped socket (phone lock, network
   flip) reads as a deliberate, on-brand moment instead of a stock gray toast.
   This sheet loads after nicegui.css, so these override by source order; the
   framework's [aria-hidden] show/hide transitions still drive visibility.
--------------------------------------------------------------------------- */
.nicegui-error-popup {
    background: var(--wiz-charcoal) !important;
    color: var(--wiz-gold) !important;
    border: 1px solid color-mix(in srgb, var(--wiz-gold) 45%, transparent);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-3);
    font-family: var(--font-body);
    /* Clear the mobile bottom nav / home indicator when pinned to the bottom. */
    margin-bottom: calc(var(--wiz-bottom-nav-h) + env(safe-area-inset-bottom));
}

.nicegui-error-popup > span:first-child {
    font-family: var(--font-display);
    font-weight: 600;
    color: #fff;
}
