/* ============================================================
   Navigator - shared app stylesheet
   Design tokens from navigator-design-system (Figma handover).
   Tokens now live in css/ssytokens.css - that is the only file with raw hex.
   Everything here uses var(--ssy-*).
   ============================================================ */

/* ---------- Design tokens ----------
   Every colour token (plus the type / spacing / motion scales) now lives in
   css/ssytokens.css - the one file allowed to contain raw hex. Imported first
   so this file and every stylesheet imported below can read them. */
@import url("css/ssytokens.css");

/* ---------- Fonts (Söhne, Klim Type Foundry) ----------
   @font-face declarations live in css/fonts.css (one .woff2 per cut in wwwroot/fonts).
   Söhne Schmal (condensed display) is now bundled, so Display headings render correctly. */
@import url("css/fonts.css");

/* ---------- App shell (MainLayout) ----------
   All Nav Main / Nav Child / Nav Title / page-frame styles live in css/MainLayout.css as ordinary
   global CSS (no isolation, no ::deep). Imported here so every host and render mode gets it.
   Its `mainlayout-` classes are RESERVED to MainLayout.razor - pages must not use them. */
@import url("css/MainLayout.css");

/* ---------- Shared element library (ssynavigator.css) ----------
   All `ssy-` prefixed reusable elements - buttons first, more to follow. Imported AFTER
   MainLayout.css so the shell can use `ssy-btn` and the button rules land last.
   Rules for that file are documented in its header; the short version: `ssy-` prefix,
   tokens not hex, no breakpoints, full state set per element. */
@import url("css/ssynavigator.css");

/* ---------- Icons (ssyicons.css) ----------
   GENERATED - `navigator-design-system/build-icons.py` writes it from
   `icons.manifest.txt`. One `.ssy-i-<name>` per Figma glyph, each a CSS mask, so an
   icon is one <span>, one DOM node and no Blazor component. Set `color`, never
   `background-color`. Imported after ssynavigator.css so `.ssy-btn` can host one. */
@import url("css/ssyicons.css");

/* ---------- Account screens (ssyaccount.css) ----------
   The /Account/* element set - fields, inputs, checkboxes, status banners, link rows. Imported AFTER
   ssynavigator.css so it can lean on `ssy-btn` and land last.

   Global rather than scoped to AccountLayout on purpose: scoped CSS reaches only one component's own
   markup, so AccountLayout.razor.css cannot style a class used inside Login.razor. AccountLayout's
   own panels ARE scoped; everything inside the form lives here.

   ⚠ This is the file to REPLACE when the designers deliver the account screens. Keep new account page
   styling in here rather than in per-page .razor.css files, or that swap stops being one file. */
@import url("css/ssyaccount.css");

/* ---------- Base ----------

   THE CANVAS COLOUR (added 2026-09-14). html/body carried no background, so the document canvas was
   the browser default - white - underneath an app whose default theme is dark. Nothing revealed it
   on desktop: the layout root is `height: 100dvh` and paints over the lot. A phone does reveal it.
   Focus an input, the native keyboard comes up, iOS scrolls and rubber-bands the document to get the
   field above it, and the strip that opens up is CANVAS, not layout - so it flashed white against a
   navy app and the whole screen appeared to change colour as the keyboard animated in.

   So the base tracks the app surface instead. Two things make this look odder than it is:

   • `data-theme` lives on the layout root DIV, never on <html> (MainLayout / EmptyLayout /
     AccountLayout all set it there - see the note in ssytokens.css §4d). The semantic tokens
     therefore do not resolve up at <html>, which is why this reaches for the PRIMITIVES: those are
     declared on :root and do resolve. `html:has([data-theme="light"])` reads the theme back UP the
     tree, and re-evaluates on its own when the mode toggle rewrites the attribute.
   • Dark is the app default (`isLight = false`), so dark is the unqualified value here.

   The dark value is surface-FLAT (#14142D, = the `theme-color` meta, so the Android status bar, the
   canvas and the top of the app are one colour). --ssy-surface proper is a gradient that ends at
   #000061 at the BOTTOM of the viewport, which is the edge the keyboard actually meets; if the seam
   there still reads, --ssy-simpson-500 is the value to swap in. */

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--ssy-font-body);
    font-size: 16px;
    background-color: var(--ssy-dark-simpson-500);
}

