html, body {
    /* The design font, not the Blazor template's Helvetica default. Overlay
       content (EbDialog's card body) inherits from body, so leaving this as
       Helvetica made the Settings dialog read in a different typeface.

       The base size lives here for the same reason: the hub, the launcher and
       every dialog render outside `.note-trainer`, so putting it only on the
       app root left every overlay at the browser default. */
    font-family: var(--font-body, "Barlow Condensed", "Segoe UI", Arial, sans-serif);
    font-size: var(--font-base, 17px);
    margin: 0;
    padding: 0;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

#blazor-error-ui {
    background: lightyellow;
    /* Stated, not inherited. This is the one light surface in a dark-scheme app, and
       the UA default text colour under `color-scheme: dark` is white - which on
       lightyellow is an error message nobody can read. */
    color: #212529;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

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

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

/* app.css */

/* ===== Music Staff Note Animations ===== */
/* Global keyframes for scrolling notes - must be global to work with Blazor scoped CSS */

.scrolling-note {
    will-change: transform;
    animation: scrollLeftNote var(--scroll-duration, 5s) linear forwards;
}

.scrolling-paused {
    animation-play-state: paused;
}

@keyframes scrollLeftNote {
    from {
        transform: translateX(var(--start-x, 600px));
    }
    to {
        transform: translateX(var(--end-x, 100px));
    }
}

/* Note fade animations */
.note-fade-in {
    animation: noteFadeIn 0.3s ease-in forwards;
}

.note-fade-out {
    animation: noteFadeOut 0.3s ease-out forwards;
}

@keyframes noteFadeIn {
    from {
        opacity: 0;
        transform: scale(0.8);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}

@keyframes noteFadeOut {
    from {
        opacity: 1;
        transform: scale(1);
    }
    to {
        opacity: 0;
        transform: scale(0.8);
    }
}

/* ===== Design palette =====================================================
   These live here, on :root, rather than in GameContainer.razor.css.

   EbDialog renders its content OUTSIDE the component that declares it, into
   `div.eb-overlay`. Custom properties only inherit down the DOM tree, so
   anything defined on `.note-trainer` is invisible to that overlay, and a
   `.note-trainer ::deep .eb-...` rule can never match it either. Declaring
   the palette on :root is what makes the --eb-* mapping below actually reach
   the Settings dialog, the wizard and the tour.

   The practice hub is deliberately NOT an ElectricBlazor overlay - it is plain
   markup this component renders, so its styles live in the scoped
   GameContainer.razor.css rather than here.

   Specificity note: ElectricBlazor declares its own tokens at
   `[data-eb-theme="neon"]` (0,1,0). `:root[data-theme="..."]` is (0,2,0),
   so these win. Switch themes by changing data-theme on <html> in wwwroot/index.html.
   ========================================================================= */

/* Every theme is a dark one, so this is unconditional and sits on bare :root rather
   than alongside them -- it has to hold before data-theme is stamped on <html> too.

   This is what the browser reads to decide how to paint the parts of a control it
   draws itself. A <select>'s option list is an OS-level popup: no stylesheet reaches
   inside it, and without this it is painted light, so the drill setup dropdowns
   opened as near-black text on white in the middle of a dark app. Scrollbars, the
   caret, checkboxes and date pickers come along with it. */
:root {
    color-scheme: dark;
}

:root[data-theme="purple"],
:root[data-theme="ocean"],
:root[data-theme="mint"],
:root[data-theme="amber"] {
    --page-bg: #020c13;
    --page-bg-soft: #071622;
    --panel-bg: #06131d;
    --panel-bg-strong: #030d14;
    --panel-line: rgba(219, 181, 112, .72);
    --panel-line-soft: rgba(219, 181, 112, .34);
    --text: #f6f0e6;
    --muted: #b6b2ad;
    --paper: #fcfcfd;
    --paper-edge: #e6e9ed;
    --ink: #090d11;
    --accent: #8c4fc7;
    --accent-bright: #a767df;
    --accent-soft: #c891ef;
    --secondary: #52b6b0;
    --secondary-dark: #247a79;
    --warm: #ff6d62;
    --gold: #d7ad69;
    --success: #53aaa6;
    --shadow: 0 16px 42px rgba(0, 0, 0, .34);
    --radius-lg: 18px;
    --radius-md: 12px;
    --radius-sm: 9px;

    /* ----- Type -------------------------------------------------------------
       The app sizes almost everything in `em` off a single base, so --font-base
       scales the whole interface. --font-size-min is a genuine floor: small
       sizes are written `max(0.72em, var(--font-size-min))`, so shrinking the
       base can never drive a label below something readable. Raise the floor
       and the smallest text grows; the rest is untouched. */
    --font-base: 17px;
    --font-size-min: 13px;
    --font-body: "Barlow Condensed", "Segoe UI", Arial, sans-serif;
    --font-display: "Bebas Neue", "Barlow Condensed", Impact, sans-serif;

    /* Surfaces that were hardcoded rgba() literals before the skin maker: a
       skin can't recolour what isn't a token. */
    --surface-raised: rgba(255, 255, 255, .045);
    --surface-sunken: rgba(0, 0, 0, .22);
    --surface-hover: rgba(255, 255, 255, .07);
    --line-faint: rgba(255, 255, 255, .12);

    /* Map ElectricBlazor's tier-1 theme tokens onto our palette so EbDialog
       (Settings, the setup wizard, the report card) blends with the gold/dark
       aesthetic instead of the "neon" theme's default cyan accent. */
    --eb-accent: var(--gold);
    --eb-accent-soft: rgba(219, 181, 112, .2);
    --eb-accent-contrast: var(--ink);
    --eb-surface: var(--panel-bg-strong);
    --eb-surface-raised: var(--panel-bg);
    --eb-surface-sunken: var(--page-bg);
    --eb-border: var(--panel-line);
    --eb-border-strong: var(--panel-line);
    --eb-text: var(--text);
    --eb-text-muted: var(--muted);
    --eb-text-inverse: var(--ink);

    /* ElectricBlazor ships its component CSS as *scoped* bundles, so its own
       rules carry a [b-xxxxx] attribute -- specificity (0,2,0). A plain
       `.eb-edge-tab-toggle { font-family: ... }` here is (0,1,0) and loses.
       Re-pointing the token is the supported way in, and it reaches every
       component at once. */
    --eb-font-family: "Barlow Condensed", "Segoe UI", Arial, sans-serif;

    /* EbTour. Same situation as the dialogs and drawers above, and for the same
       reason: EbTourHost is mounted in MainLayout and renders its backdrop,
       spotlight and coachmark as siblings of the app -- so its tokens have to be
       declared here on :root, never in GameContainer.razor.css. */
    --eb-tour-spotlight-ring: var(--gold);
    --eb-tour-coachmark-accent: var(--gold);
    --eb-tour-coachmark-surface: var(--panel-bg-strong);
    --eb-tour-coachmark-text: var(--text);
    --eb-tour-coachmark-border: var(--panel-line);
    --eb-tour-coachmark-shadow: 0 18px 40px rgba(0, 0, 0, .45);

    /* EbWizard. Hosted inside an EbDialog, so it inherits the dialog surface, but
       its own tokens still have to be declared here rather than in the scoped
       GameContainer.razor.css -- the dialog renders into div.eb-overlay, outside
       this component's subtree, so a scoped rule could never reach it. */
    --eb-wizard-surface: transparent;
    --eb-wizard-border: var(--panel-line);
    --eb-wizard-radius: var(--radius-md);
    --eb-wizard-shadow: none;
    --eb-wizard-accent: var(--gold);
    --eb-wizard-accent-text: var(--ink);
    --eb-wizard-danger: var(--warm);
    --eb-wizard-min-height: 16rem;

    --eb-wizard-progress-track: var(--panel-line-soft, rgba(255, 255, 255, .12));
    --eb-wizard-progress-fill: var(--accent);
    --eb-wizard-progress-complete: var(--gold);

    --eb-wizard-field-surface: var(--panel-bg);
    --eb-wizard-field-border: var(--panel-line);
    --eb-wizard-field-border-invalid: var(--warm);
    --eb-wizard-field-accent: var(--gold);
    --eb-wizard-field-danger: var(--warm);

    --eb-wizard-checklist-surface: var(--panel-bg);
    --eb-wizard-checklist-accent: var(--gold);
    --eb-wizard-checklist-done: var(--gold);
    --eb-wizard-checklist-track: var(--panel-line-soft, rgba(255, 255, 255, .12));

    --eb-wizard-launcher-surface: var(--panel-bg);
    --eb-wizard-launcher-accent: var(--gold);
    --eb-wizard-launcher-accent-text: var(--ink);
}

:root[data-theme="ocean"] {
    --accent: #4f91dc;
    --accent-bright: #69adf4;
    --accent-soft: #a5cef5;
    --secondary: #5fb7dc;
    --secondary-dark: #2c7196;
    --warm: #ff704f;
    --gold: #cab374;
}

:root[data-theme="mint"] {
    --accent: #52bda8;
    --accent-bright: #6fd3be;
    --accent-soft: #a5e6d8;
    --secondary: #66aaa6;
    --secondary-dark: #2f7472;
    --warm: #ff6759;
    --gold: #d0ac75;
}

:root[data-theme="amber"] {
    --accent: #d9a338;
    --accent-bright: #efbd56;
    --accent-soft: #f2d38d;
    --secondary: #87904d;
    --secondary-dark: #59612d;
    --warm: #ee674e;
    --gold: #d5aa62;
}

/* ===== Native form controls ===============================================
   Global rather than scoped, and deliberately unqualified: selects appear both in
   GameContainer's own markup and inside the ElectricBlazor overlays, which render
   outside that component's subtree where a scoped rule can never reach them.

   color-scheme above is what actually makes the option popup dark. These name the
   colours so it matches the panel palette instead of the browser's generic grey,
   and they are what Firefox honours -- it styles options from CSS rather than from
   the colour scheme alone. The tokens carry literal fallbacks because a select can
   sit outside any element that inherits the palette.
   ========================================================================= */

option,
optgroup {
    background-color: var(--panel-bg-strong, #030d14);
    color: var(--text, #f6f0e6);
}

option:disabled {
    color: var(--muted, #b6b2ad);
}

/* ===== ElectricBlazor overlay integration =================================
   Global, not scoped: these elements are rendered outside the component
   subtree, so Blazor's scoped-CSS attribute (and ::deep) cannot reach them.
   ========================================================================= */

/* Typography comes from --eb-font-family above, which the components' own
   (higher-specificity) scoped rules read. Only sizing needs stating here. */
.eb-dialog.settings-dialog {
    --eb-dialog-width: min(92vw, 640px);
    --eb-dialog-max-height: 85vh;
}

/* Router <NotFound> fallback */
.route-not-found {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 24px;
    text-align: center;
    font-family: "Barlow Condensed", "Segoe UI", Arial, sans-serif;
    color: var(--text, #f6f0e6);
    background: var(--page-bg, #020c13);
}

.route-not-found a {
    color: var(--gold, #d7ad69);
}






/* The property list is the tall part; let it flow inside the scrolling column
   rather than sizing itself against the viewport. */


/* The library's own rule list is read-only, and the selector it commits to for an
   element with no class of its own is a positional path. Hidden in favour of the
   editable list rendered by GameContainer - two rule lists disagreeing about the
   same changeset would be worse than either alone. */
.live-css-editor-changeset {
    display: none;
}

/* ===== Design mode =========================================================
   The dock (GameContainer.razor.css) provides the panel; these rules only tidy
   the library's own markup inside it. They live here because that markup is the
   library's — scoped CSS would add the component's [b-xxxxx] and never match.
   ========================================================================== */

.live-css-editor {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.live-css-editor .element-picker-toggle {
    align-self: flex-start;
    padding: 9px 16px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--accent) 24%, transparent);
    color: var(--text);
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}

.live-css-editor .element-picker-toggle:hover {
    background: color-mix(in srgb, var(--accent) 40%, transparent);
}

.live-css-editor .element-picker-toggle.is-active {
    border-color: var(--warm);
    background: color-mix(in srgb, var(--warm) 30%, transparent);
}

.live-css-editor-hint,
.live-css-editor-status {
    margin: 0;
    font-size: max(0.86em, var(--font-size-min));
    opacity: .75;
}

/* The property list is the tall part; let it flow inside the dock's scrolling body
   rather than sizing itself against the viewport. */
.live-css-editor .editor-panel {
    position: static;
    width: 100%;
    max-height: none;
}

/* The library's own rule list is read-only, and the selector it commits to for an
   element with no class of its own is a positional path. Hidden in favour of the
   editable list in the dock — two rule lists disagreeing would be worse than either. */
.live-css-editor-changeset {
    display: none;
}

/* ----- The selection outline ------------------------------------------------
   The picker draws a box sized exactly to the element's rect, so its border sits
   ON the thing you are trying to look at. Redrawn as an outline standing 10px
   clear, with the fill dropped so nothing is tinted, and a slow pulse so it is
   findable on a busy screen.

   These beat the library's injected <style> (which lands later in the head) by
   specificity: `html .eb-…` is (0,1,1) against its (0,1,0). */

html .eb-element-picker-selected {
    border-color: transparent;
    background: transparent;
    outline: 2px solid rgba(255, 122, 0, .95);
    outline-offset: 10px;
    animation: design-selected-pulse 1.6s ease-in-out infinite;
}

@keyframes design-selected-pulse {
    0%, 100% {
        outline-offset: 10px;
        outline-color: rgba(255, 122, 0, .95);
    }
    50% {
        outline-offset: 16px;
        outline-color: rgba(255, 122, 0, .35);
    }
}

/* Hover keeps a fill (it is how you tell what you are about to pick) but stands
   off too, so it never hides the element's own edge. */
html .eb-element-picker-highlight {
    border-color: transparent;
    outline: 1px solid rgba(66, 133, 244, .9);
    outline-offset: 6px;
}

@media (prefers-reduced-motion: reduce) {
    html .eb-element-picker-selected {
        animation: none;
    }
}

/* ===== Dialogs that are taller than the dialog ==============================
   EbDialog's card is a flex column capped by max-height with overflow:hidden,
   and its body wrapper neither scrolls nor shrinks. Any content taller than the
   cap is therefore simply cut off with no way to reach it.

   That made onboarding impossible to finish: the self-assessment step is ~1334px
   against an 850px cap, and the wizard's own Next button sits at the bottom of
   that content, so it was clipped away entirely. The step looked like it had no
   Next button because, on screen, it didn't.

   Global rather than scoped: the card is EbDialog's markup, rendered into
   div.eb-overlay outside this component's subtree.
   ========================================================================== */

.eb-overlay .eb-card-body-wrapper {
    /* min-height:0 is what lets a flex child shrink below its content height;
       without it overflow-y never engages and the clipping stays. */
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Keep the wizard's controls on screen rather than making people scroll to the
   bottom of a long step to find Next. */
.eb-overlay .eb-wizard-footer {
    position: sticky;
    bottom: 0;
    z-index: 2;
    padding-top: 10px;
    padding-bottom: 4px;
    background: var(--eb-surface, var(--panel-bg-strong));
    box-shadow: 0 -8px 16px -8px rgba(0, 0, 0, .6);
}
