/* Values come from css/stn-tokens.css, which _Layout.cshtml links immediately before this file. */

html, body {
    font-family: var(--stn-font-sans);
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: var(--stn-blue);
}

.content {
    padding-top: 1.1rem;
}

/* The app bar is rendered with Fixed="false" (MainLayout), so it sits in normal flow and takes
   its own height. MudMainContent still adds a padding-top sized to clear a *fixed* bar, which
   here is pure empty space stacked under the bar. Trim it to a small breathing gap. */
.mud-main-content {
    padding-top: 8px !important;
}

/* Brand panel treatment: a faint version of the landing hero gradient, shared by the main content
   panels across pages (chat, profile, admin, …) so the app reads as one product rather than a set
   of bare grey cards. Add the `stn-panel` class to a MudPaper to opt in. Kept low-alpha so it
   never fights the panel's own text or controls for contrast. */
.stn-panel.mud-paper {
    background: var(--stn-wash-panel), var(--mud-palette-surface) !important;
    border: 1px solid var(--stn-hairline-soft);
    border-radius: var(--stn-r-lg);
}

/* ---------------------------------------------------------------------------
   Branded top bar. The wordmark (a spinning shuriken + "Smalltalk" / gradient
   "Ninja") lives in the app bar, so the bar wears a faint version of the hero
   gradient — both to give the gradient text a dark backdrop to read against and
   to match the landing and chat.
   --------------------------------------------------------------------------- */
.mud-appbar {
    background: var(--stn-wash-appbar), linear-gradient(100deg, #20202c 0%, #171720 100%) !important;
    border-bottom: 1px solid var(--stn-hairline);
    /* Frosted rather than opaque: content scrolling under the bar stays faintly visible, which is
       the main thing that separates a current-looking app bar from a flat 2019 one. The solid
       gradient above is the fallback where backdrop-filter is unsupported. */
    backdrop-filter: saturate(1.4) blur(10px);
    -webkit-backdrop-filter: saturate(1.4) blur(10px);
}

.stn-appbrand-link {
    display: inline-flex;
    text-decoration: none;
    border-radius: var(--stn-r-sm);
    transition: filter var(--stn-dur) var(--stn-ease);
}
.stn-appbrand-link:hover { filter: brightness(1.12); }
.stn-appbrand-link:focus-visible { outline: 2px solid var(--stn-blue-pale); outline-offset: 3px; }

/* App-bar variant of the shared wordmark (<StnWordmark Class="stn-appbrand" />). The bar is dense,
   so the text is pinned to a fixed size rather than the hero's viewport clamp, and the accent uses
   the paler gradient — the standard one is too dim to read at this size against the bar. */
.stn-appbrand { gap: 9px !important; margin-left: var(--stn-s-1); }
.stn-appbrand .stn-brand-mark {
    filter: drop-shadow(0 1px 6px rgba(var(--stn-blue-rgb), 0.6));
}
.stn-appbrand .stn-brand-text { font-size: 1.18rem; letter-spacing: 0.01em; }
/* background-IMAGE, not the `background` shorthand: the shorthand resets background-clip back to
   border-box, which paints the gradient over the whole span and — with text-fill-color transparent
   — turns the wordmark into a solid gradient rectangle. */
.stn-appbrand .stn-brand-accent { background-image: var(--stn-grad-text-soft); }

/* ===========================================================================
   Shared brand utility layer.

   These were originally written inline in Index.razor's <style> block and were
   the reason the landing page looked like a different product from the rest of
   the app: no other page could reach them. They now live here, token-driven, so
   any page (and the Razor-Pages side, which never loads MudBlazor) can use the
   same hero, kicker, CTA, card and prose treatments.
   =========================================================================== */

/* The shuriken's slow rotation. Shared by the app-bar mark and the landing hero
   mark, so it lives here rather than in either page. */
@keyframes stn-brand-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .stn-appbrand-mark, .stn-brand-mark { animation: none; }
}

/* ---- Page shell ----------------------------------------------------------
   One container strategy for interior pages, replacing the previous mix of
   MudContainer.Medium (prose pages), inline max-width:640px (redeem, codes),
   and full-bleed (profile, admin, notifications). */
.stn-page {
    max-width: var(--stn-page-max);
    margin: 0 auto;
    padding: var(--stn-s-2) 0 var(--stn-s-12);
}
.stn-page--read { max-width: var(--stn-read-max); }
.stn-page--form { max-width: var(--stn-form-max); }