html:has([data-theme="light"]),
html:has([data-theme="light"]) body {
    background-color: var(--ssy-neutral-white);
}

/* ---------- Scrollbars ----------

   The app had no scrollbar colour at all, so every scroller - the shell's, the popup bodies, the
   Intraday strips - rendered in the PLATFORM's colours. On Windows that is a light-grey bar on a
   near-white track, which is invisible in light mode and a bright stripe down the side of a navy app
   in dark. `wwwroot/css/ssyglobal.css` does carry `scrollbar-color` rules (`.ssy-overflow-auto-lw` /
   `-dw`), but that file is dead - see the block below - and they were opt-in per element anyway.

   ONE PAIR OF RULES COVERS EVERY SCROLLER IN THE APP, because `scrollbar-color` is an INHERITED
   property: set on <html> it reaches `.mainlayout-page`, the popup bodies, the curve tables and
   anything added later, with no per-component opt-in and nothing to remember. Components keep
   overriding `scrollbar-width` where they want a thin bar; that is a separate property and is not
   touched here.

   Placed on <html> rather than on the layout roots for the same reason the canvas colour above is:
   under EmptyLayout (login, error) there is no inner scroll container and the DOCUMENT scrolls, so
   the bar that needs colouring belongs to <html> itself. That also forces the primitives - the
   semantic `--ssy-*` tokens are declared inside `[data-theme]`, which is on the layout root DIV and
   so does not resolve up here - and the same `html:has([data-theme="light"])` read-the-theme-upwards
   trick. Read the canvas note above before changing either.

   Track is `transparent`, not a colour: the thumb then floats over whatever is actually behind it
   (page surface, nav sheet, popup), so one rule works on all of them and no track seam appears
   where two surfaces meet. The thumbs are the neutral ramp at the step that stays legible on both
   of its own backgrounds - 300 (#727281) on the dark surface, which runs #14142D to #000061, and
   200 (#A1A1AB) on white - deliberately quiet rather than branded: a scrollbar is furniture.

   ⚠ `scrollbar-color` is ignored by Blink/WebKit on any element that also has `::-webkit-scrollbar`
   rules. Nothing of ours uses those (only vendored ext/ libraries - pdf.js, DataTables - which own
   their own chrome). Do not introduce them without moving this whole block to match. */
html {
    scrollbar-color: var(--ssy-dark-simpson-300) transparent;
}

html:has([data-theme="light"]) {
    scrollbar-color: var(--ssy-dark-simpson-200) transparent;
}

