/* ==========================================================================
   Design tokens
   --------------------------------------------------------------------------
   Both themes are defined here, together, as two complete palettes. Neither is
   derived from the other: dark is not light with the lightness flipped, it is
   its own set of values chosen against the same contrast floors. Inverting a
   light palette is what produces the washed-out greys and the one unreadable
   link that every retro-fitted dark mode has.

   Every pair below was checked against the WCAG 2.2 thresholds that actually
   apply to it — 4.5:1 for text, 3:1 for a non-text indicator like a focus ring
   or a validation outline. The two are not interchangeable, and the ratio noted
   on each line is the computed one.

   Resolution order, which is what makes the theme trigger behave:
     1. :root                                  — light, the starting point
     2. prefers-color-scheme: dark             — dark, unless a light choice
                                                 has been made explicitly
     3. :root[data-theme="dark"]               — dark, chosen explicitly
   The :not([data-theme="light"]) guard on step 2 is the load-bearing part: it
   is what stops the operating system flipping to dark at sunset and silently
   overriding a person who chose light on purpose.
   ========================================================================== */

:root {
    color-scheme: light;

    /* Surface and text ------------------------------------------------ */
    --color-surface: #ffffff;
    --color-text: #1a1a1a;                      /* 17.40:1 on surface */
    --color-stale-text: #6b6b6b;                /* 5.33:1 — see note below */

    /* Interactive ----------------------------------------------------- */
    --color-link: #0071c1;                      /* 5.08:1 */
    --color-focus-ring: #258cfb;                /* 3.37:1, 3:1 floor */
    --color-action-primary: #1b6ec2;            /* 5.18:1 against its own text */
    --color-action-primary-text: #ffffff;
    --color-action-primary-border: #1861ac;

    /* Status ---------------------------------------------------------- */
    --color-danger: #dc3545;                    /* 4.53:1 */

    /* Corrected, not inherited. The green this replaced computed to 2.83:1
       against white — below the 3:1 floor that applies to it as a validation
       outline. It was already failing before this file was tokenised; carrying
       it forward under a variable name would have been laundering a known
       defect rather than fixing one. */
    --color-success: #178040;                   /* 5.00:1, 3:1 floor */

    --color-code: #c02d76;                      /* 5.41:1 */
    --color-danger-surface: #b32121;            /* 6.65:1 against its text */
    --color-danger-surface-text: #ffffff;

    /* Sidebar --------------------------------------------------------- */
    --color-sidebar-grad-start: rgb(5, 39, 103);
    --color-sidebar-grad-end: #3a0647;
    --color-sidebar-text: #ffffff;              /* 14.07:1 worst-case stop */
    --color-nav-item: #d7d7d7;                  /* 11.25:1 */

    /* Chrome ---------------------------------------------------------- */
    --color-progress-track: #e0e0e0;

    /* Typography ------------------------------------------------------ */
    --font-family-base: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --font-size-body: 1rem;
    --font-size-small: 0.875rem;
    --font-size-heading-3: 1.25rem;
    --font-size-heading-1: 1.75rem;

    /* Spacing, radius, elevation -------------------------------------- */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 2rem;
    --radius-default: 0.375rem;
    --elevation-banner: 0 -1px 2px rgba(0, 0, 0, 0.2);
    --elevation-card: 0 1px 3px rgba(0, 0, 0, 0.12);

    /* Bootstrap reads its own custom properties, so the app's danger colour
       and Bootstrap's become the same value again by redefining one variable
       rather than by out-specificing its selectors. */
    --bs-danger: var(--color-danger);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --color-surface: #000000;
        --color-text: #ffffff;                  /* 21.00:1 */
        --color-stale-text: #c4c4c4;            /* 12.04:1 — see note below */

        --color-link: #7ab8f5;                  /* 10.00:1 */
        --color-focus-ring: #7ab8f5;            /* 10.00:1, 3:1 floor */
        --color-action-primary: #4a94dc;        /* 6.56:1 against its own text */
        --color-action-primary-text: #000000;
        --color-action-primary-border: #356f9e;

        --color-danger: #ff7a7a;                /* 8.32:1 */
        --color-success: #57d382;               /* 11.05:1, 3:1 floor */
        --color-code: #ec84b8;                  /* 8.56:1 */
        --color-danger-surface: #6e0f0f;        /* 12.08:1 against its text */
        --color-danger-surface-text: #ffffff;

        /* Retained, not flattened: the gradient is a fixed, minor portion of the
           screen and the app's one persistent identity landmark. Both values below
           are checked against the gradient's own stops, never against
           --color-surface, so re-anchoring the surface to black leaves them
           untouched. Worst case is the lighter stop, #04173d. */
        --color-sidebar-grad-start: #04173d;
        --color-sidebar-grad-end: #22032c;
        --color-sidebar-text: #f0f0f0;          /* 15.42:1 worst-case stop */
        --color-nav-item: #c9c9c9;              /* 10.61:1 worst-case stop */

        --color-progress-track: #1a1a1a;

        --elevation-card: 0 1px 3px rgba(0, 0, 0, 0.4);
    }
}