/* ---- Kicker: the small uppercase label above a title --------------------- */
.stn-kicker {
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: var(--stn-track-kicker);
    text-transform: uppercase;
    color: var(--stn-blue-pale);
    margin-bottom: var(--stn-s-3);
}

/* ---- Page header (StnPageHeader.razor) -----------------------------------
   The interior-page counterpart to the landing hero: kicker + title + lede over
   a gradient hairline. Gives Profile/Admin/Redeem/etc. a branded entrance
   without the full hero's weight. */
.stn-page-header { margin: var(--stn-s-6) 0 var(--stn-s-6); }
.stn-page-header-title {
    margin: 0 0 var(--stn-s-2);
    font-size: clamp(1.5rem, 3.2vw, 2rem);
    font-weight: 800;
    letter-spacing: -0.018em;
    line-height: 1.15;
    color: var(--stn-text);
}
.stn-page-header-lede {
    margin: 0;
    max-width: 68ch;
    color: var(--stn-text-dimmer);
    line-height: 1.6;
}
.stn-page-header-rule {
    height: 1px;
    margin-top: var(--stn-s-5);
    background: var(--stn-grad-hairline);
}

/* ---- Hero ---------------------------------------------------------------- */
.stn-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--stn-r-xl);
    /* Plain value first, fluid one second: a browser without clamp() (pre-Chrome
       79) drops the second and keeps a sane fixed padding rather than falling all
       the way back to none. Same pattern as the hero rules in Index.razor. */
    padding: 32px 24px;
    padding: clamp(32px, 6vw, 72px) clamp(24px, 5vw, 56px);
    margin-bottom: var(--stn-s-6);
    background: var(--stn-wash-hero), var(--stn-grad-hero-base);
    border: 1px solid var(--stn-hairline);
}
.stn-hero-title {
    margin: 0 0 var(--stn-s-4);
    font-size: clamp(2rem, 5.5vw, 3.4rem);
    line-height: 1.06;
    font-weight: 800;
    letter-spacing: -0.022em;
    color: var(--stn-text);
}
.stn-hero-sub {
    margin: 0;
    max-width: 60ch;
    font-size: clamp(1rem, 1.6vw, 1.2rem);
    line-height: 1.6;
    color: var(--stn-text-dim);
}

/* ---- Brand wordmark (StnWordmark.razor) ---------------------------------- */
.stn-brand { display: inline-flex; align-items: center; gap: 11px; }
.stn-brand-mark {
    flex-shrink: 0;
    filter: drop-shadow(0 2px 9px rgba(var(--stn-blue-rgb), 0.5));
    animation: stn-brand-spin 14s linear infinite;
}
.stn-brand-text {
    font-size: clamp(1.2rem, 2.3vw, 1.6rem);
    font-weight: 800;
    letter-spacing: 0.005em;
    line-height: 1;
    color: var(--stn-text);
}
.stn-brand-accent {
    background: var(--stn-grad-text);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}

/* ---- Gradient text ------------------------------------------------------- */
.stn-gradient-text {
    background: var(--stn-grad-text);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}

/* ---- Primary CTA pill ----------------------------------------------------
   A gradient cannot be a MudBlazor palette value, so the brand's loudest button
   stays a CSS utility applied to a MudButton via Class="stn-cta". */
.stn-cta.mud-button-root {
    padding: 14px 36px;
    font-size: 1.12rem;
    font-weight: 700;
    text-transform: none;
    border-radius: var(--stn-r-pill);
    color: #fff !important;
    background: var(--stn-grad-brand) !important;
    box-shadow: var(--stn-glow-blue) !important;
    transition: transform var(--stn-dur) var(--stn-ease),
                box-shadow var(--stn-dur), filter var(--stn-dur);
}
.stn-cta.mud-button-root:hover {
    transform: translateY(-2px);
    filter: brightness(1.06);
    box-shadow: var(--stn-glow-purple) !important;
}
.stn-cta .mud-icon-root { font-size: 1.4rem; }

/* Secondary CTA — readable on the dark hero without competing with the pill. */
.stn-cta-ghost.mud-button-root {
    padding: 12px 26px;
    border-radius: var(--stn-r-pill);
    text-transform: none;
    font-weight: 600;
    color: #fff !important;
    border: 1px solid rgba(255, 255, 255, 0.5);
}
.stn-cta-ghost.mud-button-root:hover {
    border-color: #fff;
    background: rgba(255, 255, 255, 0.08);
}