/* ════ THE DOCUMENT NEVER SCROLLS BEHIND THE APP SHELL ═════════════════════════════════════════
   A SITE-WIDE INVARIANT, not a preference:

       While the app shell is on screen the ONLY scrollbar is `.mainlayout-page`'s.
       Content longer than the page scrolls INSIDE the frame, under the title bar.

   WHAT WENT WRONG. `height: 100%` above pins html and body to the viewport, but nothing CLIPPED
   them, so anything that ended up taller than the shell - a stray margin, an absolutely-positioned
   element measured against the initial containing block, a script that appends a node - grew the
   document and gave it its own full-height scrollbar, right beside the shell's. Seen 2026-09-15 on
   /Derivatives/Intraday: the shell reduced to a sliver at the top of a document several times the
   viewport height, with nothing at all in the space below it.

   ⚠ AND THE RULE EVERYONE THOUGHT WAS PREVENTING THIS WAS NEVER RUNNING. `wwwroot/css/ssyglobal.css`
   carries `html { overflow-y: hidden }` with the comment "Add this line" - and **that file is not
   imported by anything**. This file is the entry point (App.razor links `app.css` and
   `Navigator.Web.styles.css`, nothing else) and its @import list is ssytokens, fonts, MainLayout,
   ssynavigator, ssyicons, ssyaccount. ssyglobal is dead. Before believing any global rule, check it
   is in that list.

   WHY CLIPPING AND NOT JUST SIZING. `.mainlayout-root` is `height: 100dvh; overflow: hidden`, so
   everything the user should ever see is already inside a viewport-sized box that delegates its
   scrolling to `.mainlayout-page`. Nothing outside that box is part of the layout. Clipping the
   document makes a second scrollbar STRUCTURALLY IMPOSSIBLE rather than merely unlikely. `overflow`
   is set on `html` explicitly as well as `body`, because a lone `body` value propagates to the
   viewport and the result then depends on which element you happened to style.

   ⚠ SCOPED WITH `:has()` - IT MUST NOT TOUCH THE LOGIN AND ERROR PAGES. Those use EmptyLayout
   (`.emptylayout-root`), which has no inner scroll container, so clipping the document there would
   make a form taller than the window unreachable instead of scrollable. The condition is the
   shell's PRESENCE, not the route, so a new layout gets the right behaviour by having or not having
   `.mainlayout-root`. Same technique as the theme rule directly above.

   ⚠ THIS GUARANTEES THE SYMPTOM CANNOT RECUR; IT DOES NOT DIAGNOSE A CAUSE. If something genuinely
   renders thousands of pixels tall outside the shell, this makes it invisible rather than
   scrollable - right for the user, quiet for whoever is debugging. To hunt one, comment the clip out
   and look for a body descendant outside `.mainlayout-root` with a real height. Do NOT relax this
   rule to "see" a future overflow; fix what is overflowing. */
html:has(.mainlayout-root),
html:has(.mainlayout-root) body {
    overflow: hidden;
}


*, *::before, *::after {
    box-sizing: border-box;
}

h1:focus {
    outline: none;
}

/* ============================================================
   App shell - Nav Main / Nav Child / Nav Title / Page
   (region names per dev-design-decisions.md)

   All shell styles live in css/MainLayout.css (imported at the top of this file). Nothing for the
   shell belongs here - see the rules at the head of that file.
   ============================================================ */

/* Shared in-page title utility - Söhne Schmal display cut, uppercase (Figma page-title, 32px).
   Applied to a page's main <h1>. A PAGE-facing utility, so it is deliberately NOT a `mainlayout-`
   class (those are reserved to the layout). Used by DepartmentPage, MyAccount, MyDashboard,
   LearningHub, MySsy. */
.ssy-page-title {
    font-family: var(--ssy-font-display);
    font-weight: 700;
    font-size: clamp(1.75rem, 4vw, 2rem);
    letter-spacing: 0;
    text-transform: uppercase;
    color: var(--ssy-on-surface);
    margin: 0 0 var(--ssy-sp-05);
}



/* ---------- Blazor framework bits ---------- */

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--ssy-validation-valid);
}

.invalid {
    outline: 1px solid var(--ssy-validation-invalid);
}

.validation-message {
    color: var(--ssy-validation-invalid);
}

.blazor-error-boundary {
    background: var(--ssy-error-ui-bg);
    padding: 1rem;
    color: white;
    border-radius: var(--ssy-radius-md);
}

    .blazor-error-boundary::after {
        content: "An error has occurred.";
    }