/* The explicitly-chosen dark theme. Every value below is deliberately identical
   to the prefers-color-scheme block above — the two are one palette written
   twice, because a person who picks dark and a person whose system picks dark
   must not get different screens. Editing one without the other is the defect
   this comment exists to prevent; the computed ratios live above, once. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --color-surface: #000000;
    --color-text: #ffffff;
    --color-stale-text: #c4c4c4;

    --color-link: #7ab8f5;
    --color-focus-ring: #7ab8f5;
    --color-action-primary: #4a94dc;
    --color-action-primary-text: #000000;
    --color-action-primary-border: #356f9e;

    --color-danger: #ff7a7a;
    --color-success: #57d382;
    --color-code: #ec84b8;
    --color-danger-surface: #6e0f0f;
    --color-danger-surface-text: #ffffff;

    --color-sidebar-grad-start: #04173d;
    --color-sidebar-grad-end: #22032c;
    --color-sidebar-text: #f0f0f0;
    --color-nav-item: #c9c9c9;

    --color-progress-track: #1a1a1a;

    --elevation-card: 0 1px 3px rgba(0, 0, 0, 0.4);
}

/* ==========================================================================
   Base
   ========================================================================== */

html, body {
    font-family: var(--font-family-base);
    font-size: var(--font-size-body);
    background-color: var(--color-surface);
    color: var(--color-text);
}

h1:focus {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 1100;
    padding: var(--space-2) var(--space-3);
    background: var(--color-action-primary);
    color: var(--color-action-primary-text);
}

.skip-link:focus {
    left: var(--space-2);
    top: var(--space-2);
}

a, .btn-link {
    color: var(--color-link);
}

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

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem var(--color-surface), 0 0 0 0.25rem var(--color-focus-ring);
}

.content {
    padding-top: 1.1rem;
}

/* ==========================================================================
   The session row
   ========================================================================== */

/* Scoped to main deliberately. `.top-row` is also the sidebar's own mobile
   header, styled in NavMenu.razor.css — a bare `.top-row` rule here would reach
   into that component and restyle it from a file it does not read. The child
   combinator keeps this rule on the one element it was written for. */
main > .top-row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    min-height: 3.5rem;
    border-bottom: 1px solid var(--color-stale-text);
}

/* DISABLED, ON PURPOSE, AFTER VISUAL REVIEW — not deleted. The sidebar stays
   fixed at desktop and tablet widths per the principal's own inspection; the
   toggle is kept out of view rather than torn out so the feature can return
   later without rebuilding it. The button, its click handler, the
   sidebarHidden state, aria-expanded wiring, and both tests in
   ThemeTriggerPlacementTests all still exist and still work — only visibility
   changed. To bring it back: restore the `display: inline-flex` rule inside
   the min-width: 641px media query directly below this comment, matching what
   this block looked like before this note was added. */
.top-row__sidebar-toggle {
    display: none;
    margin-right: auto;
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--color-stale-text);
    border-radius: var(--radius-default);
    background: none;
    color: var(--color-text);
    line-height: 1;
    cursor: pointer;
}

.top-row__sidebar-toggle:hover {
    border-color: var(--color-link);
    color: var(--color-link);
}

.top-row__sidebar-toggle:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

.top-row__sidebar-toggle-icon {
    font-size: var(--font-size-body);
}

@media (min-width: 641px) {
    /* .top-row__sidebar-toggle { display: inline-flex; } — commented out
       rather than removed; see the note above the base rule. */
}

.session-bar {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    font-size: var(--font-size-small);
}

.session-bar__holder {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
}

/* Quieter than the name it introduces, so the eye lands on "who" rather than
   on "signed in as". */
.session-bar__label {
    color: var(--color-stale-text);
}

.session-bar__name {
    font-weight: 600;
    color: var(--color-text);
}

.session-bar__credential {
    color: var(--color-stale-text);
}

/* A button that reads as a link, because ending a simulated session is not a
   primary action and a filled button here would out-rank the page's own. */
.session-bar__signout {
    padding: 0;
    border: 0;
    background: none;
    color: var(--color-link);
    text-decoration: underline;
}

/* Sized and coloured by the same .btn-outline-primary .btn-sm pair the match
   list uses for "View details", so the two read as one control vocabulary.
   Nothing is redeclared here — only the spacing that keeps it off the edge. */
.session-bar__signin {
    margin-left: var(--space-2);
}

/* ==========================================================================
   The match card
   ========================================================================== */

.match-card__name {
    font-size: var(--font-size-heading-3);
}

.match-card__meta,
.match-card__where,
.match-card__when {
    font-size: var(--font-size-small);
}