/* ---- Card ---------------------------------------------------------------- */
.stn-card {
    display: flex;
    flex-direction: column;
    gap: var(--stn-s-3);
    padding: 28px !important;
    background: var(--stn-surface) !important;
    border: 1px solid var(--stn-hairline) !important;
    border-radius: var(--stn-r-lg) !important;
    transition: transform var(--stn-dur) var(--stn-ease),
                border-color var(--stn-dur), box-shadow var(--stn-dur);
}
.stn-card:hover {
    transform: translateY(-3px);
    border-color: rgba(var(--stn-blue-rgb), 0.55) !important;
    box-shadow: var(--stn-e-3);
}
.stn-card-icon {
    width: 52px; height: 52px;
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--stn-r-md);
    background: rgba(var(--stn-blue-rgb), 0.14);
    color: var(--stn-blue-bright);
}

/* ---- Soft feature tile --------------------------------------------------- */
.stn-tile {
    display: flex;
    gap: 14px;
    padding: 18px;
    border-radius: var(--stn-r-md);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--stn-hairline-soft);
}
.stn-tile .mud-icon-root { color: var(--stn-blue-bright); flex-shrink: 0; margin-top: 2px; }

/* ---- Section: a lightly-washed block on a page --------------------------- */
.stn-section {
    margin: var(--stn-s-8) 0 var(--stn-s-6);
    padding: 28px clamp(20px, 4vw, 34px);
    border-radius: var(--stn-r-lg);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--stn-hairline);
}

/* ---- Prose (Rules / Privacy / Contact) -----------------------------------
   These three pages each carried a near-identical private style block; this is
   the shared version. */
.stn-prose { color: var(--stn-text-dim); }
.stn-prose h2, .stn-prose h3 { color: var(--stn-text); }
.stn-prose ul { margin: var(--stn-s-2) 0; padding-left: var(--stn-s-5); }
.stn-prose li { margin: 6px 0; font-size: 0.9rem; line-height: 1.55; }
.stn-prose li b, .stn-prose strong { color: var(--stn-text); font-weight: 600; }
.stn-prose p { line-height: 1.65; }
.stn-prose a { color: var(--stn-blue-bright); }

/* ---- Statistics: tiles, trend strip, distribution rows -------------------
   The admin dashboard's vocabulary. Kept here rather than in the page's own
   <style> block because a number on a card is a design-system shape, not a
   one-page decoration — the settings page reuses the tile for its own summary. */
.stn-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
    gap: 14px;
}
.stn-stat {
    padding: 16px 18px;
    border-radius: var(--stn-r-md);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--stn-hairline-soft);
}
.stn-stat-value {
    font-size: 1.65rem;
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: var(--stn-text);
}
.stn-stat-value.stn-muted { color: var(--stn-text-dimmer); }
.stn-stat-value.stn-warn { color: #ffb74d; }
.stn-stat-label {
    margin-top: 7px;
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--stn-text-dimmer);
}
.stn-stat-note {
    margin-top: 5px;
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--stn-text-dim);
}

/* Thirty bars, one per day. Deliberately unlabelled below: the shape is the
   information, and the exact figure is in the tooltip. */
.stn-bars {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 76px;
    margin-top: var(--stn-s-2);
}
.stn-bar {
    flex: 1 1 0;
    min-width: 2px;
    min-height: 2px;
    border-radius: 3px 3px 0 0;
    background: var(--stn-grad-brand);
    opacity: 0.85;
}
.stn-bar:hover { opacity: 1; }
.stn-bar-empty { background: rgba(255, 255, 255, 0.07); }