/* ============================================================
   Reduced motion (rewritten 2026-07-28, later)

   The old version was `*, *::before, *::after { animation-duration: .01ms !important; … }` - a
   carpet-bomb that silently killed EVERY animation and transition in the app, including the Nav
   Child level slide, the panel width transition and all hover fades. On a machine with Windows
   "Animation effects" turned off (which reports prefers-reduced-motion: reduce) the whole nav
   looked broken rather than reduced, and no amount of rebuilding changed it. **Do not reinstate
   a global `*` !important block.**

   Instead: reduce the motion, don't delete it. Travel (translate/parallax) is what reduced-motion
   users actually object to, so the level slide becomes a short cross-fade and the decorative
   nudges/drifts are dropped. Everything stays legible and visibly working either way.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {

    /* Nav Child levels: cross-fade in place instead of sliding across. */
    .mainlayout-navchild-level-current.mainlayout-navchild-level-deeper,
    .mainlayout-navchild-level-current.mainlayout-navchild-level-back {
        animation: mainlayout-navchild-level-fade var(--ssy-motion-base) linear;
    }

    /* The outgoing layer only has to be held for the length of the fade. */
    .mainlayout-navchild-level {
        animation-duration: var(--ssy-motion-base);
    }

    /* Panel open/close: snap rather than slide the content column. */
    .mainlayout-navchild,
    .mainlayout-navmain {
        transition-duration: 0s;
    }

    /* Drop the decorative travel: chevron nudges, tooltip drift, popup rise. */
    /* (the Overview CTA's chevron is now `.ssy-btn-chevron` - handled in ssynavigator.css) */
    .mainlayout-navchild-chevron,
    .mainlayout-navchild-back-chevron,
    .mainlayout-navmain-item::after {
        transition-property: opacity, color;
        transform: none !important;
    }

    /* Renamed from .mainlayout-settings-popup 2026-09-01 when the sheet was rebuilt from Figma. */
    .mainlayout-usermenu {
        animation: none;
    }

    html {
        scroll-behavior: auto;
    }
}

/* ============================================================
   THE FRONT-DOOR BOOT SCREEN (2026-09-30)
   ------------------------------------------------------------
   /home is a WebAssembly app rendered with NO prerender (App.razor's IsFrontDoor), so until the runtime
   has downloaded and started the body holds nothing of the page. App.razor renders this static block in
   its place: the signed-out shell's ground, the SSY mark, a bar that follows --blazor-load-percentage
   (which blazor.web.js sets on <html> as the runtime's files arrive) and one line of text for a screen
   reader. The block carries data-theme="dark" so the --ssy-login-* tokens resolve on it - they are
   declared per theme (see the canvas note above) - and it paints the same ground the shell paints, so
   the hand-over is a change of content, not of colour.

   Hidden the moment the app has rendered a layout root, whichever layout that is (the front door's own,
   or MainLayout if routing falls through to the temporary page); Home.razor.js also removes it outright.
   Global rather than scoped on purpose: it exists before any component does. The dashboard renders the
   same block from inside the app (.ssy-boot-page, at the foot of this section).
   ============================================================ */
.ssy-boot {
    position: fixed;
    inset: 0;
    z-index: 0;

    display: grid;
    place-items: center;
    align-content: center;
    gap: var(--ssy-sp-06);

    background: var(--ssy-login-base);
    color: var(--ssy-on-surface);
    font-family: var(--ssy-font-body);
}

.ssy-boot-mark {
    display: block;
    width: 117px;
    height: 79px;
    background-color: currentColor;
    -webkit-mask: var(--ssy-login-mark) no-repeat center / contain;
            mask: var(--ssy-login-mark) no-repeat center / contain;
    animation: ssy-boot-pulse 1.8s ease-in-out infinite;
}

.ssy-boot-bar {
    position: relative;
    display: block;
    width: 160px;
    height: 2px;
    overflow: hidden;
    border-radius: 1px;
    background: var(--ssy-login-tab-line);
}

.ssy-boot-bar::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--blazor-load-percentage, 0%);
    background: var(--ssy-login-tab-grad-b);
    transition: width 200ms linear;
}

.ssy-boot-text {
    margin: 0;
    font-size: var(--ssy-text-body);
    color: var(--ssy-login-muted);
}