/* Capacity reads as a fact about the fixture, not as an alarm. Sold out is the
   one state worth a colour, and it is the app's danger token rather than a new
   one, because "you cannot get in" is the same category of news the record
   already uses that colour for. */
.match-card__slots {
    font-size: var(--font-size-small);
    color: var(--color-stale-text);
}

/* A statement about the reader's own record, so it reads as a link back to it
   rather than as another control on the card. */
.match-card__pending {
    font-size: var(--font-size-small);
}

.match-card__slots--none {
    color: var(--color-danger-surface);
    font-weight: 600;
}

/* The count line and the control that changes it. The count is a statement
   about the list, so it reads quietly; the button is the only thing below the
   grid, so it does not compete with the cards above it. */
.matches__count {
    font-size: var(--font-size-small);
    color: var(--color-stale-text);
    margin-bottom: var(--space-2);
}

.matches__show-more {
    margin-bottom: var(--space-4);
}

/* ==========================================================================
   Settings fields
   ========================================================================== */

.settings {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    max-width: 32rem;
}

.settings-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.settings-field__label {
    font-weight: 600;
    color: var(--color-text);
}

.settings-field__options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* A value, not a control. It carries no border and no box, because a read-only
   field dressed as an input is an invitation to type into something that will
   not keep what you typed. */
.settings-field__value {
    margin: 0;
    color: var(--color-text);
}

.settings__signout {
    align-self: flex-start;
}

/* The three appearance states, shaped as one control rather than three loose
   buttons: the current one is marked rather than removed, so the field always
   has the same shape and a screen reader is told which is active instead of
   inferring it from what is missing. */
.appearance-option {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-stale-text);
    border-radius: var(--radius-default);
    background: none;
    color: var(--color-text);
    font-size: var(--font-size-small);
    cursor: pointer;
}

/* The glyph beside each option's label. "System" ships both a laptop and a
   phone and the breakpoint decides which is true for the device in hand — the
   same 641px the rest of the app turns on, so there is one answer to "is this a
   phone" rather than a CSS one and a JavaScript one that can disagree. */
.appearance-option__icon {
    display: inline-flex;
}

.appearance-option__icon--wide {
    display: none;
}

@media (min-width: 641px) {
    .appearance-option__icon--wide {
        display: inline-flex;
    }

    .appearance-option__icon--narrow {
        display: none;
    }
}

.appearance-option--current {
    background-color: var(--color-action-primary);
    border-color: var(--color-action-primary-border);
    color: var(--color-action-primary-text);
}

.appearance-option:disabled {
    cursor: default;
    opacity: 0.6;
}

.appearance-option:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

/* ==========================================================================
   Forms
   ========================================================================== */

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--color-success);
}

.invalid {
    outline: 1px solid var(--color-danger);
}

.validation-message {
    color: var(--color-danger);
}

/* ==========================================================================
   Staleness
   --------------------------------------------------------------------------
   Its own token rather than a generic muted grey, because this is the one
   piece of secondary text in the app that must never read as decoration. A
   state that is knowingly out of date has to say so visibly enough to be
   registered at a barrier, on a phone, in a hurry.
   ========================================================================== */

.stale-text {
    color: var(--color-stale-text);
    font-size: var(--font-size-small);
}

/* ==========================================================================
   Error surfaces
   ========================================================================== */

#blazor-error-ui {
    /* A hard light-scheme override used to sit here, pinning this one banner's
       native controls and scrollbars to light regardless of the rest of the
       page. Triggering an error in dark mode therefore produced a light flash
       that read as a rendering bug rather than as an error message. Removed,
       not scoped: the banner now themes with the page like everything else. */
    background: var(--color-danger-surface);
    color: var(--color-danger-surface-text);
    bottom: 0;
    box-shadow: var(--elevation-banner);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .reload {
        color: var(--color-danger-surface-text);
    }

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: var(--space-2);
    }

.blazor-error-boundary {
    /* The data URI below is an embedded warning icon, not a colour
       declaration — the only hex left in this file outside the token block. */
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--color-danger-surface);
    padding: var(--space-3) var(--space-3) var(--space-3) 3.7rem;
    color: var(--color-danger-surface-text);
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* ==========================================================================
   Boot-time loading indicator
   ========================================================================== */

.loading-progress {
    position: absolute;
    display: block;
    width: 8rem;
    height: 8rem;
    inset: 20vh 0 auto 0;
    margin: 0 auto 0 auto;
}

    .loading-progress circle {
        fill: none;
        stroke: var(--color-progress-track);
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: var(--color-action-primary);
            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
            transition: stroke-dasharray 0.05s ease-in-out;
        }

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
    color: var(--color-text);
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Loading");
    }

/* ==========================================================================
   Misc
   ========================================================================== */

code {
    color: var(--color-code);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