.stn-dist { display: flex; flex-direction: column; gap: 9px; }
.stn-dist-row {
    display: grid;
    grid-template-columns: minmax(84px, 30%) 1fr auto;
    gap: 12px;
    align-items: center;
    font-size: 0.86rem;
    color: var(--stn-text-dim);
}
.stn-dist-track {
    height: 8px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.06);
    overflow: hidden;
}
.stn-dist-fill { height: 100%; border-radius: 4px; background: var(--stn-grad-brand); }
.stn-dist-count { color: var(--stn-text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* A pair of numbers on one line — "what is running" against "what was saved". */
.stn-setting-row { padding: 14px 0; border-bottom: 1px solid var(--stn-hairline-soft); }
.stn-setting-row:last-child { border-bottom: none; }
.stn-setting-help {
    margin-top: 4px;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--stn-text-dimmer);
    max-width: 76ch;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    background: lightyellow;
    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 {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.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."
    }

/* ---------------------------------------------------------------------------
   SignalR reconnection overlay.
   Blazor toggles the components-reconnect-* classes on #components-reconnect-modal
   (defined in _Layout.cshtml) as the circuit drops, retries, and fails. We style
   all four states here so the framework's default white box is replaced by a dark
   panel that matches the MudBlazor theme. Colours come from the MudThemeProvider's
   CSS variables (with dark fallbacks) so the panel tracks the palette.
   --------------------------------------------------------------------------- */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: var(--stn-scrim-strong);
    backdrop-filter: blur(2px);
    font-family: var(--stn-font-sans);
}

/* Blazor adds exactly ONE of these at a time (its removeClasses() strips the rest first), and any
   of them means "show the overlay". All six must be listed: a state missing from this selector
   leaves the overlay hidden over a page whose circuit is already dead — the user sees a normal-
   looking page that has silently stopped responding. `paused` and `resume-failed` were missing. */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

.stn-reconnect-panel {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    width: min(92vw, 460px);
    /* Floor the height so the shortest state (the busy stand-in, two lines of copy) still gives
       the ninja a full-length column to stand in. */
    min-height: 208px;
    /* Right padding keeps the copy clear of the ninja standing in that corner. */
    padding: 1.75rem 1.5rem 1.75rem;
    border-radius: var(--stn-r-lg);
    text-align: center;
    background: var(--stn-wash-panel), var(--mud-palette-surface, #2b2b35);
    color: var(--mud-palette-text-primary, rgba(255, 255, 255, 0.82));
    border: 1px solid var(--mud-palette-lines-default, rgba(255, 255, 255, 0.12));
    box-shadow: var(--stn-e-4);
}

/* Gradient top edge, matching the auth card. */
.stn-reconnect-panel::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: var(--stn-grad-brand);
}

/* She stands in the bottom-right corner behind the copy, as on the chat empty state. The mask
   fades her out towards the text so nothing has to compete for contrast. */
.stn-reconnect-ninja {
    position: absolute;
    right: -18px;
    bottom: 0;
    /* Scaled to the panel rather than a fixed height: the states differ in length, and a fixed
       height anchored to the bottom edge crops her head off in the short ones. */
    height: 100%;
    max-height: 240px;
    width: auto;
    opacity: 0.55;
    pointer-events: none;
    user-select: none;
    filter: drop-shadow(0 0 22px rgba(var(--stn-blue-rgb), 0.22));
    -webkit-mask-image: linear-gradient(to left, #000 62%, transparent 98%);
    mask-image: linear-gradient(to left, #000 62%, transparent 98%);
}

.stn-reconnect-content {
    position: relative;
    z-index: 1;
    /* Balances the ninja: the copy sits left of her rather than under her. */
    padding-right: 96px;
    text-align: left;
}

.stn-reconnect-brand {
    margin-bottom: 1.1rem;
}
/* The wordmark is set at hero scale by .stn-brand-text; the panel needs it small. */
.stn-reconnect-brand .stn-brand-text { font-size: 1.05rem; }

/* Below this the panel is too narrow to give her a column of her own. */
@media (max-width: 460px) {
    .stn-reconnect-ninja { display: none; }
    .stn-reconnect-content { padding-right: 0; }
}

.stn-reconnect-title {
    margin: 0 0 0.5rem;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--mud-palette-text-primary, #fff);
}

.stn-reconnect-text {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--mud-palette-text-secondary, rgba(255, 255, 255, 0.6));
}

.stn-reconnect-attempt {
    margin: 0.9rem 0 0;
    font-size: 0.8rem;
    color: var(--mud-palette-text-disabled, rgba(255, 255, 255, 0.4));
}

/* The way out of a stuck reconnect. Quiet on purpose — reconnecting is the expected outcome and
   this is the alternative, so it sits below the action as a link rather than competing with it as
   a second button. Kept clear of the progress line along the bottom edge. */
.stn-reconnect-exit {
    display: inline-block;
    margin: 1.25rem 0 0.25rem;
    font-size: 0.8rem;
    color: var(--mud-palette-text-disabled, rgba(255, 255, 255, 0.4));
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.stn-reconnect-exit:hover,
.stn-reconnect-exit:focus-visible {
    color: var(--mud-palette-text-secondary, rgba(255, 255, 255, 0.6));
    border-bottom-color: currentColor;
}

/* Indeterminate activity line across the panel's bottom edge, replacing the circular spinner. It
   pairs with the gradient hairline on the top edge, and the panel's overflow:hidden crops it to
   the rounded corners. */
.stn-reconnect-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.06);
}

.stn-reconnect-progress::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 40%;
    background: var(--stn-grad-brand);
    animation: stn-reconnect-sweep 1.3s ease-in-out infinite;
}