/* Once the download is done the runtime is starting, which the bar cannot show: it pulses instead
   (App.razor's inline script sets data-stage). After 30 s the second line offers a reload. A start that
   failed ("failed") stops pulsing: #blazor-error-ui says so and offers the reload. */
.ssy-boot[data-stage]:not([data-stage="failed"]) .ssy-boot-bar::after {
    animation: ssy-boot-pulse 1.2s ease-in-out infinite;
}

.ssy-boot-slow {
    margin: 0;
    max-width: 34ch;
    text-align: center;
    font-size: var(--ssy-text-body);
    line-height: 1.4;
    color: var(--ssy-login-muted);
}

.ssy-boot-slow[hidden] {
    display: none;
}

.ssy-boot-slow a,
.ssy-boot-error a {
    color: var(--ssy-on-surface);
    text-decoration: underline;
}

/* Blazor's error surface (#blazor-error-ui): hidden until the framework shows it.
   The id is in the selector on purpose. MainLayout.css styles #blazor-error-ui for the app (a full-width
   light-yellow bar), and an id beats a class, so with `.ssy-boot-error` alone the front door's line came
   out as white text on light yellow, half off the left edge - invisible until 2026-10-09, when the boot
   script started showing it for a failed start. width and box-shadow undo the two properties of that rule
   this one does not otherwise set. */
#blazor-error-ui.ssy-boot-error {
    display: none;
    position: fixed;
    left: 50%;
    bottom: var(--ssy-sp-06);
    z-index: 1;
    translate: -50% 0;
    width: auto;
    box-shadow: none;
    padding: var(--ssy-sp-04) var(--ssy-sp-05);
    border: 1px solid var(--ssy-login-tab-line);
    border-radius: 8px;
    background: var(--ssy-login-base);
    color: var(--ssy-on-surface);
    font-family: var(--ssy-font-body);
    font-size: var(--ssy-text-body);
}

body:has(.signedout-root, .mainlayout-root) .ssy-boot:not(.ssy-boot-page) {
    display: none;
}

/* THE SAME SCREEN, HELD BY A PAGE (2026-10-01). The dashboard is signed-in only, and it renders this
   block itself - inside MainLayout, in place of its content - until it has confirmed who is looking;
   a stranger keeps it while the page sends them to /home (MyDashboard.razor's header). So it is
   exempt from the rule above, which hides the front door's copy once a layout exists, and it sits
   over the shell rather than under it: above everything MainLayout paints (the highest is the
   SSY-Internal frame at 200), below --ssy-popup-z (970) and #blazor-error-ui (1000), so a framework
   error still shows over it. It goes when the page renders the dashboard in its place. */
.ssy-boot.ssy-boot-page {
    z-index: 900;
}

@keyframes ssy-boot-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .ssy-boot-mark { animation: none; }
    .ssy-boot-bar::after { transition: none; }
}

/* THE /home ZOOM'S TWO VARIABLES ARE NOT INHERITED (2026-10-12). Home.razor.js writes --home-zoom and --home-settle on
   every scroll frame, and an unregistered custom property is inherited: the write restyled everything under the
   element it was set on, which was the whole page (880 ms of style recalculation in a 3.5 s scroll at 4x CPU).
   Registered with `inherits: false`, a write restyles only the element it is set on. The script sets them on the
   two elements that read them, .home-stage-pin and .home-frame (Home.razor.css), and nothing else does. A browser
   without @property (before iOS 16.4) ignores these rules and falls back to the old inherited behaviour: slower,
   not broken. Global rather than in Home.razor.css: the scoped-CSS rewriter is not guaranteed to leave an
   at-rule with declarations alone. */
@property --home-zoom {
    syntax: '<number>';
    inherits: false;
    initial-value: 0;
}

@property --home-settle {
    syntax: '<number>';
    inherits: false;
    initial-value: 0;
}