/* The bar is 40% of the track, so -100% parks it just off the left edge and 250% just off the
   right — a full sweep with no visible jump at the loop. */
@keyframes stn-reconnect-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(250%); }
}

/* Each state block is hidden until Blazor's class on the modal selects it. `retrying` rides on top
   of `show` (Blazor adds it once an attempt is pending), so it maps to the same block. */
.stn-reconnect-state { display: none; }
.components-reconnect-show .stn-reconnect-state-show,
.components-reconnect-retrying .stn-reconnect-state-show { display: block; }
.components-reconnect-failed .stn-reconnect-state-failed { display: block; }
.components-reconnect-paused .stn-reconnect-state-paused { display: block; }
.components-reconnect-resume-failed .stn-reconnect-state-resume-failed { display: block; }
.components-reconnect-rejected .stn-reconnect-state-rejected { display: block; }

/* The line means "something is happening right now", so it is hidden in the four resting states —
   given up, parked, unresumable, or gone. */
.components-reconnect-failed .stn-reconnect-progress,
.components-reconnect-paused .stn-reconnect-progress,
.components-reconnect-resume-failed .stn-reconnect-progress,
.components-reconnect-rejected .stn-reconnect-progress { display: none; }

/* Hand-pressed retry in flight (set by the script in _Layout, cleared when the attempt settles or
   Blazor repaints). It has to beat every rule above, because the modal still carries whichever
   Blazor state class it was in when the button was pressed. */
#components-reconnect-modal.stn-reconnect-busy .stn-reconnect-state { display: none; }
#components-reconnect-modal.stn-reconnect-busy .stn-reconnect-state-busy { display: block; }
/* A hand-pressed retry IS activity, so the line comes back even from a resting state. */
#components-reconnect-modal.stn-reconnect-busy .stn-reconnect-progress { display: block; }

#components-reconnect-modal .stn-reconnect-btn {
    margin-top: 1.25rem;
    padding: 0.55rem 1.5rem;
    font-family: var(--stn-font-sans);
    font-size: 0.9rem;
    font-weight: 600;
    /* MudBlazor's stylesheet uppercases bare <button> elements, and this panel lives in the
       layout's raw HTML where that reset still reaches it. The rest of the app is sentence-case
       (StnTheme sets TextTransform: none on the button typography), so opt out explicitly. */
    text-transform: none;
    letter-spacing: normal;
    cursor: pointer;
    color: #fff;
    background: var(--stn-grad-brand);
    border: none;
    border-radius: var(--stn-r-sm);
    transition: filter var(--stn-dur) var(--stn-ease);
}
#components-reconnect-modal .stn-reconnect-btn:hover { filter: brightness(1.1); }
#components-reconnect-modal .stn-reconnect-btn:focus-visible {
    outline: 2px solid var(--stn-blue-pale);
    outline-offset: 2px;
}

/* The sweep uses a literal duration, so it needs its own opt-out rather than riding the duration
   tokens. A static full-width bar still reads as "busy" without the movement. */
@media (prefers-reduced-motion: reduce) {
    .stn-reconnect-progress::before {
        animation: none;
        width: 100%;
        opacity: 0.55;
    }
}

/* ---------------------------------------------------------------------------
   Shared identity/tag component styles.
   These live here rather than inside the components because IdentityAvatar and
   TagPicker render many times per page — a <style> block inside a component is
   emitted once per instance, so every roster row would carry its own copy.
   --------------------------------------------------------------------------- */

.stn-avatar-wrap { position: relative; display: inline-flex; flex: none; }

/* Presence rides on the avatar as a small dot rather than standing beside it as
   a second circle of the same size — the row then reads as one person. */
.stn-presence-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid var(--mud-palette-surface);
    box-sizing: content-box;
}
.stn-presence-dot-sm { width: 8px; height: 8px; }

.stn-tagpick-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    position: relative;   /* the popover anchors to this row */
    min-height: 34px;
}
.stn-tagpick-option { padding: 1px 2px; }

/* Country flags as vendored SVGs (wwwroot/flags), so they show in full colour everywhere —
   Windows included, where the system emoji font draws a flag emoji as grey letters. Height is in
   em so a flag scales with the text beside it; the 4x3 SVGs keep their aspect via width:auto.
   The faint ring gives a mostly-white flag an edge against a light panel. */
.stn-flag {
    height: 0.95em;
    width: auto;
    vertical-align: -0.13em;
    border-radius: 2px;
    box-shadow: 0 0 0 0.5px rgba(128, 128, 128, 0.35);
}
