/*
 * BlackoutSpot - global design polish on top of MudBlazor.
 * MudTheme (see AppTheme.cs) handles palette/typography; this file handles
 * things that live outside the theme: web fonts, root-level polish,
 * scroll behaviour, focus rings, iOS safe-area, and the loading splash.
 *
 * Token names and values are pinned to breaking-school-app-design-spec.md §2
 * and blackoutspot-ux-navigation-spec.md - see CLAUDE.md for the full rules.
 * The wolf's red overrides the spec's amber for --accent per §2.
 */

:root, :root[data-theme="dark"] {
    /* Tell the browser we're a dark-themed page. Without this, Chrome on
       Android may apply its own "Force Dark" filter on top of our palette
       (mutes vibrant reds/oranges, greys out photos, breaks tier tints).
       Also sets form-control chrome, scrollbars and system colors to the
       dark family so a native <input> doesn't ship a white plate on top
       of --surface. Overridden below for the light-theme variant. */
    color-scheme: dark;

    /* Asphalt palette - "the interface starts black and light arrives" */
    --bg:             #101312;
    --surface:        #191D1C;
    --surface-raised: #232827;
    --text:           #F2F4F1;
    --text-muted:     #8C948F;

    /* Brand red from the wolf mark (blackout_logo.svg). Bumped slightly brighter
       from the original #E7181A so accent-as-text on --bg passes WCAG AA (4.5:1)
       for small text - was 4.17:1, now 4.62:1. Trades ~0.25 stops of white-on-red
       ink contrast, still readable at button sizes. Light theme uses a darker
       variant tuned for white backgrounds. */
    --accent:      #F52428;
    --accent-ink:  #F2F4F1;
    /* Same hue as --accent but a touch brighter/more saturated. Used ONLY
       for accent-COLORED TEXT (eyebrows, prices, arrow links) — thin red
       glyphs on a dark background lose saturation through sub-pixel
       antialiasing on OLED Android screens; the base --accent renders as
       pink/salmon at 400-800 weights below ~48px. This variant gives the
       glyphs enough head-room that after AA-driven perceived-brightness
       reduction they still read as the intended vibrant red.
       Fills stay on --accent (backgrounds don't need the compensation
       and the base value is contrast-validated against --accent-ink). */
    --accent-strong: #FF3B3E;

    --success: #35C08A;
    --warning: #F2B705;
    --danger:  #E5484D;

    /* External-brand identity colours for the ContactStudioSheet channel
       icons. Tokenised so any future theme sweep or brand-guideline update
       lands in ONE file instead of chasing hex codes across components.
       Colour values are the official brand marks (Viber, WhatsApp,
       Instagram — the last is a single-color approximation of the gradient
       so it works as an icon tint). Same tokens in both themes. */
    --brand-viber:     #7360F2;
    --brand-whatsapp:  #25D366;
    --brand-instagram: #E1306C;

    /* Radius, spacing, motion */
    --space: 4px;
    --radius-input: 12px;
    --radius-card:  16px;
    --radius-pill:  999px;
    --border: 1px solid var(--surface-raised);

    --dur-fast: 140ms;
    --dur-base: 220ms;
    --dur-slow: 400ms;
    --ease: cubic-bezier(0.2, 0.8, 0.2, 1);

    /* Header height, fluid on desktop; overridden to 72px inside the
       ≤720px block so mobile stays compact. Consumed by .bs-app-bar-inner
       AND by .bs-landing-hero-v2's pull-under offset so they never drift. */
    --bs-app-bar-h: clamp(96px, 7vw, 144px);

    /* Hero backdrop: asphalt-to-surface gradient only. Previous version layered
       two accent radials on top - that's the §1 SaaS "hero glow" look.
       Solid tokens keep the depth without the bling. */
    --gradient-hero: linear-gradient(180deg, var(--bg), var(--surface));

    /* Badge tier palette — the ONE deliberate exception to §1's "no metallic
       medals" rule. Not chrome / gradient / glossy — matte, dark, saturated
       tokens that read as patinated bronze, gunmetal silver, aged gold.
       Applied as a diagonal patina + solid ring on the badge puck; nothing
       shiny. Picked for AA contrast against --surface-raised so a tinted
       ring on a tinted puck stays visible. */
    --tier-bronze: #A87144;
    --tier-silver: #A6ADB6;
    --tier-gold:   #C79E44;
}

:root[data-theme="light"] {
    color-scheme: light;

    --bg:             #ECEBE7;
    --surface:        #FFFFFF;
    --surface-raised: #DEDDD8;
    --text:           #101312;
    --text-muted:     #5C635F;

    --accent:      #C81216;   /* darkened brand red - passes AA on white */
    --accent-ink:  #FFFFFF;
    /* Light-theme counterpart of --accent-strong. Red text on light bg
       loses saturation at small sizes just like on dark; a slightly
       darker/deeper red survives AA and reads as full-strength brand red. */
    --accent-strong: #A80B0F;

    --success: #17855C;
    --warning: #8A6800;
    --danger:  #C0272D;

    --gradient-hero: linear-gradient(180deg, var(--bg), var(--surface));

    /* Same three tokens, tuned darker so the tier ring + tint pass AA on
       the light theme's --surface-raised (#DEDDD8). */
    --tier-bronze: #7A4A20;
    --tier-silver: #5F6773;
    --tier-gold:   #8C6A1A;
}

html, body {
    background-color: var(--bg);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    /* Always clip horizontal overflow at the document root — a rogue wide
       child (min-width:auto on a flex grandchild, an oversized inline SVG,
       a long unbroken string) would otherwise grow the layout viewport
       past the visual viewport and force the phone to render zoomed-out
       and left-anchored. clip (not hidden) so no new scroll container is
       established and sticky headers keep sticking. Used to be scoped to
       max-width:899.98px which meant landscape tablets and desktop still
       could inflate. */
    max-width: 100vw;
    overflow-x: clip;
}

/* Prevent Android/iOS Safari from auto-inflating text on pages the browser
   considers "narrow" — that behaviour renders the whole layout larger than
   the meta viewport and produces the "opens zoomed in, pinch-out to read"
   bug. 100% keeps our own clamp()-based type scale in charge. */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* Kill the flash of blue that Android WebView paints on every tap.
   Ripples on our buttons already provide feedback; the default highlight
   just makes the whole app feel un-designed on mobile. */
* {
    -webkit-tap-highlight-color: transparent;
}

/* Nuclear layout guard: MudLayout + MudMainContent can't grow past the
   viewport, at ANY breakpoint. The user reported the page loading zoomed-in
   ONLY after login — the difference between the anon and authed shells is
   the bottom nav + (for admins) the mini drawer, both of which historically
   nudged MudLayout's intrinsic width past 100vw via min-content children
   and left flex-child min-width:auto to be the tie-breaker. This constrains
   both layout containers regardless of screen size so no future auth-only
   child can trip the viewport-scale bug again. */
.mud-layout,
.mud-main-content {
    max-width: 100vw;
    min-width: 0;
    overflow-x: clip;
}

/* -------- Design gallery (review-only, /design-gallery) -------- */
.bs-gallery {
    max-width: 960px;
    margin: 0 auto;
    padding: 2rem 0 4rem;
    display: grid;
    gap: 3rem;
}
.bs-gallery-head { display: grid; gap: 0.35rem; }
.bs-gallery-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(2rem, 5vw, 3rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin: 0;
    line-height: 1.1;
}
.bs-gallery-sub {
    color: var(--text-muted);
    font-size: 0.95rem;
    max-width: 60ch;
    line-height: 1.5;
    margin: 0;
}
.bs-gallery-section { display: grid; gap: 1rem; }
.bs-gallery-h {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    margin: 0 0 0.25rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--surface-raised);
}
.bs-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.75rem;
}
.bs-swatch {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
}
.bs-swatch-chip {
    width: 44px;
    height: 44px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.06);
    flex-shrink: 0;
}
.bs-swatch-name {
    font-family: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: var(--text);
    font-weight: 500;
}
.bs-swatch-role { color: var(--text-muted); font-size: 0.75rem; }

.bs-type-stack { display: grid; gap: 1.25rem; }
.bs-type-label {
    display: block;
    color: var(--text-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem;
    letter-spacing: 0.03em;
    margin-bottom: 0.35rem;
}
/* Base type scale (m96) - fluid clamp() sizes so the same class works from
   mid-range Android up to a 2K desktop without media queries. Every page in
   the app inherits from these (design-gallery + all .bs-page-hero uses). */
.bs-type-display-lg { font-family: 'Manrope', system-ui, sans-serif; font-weight: 800; font-size: clamp(2.5rem, 5vw, 4.5rem); line-height: 1.05; letter-spacing: -0.025em; }
.bs-type-display    { font-family: 'Manrope', system-ui, sans-serif; font-weight: 800; font-size: clamp(1.75rem, 3vw, 3rem); line-height: 1.1; letter-spacing: -0.02em; }
.bs-type-title      { font-family: 'Manrope', system-ui, sans-serif; font-weight: 600; font-size: clamp(1.125rem, 1.4vw, 1.5rem); line-height: 1.25; letter-spacing: -0.01em; }
.bs-type-body       { font-family: 'Commissioner', system-ui, sans-serif; font-weight: 400; font-size: clamp(1rem, 1.05vw, 1.125rem); line-height: 1.55; max-width: 62ch; }
.bs-type-body-strong{ font-family: 'Commissioner', system-ui, sans-serif; font-weight: 600; font-size: clamp(1rem, 1.05vw, 1.125rem); line-height: 1.55; }
.bs-type-caption    { font-family: 'Commissioner', system-ui, sans-serif; font-weight: 400; font-size: clamp(0.8125rem, 0.85vw, 0.9375rem); line-height: 1.4; color: var(--text-muted); }
.bs-type-overline   { font-family: 'Commissioner', system-ui, sans-serif; font-weight: 600; font-size: clamp(0.6875rem, 0.75vw, 0.8125rem); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); line-height: 1; }
.bs-type-data       { font-family: 'JetBrains Mono', monospace; font-weight: 500; font-size: clamp(1rem, 1.1vw, 1.1875rem); letter-spacing: 0.01em; font-variant-numeric: tabular-nums; }

.bs-ring-showcase {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1.5rem;
    padding: 1.5rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
}
.bs-ring-cell { display: grid; justify-items: center; gap: 0.5rem; }

.bs-btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}

.bs-mock-card-list { display: grid; gap: 0.75rem; }
.bs-mock-card {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 1rem;
    align-items: center;
    padding: 0.9rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    transition: border-color 0.15s ease;
}
.bs-mock-card:hover { border-color: rgba(245, 36, 40, 0.35); }
.bs-mock-card.bs-mock-card-mine { border-color: rgba(53, 192, 138, 0.35); }
.bs-mock-card-body { display: grid; gap: 2px; }
.bs-mock-card-title { font-family: 'Manrope', system-ui, sans-serif; font-weight: 600; font-size: 1.05rem; letter-spacing: -0.005em; }
.bs-mock-card-meta { color: var(--text-muted); font-size: 0.85rem; }

.bs-cal-live.is-reconnecting {
    background: rgba(242, 183, 5, 0.10);
    color: var(--warning);
}
.bs-cal-live.is-reconnecting .dot { background: var(--warning); animation-duration: 1.2s; }
.bs-cal-live.is-offline {
    background: rgba(140, 148, 143, 0.10);
    color: var(--text-muted);
}
.bs-cal-live.is-offline .dot { background: var(--text-muted); animation: none; box-shadow: none; }

.bs-mark-showcase {
    display: flex;
    align-items: center;
    gap: 2rem;
    padding: 2rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    flex-wrap: wrap;
}
.bs-mark-hero {
    width: 160px;
    height: 160px;
    border-radius: var(--radius-card);
    flex-shrink: 0;
}

/* -------- ClassCard (the app's atom) - spec §3.1 / §5 --------
   Cypher ring on the left, title+meta stack in the middle, action on the right.
   State modifiers on the wrapper (.is-mine, .is-full, .is-waitlisted, .is-cancelled,
   .is-event, .is-closed, .is-past, .is-nearly-full) drive left-edge color, backdrop
   tint, and opacity. Renders as a <button> so keyboard/screen reader treat it as
   an interactive element. */
.bs-classcard {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--surface-raised);
    border-radius: var(--radius-card);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s ease;
    width: 100%;
    margin-bottom: 0.6rem;
}
/* Widen the grid to 4 columns when a hero image is present so the thumbnail
   gets its own leading track without squashing the ring / body / action. */
.bs-classcard:has(.bs-classcard-hero) {
    grid-template-columns: auto auto 1fr auto;
}
.bs-classcard-hero {
    width: 72px;
    height: 72px;
    border-radius: var(--radius-input);
    background: var(--surface-raised) center / cover no-repeat;
    flex-shrink: 0;
}
@media (max-width: 600px) {
    /* Slightly smaller thumb on tight viewports so the card doesn't wrap
       its body content. */
    .bs-classcard-hero { width: 56px; height: 56px; }
}
.bs-classcard:hover:not(:disabled) {
    transform: translateY(-1px);
    border-color: var(--accent);
    background: rgba(255, 255, 255, 0.02);
}
.bs-classcard:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-classcard:disabled { cursor: default; }

.bs-classcard-ring { display: flex; align-items: center; }
.bs-classcard-glyph {
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--text-muted);
}
.bs-classcard-body { display: grid; gap: 3px; min-width: 0; }
.bs-classcard-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    /* Card title fluid (m96): 17→20px so the atom holds its own on a 2K
       Schedule / MyBookings row without being overwhelmed by whitespace. */
    font-size: clamp(1.0625rem, 1.15vw, 1.25rem);
    line-height: 1.25;
    letter-spacing: -0.005em;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bs-classcard-level {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: clamp(0.6875rem, 0.7vw, 0.8125rem);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 600;
    color: var(--text-muted);
    padding: 2px 8px;
    background: var(--surface-raised);
    border-radius: var(--radius-pill);
    line-height: 1.4;
}
.bs-classcard-series {
    color: var(--accent);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    opacity: 0.85;
    line-height: 1;
}
/* Kind chip on Seminar / Battle sessions - only rendered for non-Regular. */
.bs-classcard-kind {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: clamp(0.6875rem, 0.7vw, 0.8125rem);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    color: var(--accent-ink);
    line-height: 1.4;
}
.bs-classcard-kind.is-seminar { background: var(--warning); color: var(--bg); }
.bs-classcard-kind.is-battle  { background: var(--accent); }
.bs-classcard-meta {
    color: var(--text-muted);
    font-size: clamp(0.85rem, 0.9vw, 0.9375rem);
    line-height: 1.4;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
}
.bs-classcard-meta .bs-classcard-time {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500;
    color: var(--text);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    font-variant-numeric: tabular-nums;
}
.bs-classcard-sep { color: var(--surface-raised); }
.bs-classcard-spotsleft {
    color: var(--warning);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.bs-classcard-action {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-width: 88px;
}
.bs-classcard-caret {
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: 6px 10px;
    transition: color 0.15s ease, transform 0.15s ease;
}
.bs-classcard:hover .bs-classcard-caret { color: var(--accent); transform: translateX(2px); }

/* State modifiers.

   Layered stripe language on the left edge - the card's outer signal for
   at-a-glance status. `is-mine` / `is-waitlisted` / `is-cancelled` / `is-event`
   / `is-closed` carry stronger semantics than raw capacity, so the capacity
   stripes below are gated with :not(...) to defer to them. Ring stays the
   primary capacity signal; the stripe is peripheral emphasis for the two
   states where seeing it across the grid actually helps: nearly full (act now)
   and full (no action here). */

/* Capacity: nearly full (>=80%, not yet full). Solid amber left edge +
   warning-tinted ring count. Draws the eye without lighting up the whole card. */
.bs-classcard.is-nearly-full:not(.is-mine):not(.is-waitlisted):not(.is-cancelled):not(.is-event):not(.is-closed):not(.is-past) {
    border-left-color: var(--warning);
}
.bs-classcard.is-nearly-full .bs-ring-count { color: var(--warning); }

/* Capacity: full. Muted stripe + a whisper of muted overlay so the card
   reads as "closed" rather than shouting. Ring goes muted via .bs-ring.is-full
   already; existing title-muted rule below keeps the title quiet too. */
.bs-classcard.is-full:not(.is-mine):not(.is-waitlisted):not(.is-cancelled):not(.is-event):not(.is-closed) {
    border-left-color: var(--text-muted);
    background: linear-gradient(0deg, rgba(140, 148, 143, 0.04), rgba(140, 148, 143, 0.04)), var(--surface);
}

.bs-classcard.is-mine { border-left-color: var(--accent); background: rgba(245, 36, 40, 0.05); }
.bs-classcard.is-waitlisted { border-left-color: var(--warning); border-left-style: dashed; }
.bs-classcard.is-full .bs-classcard-title { color: var(--text-muted); }
.bs-classcard.is-cancelled { opacity: 0.55; }
.bs-classcard.is-cancelled .bs-classcard-title { text-decoration: line-through; }
.bs-classcard.is-past { opacity: 0.7; }
.bs-classcard.is-event {
    border-left-color: var(--warning);
    background: rgba(242, 183, 5, 0.03);
}
.bs-classcard.is-closed {
    border-left-color: var(--text-muted);
    border-left-style: dashed;
    background: repeating-linear-gradient(
        135deg,
        transparent 0,
        transparent 8px,
        rgba(140, 148, 143, 0.04) 8px,
        rgba(140, 148, 143, 0.04) 12px
    );
}
/* Practice: promoted-but-inert. Solid accent-tinted left edge signals
   "official crew moment" without stealing the full-accent fill that
   marks the member's own bookings. Same neutral surface as a regular
   card so a row of Practice entries doesn't wash the schedule. */
.bs-classcard.is-practice {
    border-left-color: var(--accent);
    background: linear-gradient(90deg, rgba(245, 36, 40, 0.04), rgba(245, 36, 40, 0) 40%), var(--surface);
}
.bs-classcard.is-practice .bs-classcard-glyph {
    color: var(--accent);
    background: rgba(245, 36, 40, 0.08);
}

/* Ultra-narrow - stack action under body */
@media (max-width: 419.98px) {
    .bs-classcard {
        grid-template-columns: auto 1fr;
    }
    .bs-classcard-action {
        grid-column: 1 / -1;
        justify-content: flex-start;
        border-top: 1px dashed var(--surface-raised);
        padding-top: 0.5rem;
        margin-top: 0.25rem;
    }
}

/* -------- SubscriptionStrip (top of Schedule) - spec §3.1 -------- */
.bs-sub-strip {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--success);
    border-radius: var(--radius-card);
    margin-bottom: 1rem;
}
.bs-sub-strip-icon .mud-icon-root { color: var(--success); font-size: 22px; }
.bs-sub-strip-body { min-width: 0; }
.bs-sub-strip-title { font-weight: 600; font-size: clamp(0.95rem, 1.05vw, 1.125rem); line-height: 1.25; }
.bs-sub-strip-sub { color: var(--text-muted); font-size: clamp(0.85rem, 0.9vw, 0.9375rem); line-height: 1.4; margin-top: 3px; }
.bs-sub-strip-actions { display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; justify-content: flex-end; }
.bs-sub-strip.is-warning { border-left-color: var(--warning); }
.bs-sub-strip.is-warning .bs-sub-strip-icon .mud-icon-root { color: var(--warning); }
.bs-sub-strip.is-danger { border-left-color: var(--danger); background: rgba(229, 72, 77, 0.05); }
.bs-sub-strip.is-danger .bs-sub-strip-icon .mud-icon-root { color: var(--danger); }
.bs-sub-strip.is-danger .bs-sub-strip-title { color: var(--danger); }
.bs-sub-strip.is-frozen { border-left-color: var(--text-muted); }
.bs-sub-strip.is-frozen .bs-sub-strip-icon .mud-icon-root { color: var(--text-muted); }
.bs-sub-strip.is-none {
    border-left-color: var(--surface-raised);
    background: repeating-linear-gradient(
        135deg,
        var(--surface) 0,
        var(--surface) 10px,
        rgba(255, 255, 255, 0.015) 10px,
        rgba(255, 255, 255, 0.015) 14px
    );
}

/* -------- Live pill reconnecting/offline variants (base already defined earlier) -------- */

/* -------- Profile hub (spec §3.5) + sub-pages -------- */
.bs-profile-header {
    display: grid;
    gap: 0.25rem;
    margin-bottom: 1.5rem;
}
/* Identity row = avatar upload + name/meta block side-by-side on desktop,
   stacked on mobile. Avatar column is capped so the MediaUpload compact
   preview doesn't dominate the header on tight viewports. */
.bs-profile-identity {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}
.bs-profile-avatar {
    flex: 0 0 auto;
    width: 128px;
}
@media (max-width: 600px) {
    .bs-profile-avatar { width: 96px; }
    .bs-profile-identity { gap: 1rem; }
}
.bs-profile-name {
    font-family: 'Manrope', system-ui, sans-serif;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    margin: 0.25rem 0 0;
}
/* Empty-state variant when the member hasn't picked a nickname yet. Lives in
   the H1 slot so it inherits the headline size + weight, but reads as muted
   until hover/focus. Not a link - a button that focuses the input below. */
.bs-profile-name-cta {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25em;
    background: none;
    border: 0;
    padding: 0;
    text-align: left;
    text-transform: none;
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease);
}
.bs-profile-name-cta:hover,
.bs-profile-name-cta:focus-visible { color: var(--accent); }
.bs-profile-name-cta:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: 4px;
}

.bs-profile-meta {
    color: var(--text-muted);
    font-size: 0.9rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    margin-top: 0.5rem;
}
.bs-profile-meta-sep { color: var(--surface-raised); }

.bs-profile-hub {
    display: grid;
    gap: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 0.5rem;
}
.bs-profile-row {
    display: grid;
    grid-template-columns: 24px 1fr auto;
    gap: 1rem;
    align-items: center;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-input);
    color: var(--text);
    text-decoration: none;
    background: transparent;
    border: none;
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: background 0.15s ease;
    width: 100%;
}
.bs-profile-row:hover { background: rgba(255, 255, 255, 0.03); }
.bs-profile-row .mud-icon-root { color: var(--text-muted); }
.bs-profile-row-body { display: grid; gap: 2px; min-width: 0; }
.bs-profile-row-title { font-weight: 600; font-size: 1rem; line-height: 1.2; }
.bs-profile-row-sub {
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-profile-row-caret { opacity: 0.5; }
.bs-profile-row-inline .bs-lang-toggle { justify-self: end; }
.bs-profile-row-danger { color: var(--danger); }
.bs-profile-row-danger .mud-icon-root { color: var(--danger); }

/* Language segmented toggle inside the hub row */
.bs-lang-toggle {
    display: inline-flex;
    gap: 0.2rem;
    padding: 3px;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
}
.bs-lang-btn {
    background: transparent;
    color: var(--text-muted);
    border: none;
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}
.bs-lang-btn.is-active { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.bs-lang-btn:hover:not(.is-active) { color: var(--text); }

/* Contact-edit section. One rhythm (1rem = 16px) between form fields so
   the section reads as evenly-paced rows; per-field mt-* utility classes
   would drift and produce the "phone hugs the eyebrow, nickname jumps
   away" look this page used to have. The nickname+toggle+hint cluster
   is a nested grid with its own tighter gap so the toggle reads as
   belonging to the field it controls. */
.bs-profile-contact-edit {
    display: grid;
    gap: 1rem;
    max-width: 520px;
    margin-top: 2rem;
}
.bs-profile-contact-edit .bs-eyebrow { margin-bottom: 0.25rem; }
.bs-profile-nickname-group {
    display: grid;
    gap: 0.5rem;
}
.bs-profile-contact-save { margin-top: 0.5rem; justify-self: start; }

/* -------- Public profile portrait (/profile) -------- */
/* Portrait card - square photo left, headline stack right. This is the
   "sexy and modest" identity page; the settings hub lives at /profile/edit.
   Elevation via border + surface step, no shadows (spec §4). One accent moment
   per screen - the initial fallback or the Edit button, never both loud. */
.bs-portrait {
    display: grid;
    grid-template-columns: minmax(180px, 240px) 1fr;
    gap: 2rem;
    padding: 2rem;
    max-width: 900px;
    margin: 1.5rem auto;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
}
@media (max-width: 700px) {
    .bs-portrait {
        grid-template-columns: 1fr;
        gap: 1.5rem;
        padding: 1.5rem;
        margin: 0.5rem;
    }
}

.bs-portrait-photo {
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: var(--radius-card);
    background: var(--surface-raised);
    display: grid;
    place-items: center;
    position: relative;
}
.bs-portrait-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
@media (max-width: 700px) {
    .bs-portrait-photo {
        max-width: 240px;
        margin: 0 auto;
    }
}

.bs-portrait-initial {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    font-size: clamp(4rem, 12vw, 6rem);
    color: var(--accent);
    line-height: 1;
}

.bs-portrait-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.bs-portrait-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 0.75rem;
}
.bs-portrait-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    font-size: clamp(2rem, 5vw, 3.5rem);
    line-height: 1.05;
    margin: 0;
    color: var(--text);
    word-break: break-word;
}
.bs-portrait-realname {
    flex-basis: 100%;
    color: var(--text-muted);
    font-size: 1rem;
    margin-top: 0.25rem;
}

.bs-portrait-bio {
    margin: 1.25rem 0 0;
    color: var(--text);
    font-size: 1rem;
    line-height: 1.6;
    max-width: 60ch;
    white-space: pre-wrap;
}

/* Dashed-outline chip used when nickname or bio is empty. Reads as "there's
   something for you to do here" without shouting. Turns solid accent on hover. */
.bs-portrait-chip {
    display: inline-block;
    margin-top: 1rem;
    padding: 0.35rem 0.75rem;
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-pill);
    color: var(--accent);
    font-size: 0.85rem;
    line-height: 1.4;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
}
.bs-portrait-chip:hover,
.bs-portrait-chip:focus-visible {
    color: var(--accent-ink);
    background: var(--accent);
    border-color: var(--accent);
    outline: none;
}
.bs-portrait-chip-muted { color: var(--text-muted); }

/* Private / not-found variant on /u/{id} - same shell, quieter treatment so
   the empty state doesn't try to be a hero. */
.bs-portrait-initial-muted { color: var(--text-muted); }
.bs-portrait-name-muted    { color: var(--text-muted); font-size: clamp(1.5rem, 3.5vw, 2rem); }

/* Actions row on the portrait card - Edit (accent) + Find members (outlined),
   wraps on narrow screens so both buttons stay legible on mobile. */
.bs-portrait-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

/* Consent hint under the "Let other members see and find me" checkbox on
   /profile/edit - spells out what the toggle actually exposes so the label
   isn't the only thing the member reads before ticking it. */
.bs-profile-consent-hint {
    margin: 0.25rem 0 0 2rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.45;
    max-width: 52ch;
}

/* -------- Member search (/members) -------- */
.bs-members {
    max-width: 720px;
    margin: 1.5rem auto;
    padding: 0 1rem;
}
.bs-members-head { margin-bottom: 1rem; }
.bs-members-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    font-size: clamp(1.75rem, 4vw, 2.25rem);
    line-height: 1.1;
    margin: 0.25rem 0 0;
    color: var(--text);
}
.bs-members-search { margin-bottom: 1rem; }
.bs-members-hint {
    color: var(--text-muted);
    padding: 1rem 0;
    text-align: center;
}
.bs-members-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
/* Row - full-width tap target, quiet elevation, hover picks up a red edge so
   there is an obvious click affordance without turning it into a button. */
.bs-members-row {
    display: grid;
    grid-template-columns: 56px 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    color: inherit;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease);
    min-height: 72px;
}
.bs-members-row:hover,
.bs-members-row:focus-visible {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, var(--surface));
    outline: none;
}
.bs-members-avatar {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--surface-raised);
    overflow: hidden;
    display: grid;
    place-items: center;
    color: var(--accent);
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: 1.4rem;
    text-transform: uppercase;
}
.bs-members-avatar img {
    width: 100%; height: 100%; object-fit: cover; display: block;
}
.bs-members-row-body { min-width: 0; }
.bs-members-nick {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.01em;
    color: var(--text);
    font-size: 1.05rem;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-members-bio {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-top: 0.15rem;
    /* One-line preview; the full bio lives on the click-through profile. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-members-caret { color: var(--text-muted); }
.bs-members-row-skel { pointer-events: none; }
.bs-members-row-skel .bs-members-avatar { background: var(--surface-raised); }

.bs-portrait-meta {
    margin-top: 1.5rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.bs-portrait-actions { margin-top: 2rem; }

/* Skeleton state - reuses the site's bs-skel shimmer via inline placeholders
   so the layout doesn't jump when /me lands. */
.bs-portrait-skeleton .bs-portrait-photo {
    position: relative;
    overflow: hidden;
}
.bs-portrait-skeleton .bs-portrait-photo::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
    animation: bs-skel-shimmer 1.4s ease-in-out infinite;
}
.bs-portrait-skel-row {
    background: var(--surface-raised);
    border-radius: 4px;
    position: relative;
    overflow: hidden;
}
.bs-portrait-skel-row::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
    animation: bs-skel-shimmer 1.4s ease-in-out infinite;
}

/* -------- Profile hero (/profile) --------
   The member's own portrait page. Editorial split:
     ┌────────────────────────────┬─────────────┐
     │ MEMBER · JOINED MMM YY     │             │
     │ BBOY NEMESIS               │  ⦿ portrait │
     │ Alexander Mouratidis       │             │
     │ Bio, editorial column      │             │
     │ 12 STREAK · 03 AWARDS · … │             │
     └────────────────────────────┴─────────────┘
     Badges → Actions (full width)
   On mobile the portrait pins top-right of the identity block so the
   overline + ring sit together, then everything below stacks.
   One accent moment (the ring around the avatar); surface-step gradient
   sets the card off without a shadow. PublicProfile.razor stays on the
   older .bs-portrait shell - peer view is deliberately quieter. */
.bs-profile-hero {
    position: relative;
    max-width: 880px;
    margin: 1.5rem auto 3rem;
    padding: 2.5rem 2.25rem;
    /* Two-tone surface step - top holds --surface, softens down to
       --surface-raised at 140%. Reads as depth without a shadow, honoring
       the "elevation from surface steps" rule. */
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-raised) 140%);
    border: var(--border);
    border-radius: var(--radius-card);
    display: flex;
    flex-direction: column;
    gap: 2rem;
}
/* Top-3 leaderboard tier tint on the hero shell — same tokens as the
   podium + badge medals. A 12% diagonal patina layered on top of the
   default two-tone gradient plus a solid tier border, so a champion's
   profile card reads as gold at a glance without a badge decoration
   attached. Ranks 4+ leave the class empty and keep the plain shell. */
.bs-profile-hero.is-rank-gold { --tier-hue: var(--tier-gold); }
.bs-profile-hero.is-rank-silver { --tier-hue: var(--tier-silver); }
.bs-profile-hero.is-rank-bronze { --tier-hue: var(--tier-bronze); }
.bs-profile-hero.is-rank-gold,
.bs-profile-hero.is-rank-silver,
.bs-profile-hero.is-rank-bronze {
    border-color: var(--tier-hue);
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--tier-hue) 12%, transparent) 0%,
            transparent 45%),
        linear-gradient(180deg, var(--surface) 0%, var(--surface-raised) 140%);
}
.bs-profile-hero.is-rank-gold { border-width: 2px; }
/* Portrait avatar picks up a tier ring on top-3 profiles — matches the
   podium's avatar treatment so the same person on the leaderboard and
   on their own profile wears the same ring. */
.bs-profile-avatar.is-rank-gold { --tier-hue: var(--tier-gold); }
.bs-profile-avatar.is-rank-silver { --tier-hue: var(--tier-silver); }
.bs-profile-avatar.is-rank-bronze { --tier-hue: var(--tier-bronze); }
.bs-profile-avatar.is-rank-gold {
    outline: 3px solid var(--tier-hue);
    outline-offset: 3px;
}
.bs-profile-avatar.is-rank-silver,
.bs-profile-avatar.is-rank-bronze {
    outline: 2px solid var(--tier-hue);
    outline-offset: 3px;
}
@media (max-width: 640px) {
    .bs-profile-hero {
        margin: 0.5rem;
        padding: 1.5rem 1.25rem 2rem;
        gap: 1.5rem;
    }
}


/* Editorial split - identity | portrait. Grid collapses to one column on
   narrow screens; there the portrait floats top-right of the identity
   block via `order` + `float`-like sizing so the overline + name still
   read as a single block above the bio. */
.bs-profile-hero-grid {
    position: relative;
    display: grid;
    /* Portrait first (left), identity second (right) - LTR eye-flow starts
       at the face, moves to the name. Mobile keeps the same order so the
       portrait pins top-left of the identity block. Gap tightened from
       2.25rem so the accent rail + identity padding don't add up to a
       cavernous span between portrait and content. */
    grid-template-columns: auto minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
}
@media (max-width: 640px) {
    .bs-profile-hero-grid { gap: 1rem; }
}
/* Identity column - right of the portrait. Accent rail on its left edge
   is the one accent structural mark on the card: 2px, spans the column
   height, sits in the grid gap between portrait and identity. Acts as
   both a framing element and the section marker (replaces the previous
   tick + surface-raised divider). */
.bs-profile-identity {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    border-left: 2px solid var(--accent);
    padding-left: 1.5rem;
}
@media (max-width: 640px) {
    .bs-profile-identity { padding-left: 1rem; }
}
.bs-profile-portrait {
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;
}
.bs-profile-overline {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 0.25rem;
}
.bs-profile-hero-sep {
    margin: 0 0.5rem;
    color: var(--surface-raised);
}

/* Avatar - clean circular portrait, no accent ring. Sized in surface-raised
   with a 1px inset rim so the disc has a defined edge on either theme. */
.bs-profile-avatar {
    position: relative;
    /* Compact on mobile so the identity column keeps room for the display
       name; steps up on tablet/desktop where the split layout gives the
       portrait its own column to breathe. */
    width: 96px;
    height: 96px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
}
@media (min-width: 641px) {
    .bs-profile-avatar { width: 168px; height: 168px; }
}
@media (min-width: 900px) {
    .bs-profile-avatar { width: 200px; height: 200px; }
}
.bs-profile-avatar-inner {
    position: relative;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-raised);
    display: grid;
    place-items: center;
    /* Inset 1px rim used as a border (not a drop shadow). Frames the photo
       edge and gives the initial fallback a defined disc outline. */
    box-shadow: inset 0 0 0 1px var(--surface-raised);
}
.bs-profile-avatar-inner img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.bs-profile-avatar-initial {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    /* Scales with the avatar (112 → 200) so the letter always occupies
       roughly half the disc; feels right at every breakpoint. */
    font-size: clamp(3rem, 12vw, 5.5rem);
    color: var(--accent);
    line-height: 1;
}
/* Loading skeleton for the avatar - matches the live footprint at every
   breakpoint so the layout doesn't jump when /me lands. */
.bs-profile-avatar-skel {
    width: 96px;
    height: 96px;
    border-radius: 50%;
    background: var(--surface-raised);
    position: relative;
    overflow: hidden;
    flex: 0 0 auto;
}
@media (min-width: 641px) {
    .bs-profile-avatar-skel { width: 168px; height: 168px; }
}
@media (min-width: 900px) {
    .bs-profile-avatar-skel { width: 200px; height: 200px; }
}
.bs-profile-avatar-skel::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
    animation: bs-skel-shimmer 1.4s ease-in-out infinite;
}

/* Headline stack - display name in the display-lg slot per spec §3, real
   name muted below. Chip renders inline next to the name when the member
   hasn't chosen a nickname yet. */
.bs-profile-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 1rem;
}
.bs-profile-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.025em;
    /* Ceiling raised from 3.5rem so the display-lg slot actually reads
       "hero" on the split layout - the identity column has ~55% of the
       card width at desktop and can carry it. */
    font-size: clamp(2.5rem, 8vw, 4.75rem);
    line-height: 0.98;
    margin: 0;
    color: var(--text);
    word-break: break-word;
}
/* Real name → byline. Tiny mono uppercase with a leading slash so it reads
   as a credit under the display name (like a print byline) rather than a
   disconnected muted body line. Slash uses --accent to tie into the rail. */
.bs-profile-realname {
    flex-basis: 100%;
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    color: var(--text-muted);
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin-top: 0.35rem;
}
.bs-profile-realname-slash {
    color: var(--accent);
    font-weight: 600;
}
/* Muted display-name variant - used by the private/unknown state on the
   peer profile page. Keeps the display-lg treatment but drops color so it
   reads as an empty-state header, not identity. */
.bs-profile-name-muted { color: var(--text-muted); }
/* Muted initial variant for the private avatar - the dot glyph reads
   quiet against surface-raised without burning the accent. */
.bs-profile-avatar-initial-muted {
    color: var(--text-muted) !important;
    font-size: 2.5rem !important;
}

/* Bio pull-out. Quiet left rail (surface-raised, not accent - accent is
   for active state, focus, live states per spec §2) with a small indent,
   so the bio reads as the person's voice without a Vogue-cover drop-cap.
   Elevation is a border, per spec §4. Slightly larger + more airy than
   default body so it feels considered. */
.bs-profile-bio {
    margin: 0;
    padding: 0.1rem 0 0.1rem 0.9rem;
    border-left: 2px solid var(--surface-raised);
    color: var(--text);
    font-size: 1.0625rem;
    line-height: 1.65;
    max-width: 52ch;
    white-space: pre-wrap;
}

/* Empty-state chip when nickname or bio is missing - dashed outline, quiet,
   turns solid accent on hover so it reads as "there's a thing to do here"
   without shouting. */
.bs-profile-chip {
    display: inline-block;
    padding: 0.35rem 0.75rem;
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-pill);
    color: var(--accent);
    font-size: 0.85rem;
    line-height: 1.4;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
}
.bs-profile-chip:hover,
.bs-profile-chip:focus-visible {
    color: var(--accent-ink);
    background: var(--accent);
    border-color: var(--accent);
    outline: none;
}
.bs-profile-chip-muted { color: var(--text-muted); }

/* Inline stat row - Instagram-style trio with editorial dot dividers.
   Sits directly under the bio inside the identity column, replacing the
   older bordered .bs-profile-stats grid for the new split layout. Numbers
   stay JetBrains Mono per spec §3 (data role), labels stay in the tiny
   uppercase overline treatment. */
.bs-profile-stats-inline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem 1rem;
    margin: 0.25rem 0 0;
    padding: 0;
}
.bs-profile-stat-inline {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: 0;
    position: relative;
}
.bs-profile-stat-inline + .bs-profile-stat-inline::before {
    content: "·";
    color: var(--surface-raised);
    margin-right: 0.6rem;
    font-size: 1rem;
    line-height: 1;
}
.bs-profile-stat-inline-value {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: 1.25rem;
    line-height: 1;
    color: var(--text);
    margin: 0;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.bs-profile-stat-inline-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    line-height: 1;
    color: var(--text-muted);
    margin: 0;
}
/* Attendance-mood emoji renders larger and drops the mono font - the glyph is
   the whole point of the stat, and mono squashes the emoji baseline. */
.bs-profile-attendance-mood {
    font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', system-ui, sans-serif;
    font-size: 1.6rem;
    letter-spacing: 0;
    cursor: help;
}

/* Actions row - one filled (Edit), one outlined (Find members). Wraps on
   narrow screens so both stay legible without pinching. */
.bs-profile-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.25rem;
}

/* Sub-page header (Notifications / Password / Privacy) with a "back to hub" link */
.bs-profile-sub {
    display: grid;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

/* Unified back chip - single source of truth used by <PageBack> across every
   detail / sub-page in the app. Replaces the earlier .bs-session-back /
   .bs-profile-back / .bs-trial-back drift. 44x44 tap target per spec §8;
   focus ring is 2px --accent with 2px offset per spec §8; --text-muted at
   rest, --text on hover, --accent on focus-visible - the same three-state
   pattern the app-bar links use. */
.bs-page-back {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    min-height: 44px;
    padding: 0.5rem 0.75rem 0.5rem 0;
    margin: 0 0 0.75rem -0.25rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: 500;
    text-decoration: none;
    background: transparent;
    border: 0;
    cursor: pointer;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2, 0.8, 0.2, 1));
}
.bs-page-back:hover { color: var(--text); }
.bs-page-back:focus-visible {
    color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

.bs-notif-push-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem;
}
.bs-notif-what ul {
    margin: 0.5rem 0 0;
    padding-left: 1.25rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.7;
}

.bs-profile-privacy-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}
.bs-profile-danger {
    border: 1px solid rgba(229, 72, 77, 0.35);
    background: rgba(229, 72, 77, 0.04);
}

/* Admin nav sub-row (indented Classes/Instructors under Schedule, etc.) */
.bs-nav-sub .mud-nav-link-text { padding-left: 0.5rem; }
.bs-nav-sub .mud-nav-link-icon-default { color: var(--text-muted); }
.bs-nav-badge {
    height: 20px !important;
    min-width: 20px;
    font-size: 0.7rem !important;
    padding: 0 6px !important;
}

/* -------- Brand lockup (wolf + wordmark in the app bar) -------- */
.bs-brand-lockup {
    display: inline-flex;
    align-items: center;
    gap: clamp(14px, 1vw, 22px);
    /* Establish an isolated stacking context so .bs-brand-mark's
       mix-blend-mode:screen blends against a predictable backdrop
       (the header surface / photo behind it) instead of leaking to
       whatever the ancestor layout produces. */
    isolation: isolate;
}
.bs-brand-mark {
    /* Fluid: 56px on narrow desktop, up to 88px on 2K+. A fixed 60px
       looked dwarfed against the widened right-of-lockup whitespace on
       ultrawide displays (m99b). */
    width: clamp(3.5rem, 3.6vw, 5.5rem);
    height: clamp(3.5rem, 3.6vw, 5.5rem);
    display: block;
    /* SVG carries a baked-in black canvas (needed for the PWA icon /
       favicon square tile). In-page we want the wolf mark to sit on the
       host surface, not on its own black box - the black was #000 vs our
       --bg #101312 and read as a visible dark rectangle. `screen` blend
       drops black to backdrop while keeping the red gradient + white
       wordmark; parent isolation keeps the blend predictable. */
    mix-blend-mode: screen;
    background: transparent;
}
.bs-brand-word {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    /* Wordmark sized to hold its own next to the fluid wolf mark - 22px on
       narrow desktop, 38px on 2K+. Reads as a brand title, not a nav label. */
    font-size: clamp(1.375rem, 1.9vw, 2.375rem);
    letter-spacing: -0.01em;
    color: var(--text);
    line-height: 1;
    text-transform: uppercase;
}
/* Accent bar between the wolf mark and the wordmark.
   The mark is the identity, the word says the name - the red bar is the
   single tie between them and the only accent moment on the strip. */
.bs-brand-divider {
    width: clamp(2px, 0.2vw, 4px);
    height: clamp(38px, 2.6vw, 60px);
    background: var(--accent);
    border-radius: 2px;
    display: inline-block;
    flex-shrink: 0;
}

/* ==========================================================================
   Header - Variant B (m91). Replaces the old MudAppBar treatment. Sticky at
   the top; renders transparent-over-hero on the landing page (add
   `is-transparent`), solidifies past the hero via the header scroll observer
   (adds `is-solid`), stays solid on every other page by default.
   ========================================================================== */
.bs-app-bar {
    position: sticky;
    top: 0;
    z-index: 60;
    background: var(--surface);
    border-bottom: 1px solid var(--surface-raised);
    transition: background 0.2s ease, border-color 0.2s ease;
}
.bs-app-bar.is-transparent {
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
    border-bottom-color: transparent;
    border-bottom-width: 0;
}
.bs-app-bar.is-transparent.is-solid {
    /* §1 forbids glassmorphism — was `backdrop-filter: blur(12px)` on top of
       a 12% translucent surface. Flipped to fully opaque so the header
       still reads as a solid plane once the hero has scrolled off. */
    background: var(--surface);
    border-bottom-color: transparent;
    border-bottom-width: 0;
}
.bs-app-bar-inner {
    /* Full-viewport header (m94). Logo anchors to the left edge, CTAs to
       the right, at the same fluid padding as every landing section below
       so the vertical spine of the page aligns without centering the
       whole layout in a middle band. Height fluid via --bs-app-bar-h so
       a 56→88px wolf mark has room to breathe on every desktop size -
       a fixed height looked flat once the mark scaled up on ultrawide.
       Horizontal padding uses max(design-value, env(safe-area-inset-*))
       so devices with rounded corners / dynamic islands (iPhone 14+ Pro,
       iPhone 16 Pro) push the profile avatar and sign-in link inward
       enough to stay fully visible under viewport-fit=cover. */
    width: 100%;
    max-width: 1440px;
    margin-inline: auto;
    height: var(--bs-app-bar-h);
    padding-block: 0;
    padding-inline-start: max(clamp(16px, 3vw, 32px), env(safe-area-inset-left));
    padding-inline-end: max(clamp(16px, 3vw, 32px), env(safe-area-inset-right));
    display: flex;
    align-items: center;
    gap: 12px;
    box-sizing: border-box;
}
.bs-app-bar-spacer { flex: 1; }
.bs-app-bar-right {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
/* Icon-only link on the app bar (search, etc.). 44x44 to meet the touch-target
   floor from §8; hover picks up the accent. Kept quiet so the bar stays the
   navigation surface, not a chip parade. */
.bs-app-bar-icon-link {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    border-radius: 50%;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
    /* Also used as a MudMenu activator (anon 3-dot menu), so normalize the
       button surface — no browser chrome, no default padding. */
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    font: inherit;
}
/* MudMenu wrapper for the anon 3-dot activator — MudBlazor renders its own
   <div> around the ActivatorContent that would otherwise take normal flow
   height. Force it to inline-flex so the button sits on the header row
   without a phantom extra line. */
.bs-app-bar-more-menu {
    display: inline-flex;
    align-items: center;
}
.bs-app-bar-icon-link:hover,
.bs-app-bar-icon-link:focus-visible {
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    outline: none;
}
.bs-app-bar.is-transparent .bs-app-bar-icon-link { color: var(--text); }
/* Header icon-link container needs relative positioning so the notification
   dot can anchor to its top-right corner. */
.bs-app-bar-icon-link { position: relative; }
.bs-app-bar-dot {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 2px var(--bg);
}

.bs-app-bar-link {
    padding: 0 26px;
    min-height: 52px;
    display: inline-flex;
    align-items: center;
    color: var(--text-muted);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1rem, 1.05vw, 1.125rem);
    font-weight: 600;
    text-decoration: none;
    border: 1px solid var(--surface-raised);
    background: transparent;
    border-radius: var(--radius-pill);
    transition: color 0.15s ease, border-color 0.15s ease;
    line-height: 1;
    box-sizing: border-box;
}
.bs-app-bar-link:hover { color: var(--text); border-color: var(--accent); }
.bs-app-bar.is-transparent .bs-app-bar-link {
    border-color: color-mix(in srgb, var(--text) 25%, transparent);
    /* §1 forbids glassmorphism — was `backdrop-filter: blur(6px)` softening
       a 25% black plate over the hero image. Bumped the plate opacity so the
       link still reads without the blur. */
    background: rgba(0, 0, 0, 0.5);
    color: var(--text);
}
/* Avatar shell for the signed-in state */
.bs-avatar-shell {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 3px 12px 3px 3px;
    background: transparent;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    color: var(--text);
    cursor: pointer;
}
.bs-app-bar.is-transparent .bs-avatar-shell {
    border-color: color-mix(in srgb, var(--text) 25%, transparent);
    /* §1 forbids glassmorphism — same treatment as the link plate above. */
    background: rgba(0, 0, 0, 0.5);
}
.bs-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--surface-raised);
    color: var(--text);
    display: grid;
    place-items: center;
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.02em;
}
/* Uploaded avatar image variant - same 40x40 circle, but the image fills
   the circle via object-fit. Overrides the initials-mode grid layout so an
   <img> doesn't inherit place-items:center weirdness. */
.bs-avatar-img {
    display: block;
    object-fit: cover;
    padding: 0;
    background: var(--surface-raised);
}
.bs-avatar-caret { display: inline-flex; align-items: center; color: var(--text-muted); }

@media (max-width: 720px) {
    :root { --bs-app-bar-h: 72px; }
    .bs-app-bar-inner {
        padding-block: 0;
        padding-inline-start: max(16px, env(safe-area-inset-left));
        padding-inline-end: max(16px, env(safe-area-inset-right));
        gap: 10px;
    }
    .bs-brand-lockup { gap: 10px; }
    .bs-brand-mark { width: 40px; height: 40px; }
    .bs-brand-word { font-size: 17px; }
    .bs-brand-divider { height: 28px; width: 2px; }
    .bs-app-bar-right { gap: 6px; }
    /* Sign in stays visible on mobile - the whole point of the header for
       signed-out visitors. Sized to 44px touch minimum. */
    .bs-app-bar-link {
        padding: 0 16px;
        min-height: 44px;
        font-size: 14px;
    }
    .bs-avatar-shell { padding: 3px; }
    .bs-avatar-shell .bs-avatar-caret { display: none; }
}
/* Wordmark-hide breakpoint bumped from 380 → 480. The AUTHED shell puts
   FOUR icons on the right (language + search + bell + avatar) alongside
   the logo lockup; on iPhone Pro / Pro Max (390-430 CSS px) the total
   crosses the viewport once safe-area padding is subtracted, which used
   to trigger iOS Safari's content-fit heuristic and open the page at
   ~1.25x zoom until the user pinched out. Dropping the wordmark on any
   phone-sized viewport keeps the header inside the viewport and avoids
   the zoom-in-on-login bug. Wolf mark carries identity on its own. */
@media (max-width: 480px) {
    .bs-app-bar-inner {
        padding-block: 0;
        padding-inline-start: max(12px, env(safe-area-inset-left));
        padding-inline-end: max(12px, env(safe-area-inset-right));
    }
    .bs-brand-word,
    .bs-brand-divider { display: none; }
}
/* Right-hand action cluster must not shrink — the icons are the primary
   nav on mobile (search, notifications, account). Left lockup gets any
   slack via min-width:0 so it can never push the right off-screen. */
.bs-brand-lockup { min-width: 0; }
.bs-app-bar-right { flex-shrink: 0; }

/* Admin drawer eyebrow now that the drawer's brand lockup is gone (m91) */
.bs-admin-drawer-eyebrow {
    padding: 20px 20px 12px;
    color: var(--text-muted);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* ==========================================================================
   Landing v2 (m91) - hero + record. Sections beyond hero land in m92.
   ========================================================================== */

/* Hero - full-bleed background image with parallax + wolf watermark */
.bs-landing-v2 { color: var(--text); }
.bs-landing-hero-v2 {
    position: relative;
    min-height: min(720px, 92vh);
    /* Pull the hero up under the sticky transparent header so the image
       reads as truly full-bleed edge to edge, not "starts below a bar".
       Uses --bs-app-bar-h so this always matches .bs-app-bar-inner's
       height, at every viewport width. */
    margin-top: calc(-1 * var(--bs-app-bar-h));
    padding-top: var(--bs-app-bar-h);
    display: grid;
    grid-template-rows: 1fr auto;
    overflow: hidden;
    isolation: isolate;
}
/* Wolf watermark removed - CLAUDE.md restricts the mark to PWA icon /
   splash / favicon / loading indicator / empty-state mark. The header
   lockup already carries identity in the hero, and on 2K the watermark's
   black-canvas SVG read as a visible boxed logo against bright regions of
   the photograph rather than a subtle mark. */
.bs-landing-hero-image {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: url("../img/landing/hero.jpg");
    background-size: cover;
    background-position: center 30%;
    filter: saturate(0.9) contrast(1.05);
    will-change: transform;
    /* JS applies a slow translateY on scroll for parallax; this keeps a
       tiny scale so edges don't reveal on shift. */
    transform: translateZ(0) scale(1.02);
}
.bs-landing-hero-grade {
    /* Warm red tint on shadows + top-and-bottom vignette. This is what pulls
       any future crew photo into one editorial world. */
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
        radial-gradient(60% 55% at 22% 22%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 55%),
        linear-gradient(180deg, rgba(0, 0, 0, 0.28) 0%, transparent 30%, rgba(0, 0, 0, 0.6) 100%);
}
.bs-landing-hero-content {
    position: relative;
    z-index: 3;
    /* Hero content column (m101): hero PHOTOGRAPH stays edge-to-edge via
       .bs-landing-hero-image; the eyebrow/title/CTA cap at the shared
       1200px content column and center, so they line up with every
       section head below AND every other page's content column. */
    width: 100%;
    max-width: 1200px;
    margin-inline: auto;
    padding: 40px var(--bs-landing-pad, clamp(16px, 3vw, 24px)) 56px;
    align-self: end;
    box-sizing: border-box;
}
.bs-landing-hero-eyebrow {
    display: inline-block;
    font-family: 'Manrope', system-ui, sans-serif;
    /* Overline: static 11px on mobile, up to 13px on 2K. Uppercase eyebrows
       benefit from a slight bump on wide displays but a big fluid range makes
       the tracking look inconsistent (m95). */
    font-size: clamp(11px, 0.75vw, 13px);
    font-weight: 700;
    letter-spacing: 0.16em;
    color: color-mix(in srgb, var(--text) 85%, transparent);
    margin-bottom: 20px;
    text-transform: uppercase;
    line-height: 1;
}
.bs-landing-hero-title {
    font-family: 'Manrope', system-ui, sans-serif;
    /* display-lg per the m95 fluid scale - 40px on mobile, 104px on 2K.
       line-height 1.15 → 1.22 (m98b) because a 100px Greek headline with tight
       leading kept collapsing "διδάσκουμε" into the sub line. 1.22 finally
       gives descenders on ρ/μ/η/β enough room. */
    font-size: clamp(2.5rem, 6.5vw, 6.5rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.22;
    color: var(--text);
    /* Extra padding-bottom in the title's own box so the descender zone of
       the LAST line is owned by the title, not encroached on by the sub's
       margin math. Prevents margin-collapse edge cases too. */
    margin: 0 0 clamp(40px, 3.4vw, 72px);
    padding-bottom: clamp(4px, 0.6vw, 12px);
    /* The i18n string owns line structure via explicit <br/>. max-width was
       14ch which force-wrapped the widest intended line
       ("Πρωταθλητές Βαλκανίων", 21 chars) mid-line on 2K, producing 5 lines
       instead of the intended 3. Widen enough to fit the longest planned
       line ("Τώρα διδάσκουμε στον Βαρδάρη.", 29 chars) so natural wrapping
       stays out of the way. text-wrap:balance dropped for the same reason -
       balancing on top of forced breaks kept re-flowing lines. */
    max-width: 32ch;
}
.bs-landing-hero-sub {
    color: color-mix(in srgb, var(--text) 82%, transparent);
    /* Body-large: 16px mobile → 22px on 2K. */
    font-size: clamp(1rem, 1.4vw, 1.375rem);
    line-height: 1.6;
    max-width: 52ch;
    margin: 0 0 40px;
}
.bs-landing-hero-cta-row {
    display: flex;
    align-items: center;
    gap: 24px;
    flex-wrap: wrap;
}
.bs-landing-hero-primary {
    padding: clamp(14px, 1vw, 18px) clamp(24px, 2vw, 34px);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    font-weight: 700;
    letter-spacing: -0.005em;
    text-decoration: none;
    border-radius: var(--radius-pill);
    transition: transform 0.15s var(--ease), filter 0.15s var(--ease);
}
.bs-landing-hero-primary:hover { transform: translateY(-1px); filter: brightness(1.08); }
.bs-landing-hero-secondary {
    color: color-mix(in srgb, var(--text) 78%, transparent);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    font-weight: 600;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0.5rem 0.25rem;
}
.bs-landing-hero-secondary:hover { color: var(--text); }

/* "The record" - tabular credibility strip in JetBrains Mono numerals.
   Raised full-bleed band (m101): the record is the first "featured" section
   after the hero photo. Lifting its bg by one surface step turns the flat
   asphalt into visual rhythm on wide viewports without adding chrome the
   spec forbids. Bottom border removed - the next section's edge-to-edge
   top hairline (see .bs-landing-section rule) handles that divider. */
.bs-landing-record {
    background: color-mix(in srgb, var(--surface) 40%, var(--bg));
    border-top: 1px solid var(--surface-raised);
}
.bs-landing-record-inner {
    /* Record strip inner (m101): outer .bs-landing-record can span viewport
       for its bordered background, but the three stats cap at the shared
       1200px column so the first stat sits directly under the hero title
       above and the section heads below. */
    width: 100%;
    max-width: 1200px;
    margin-inline: auto;
    padding: 32px var(--bs-landing-pad, clamp(16px, 3vw, 24px));
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(24px, 4vw, 64px);
    box-sizing: border-box;
}
.bs-landing-record-item {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: 20px;
}
.bs-landing-record-num {
    font-family: 'JetBrains Mono', ui-monospace, "Consolas", monospace;
    /* Numerals scale further on 2K - 36px→72px so the record reads as a
       proper tape of credibility, not a footer of small stats (m95). */
    font-size: clamp(2.25rem, 4vw, 4.5rem);
    font-weight: 500;
    letter-spacing: -0.02em;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.bs-landing-record-label {
    color: var(--text-muted);
    /* Body fluid: 14px→18px so the label pulls its own weight next to a 72px
       numeral (was 14→16 which the numeral overwhelmed at 2K). */
    font-size: clamp(0.875rem, 1.05vw, 1.125rem);
    line-height: 1.45;
}
@media (max-width: 720px) {
    .bs-landing-record-inner { grid-template-columns: 1fr; gap: 20px; padding: 24px 16px; }
    .bs-landing-record-num { font-size: 32px; }
}
/* Leaderboard link that sits at the bottom of the Battles section - only
   rendered when battles exist (parent section hides on empty), which is the
   thematic gate: points come from battles, so the link belongs where the
   battles do. Ghost styling; the hero CTA keeps the accent (CLAUDE.md §2). */
.bs-landing-lb-row {
    display: flex;
    justify-content: flex-end;
    margin-top: 20px;
}
.bs-landing-lb-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--surface-raised);
    background: var(--surface);
    color: var(--text);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.9375rem;
    font-weight: 600;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease);
}
.bs-landing-lb-link:hover,
.bs-landing-lb-link:focus-visible {
    border-color: color-mix(in srgb, var(--accent) 40%, var(--surface-raised));
    background: var(--surface-raised);
}
.bs-landing-lb-link .mud-icon-root { font-size: 18px; color: var(--text-muted); }
@media (max-width: 720px) {
    .bs-landing-lb-row { justify-content: stretch; }
    .bs-landing-lb-link { flex: 1; justify-content: center; }
}

/* Landing v2 sections (m101 revised):
   Sections span viewport width so future full-bleed backgrounds have room,
   but each section's DIRECT children cap at the shared 1200px column and
   center - so section heads, grids, and CTAs align on the same left edge
   as every other page's content. --bs-landing-pad supplies the inner
   gutter for narrow viewports (matches the app container's 16→24px). */
.bs-landing-v2 {
    --bs-landing-pad: clamp(16px, 3vw, 24px);
    /* Grid children default to min-width: auto (= min-content), so a
       flex/grid grandchild with an intrinsic width larger than the phone
       viewport can drag the whole wrapper wider than the screen — which
       reflows every section wider than the viewport and cuts text off on
       the right edge. min-width:0 lets the wrapper shrink to its parent
       width regardless. Not clipping overflow here because the hero
       section below pulls up UNDER the sticky header via a negative
       margin-top, and any overflow:* value other than visible would turn
       overflow-y into auto (per spec), truncating that pull-under; the
       html/body clip further up the tree catches any leftover overflow. */
    max-width: 100%;
    min-width: 0;
}
.bs-landing-v2 .bs-landing-section {
    width: 100%;
    /* Same rationale as .bs-landing-v2 above — the section is a grid child
       of .bs-landing, so its min-width defaults to min-content and any
       wide grandchild would push the section (and therefore the whole
       wrapper) past viewport width. */
    min-width: 0;
    padding: clamp(48px, 6vw, 96px) 0;
    /* Edge-to-edge hairline divider between sections (m101). Turns the flat
       asphalt into a chunked composition so the empty gutters at wide
       viewports read as intentional negative space, not unused canvas.
       Uses `--surface-raised` so it matches every other divider in the app
       (cards, inputs, drawer separators). */
    border-top: 1px solid var(--surface-raised);
}
.bs-landing-v2 .bs-landing-section > * {
    width: 100%;
    max-width: 1200px;
    margin-inline: auto;
    padding-inline: var(--bs-landing-pad);
    box-sizing: border-box;
}
/* Featured full-bleed bands (m101). Three "anchor" sections lift by one
   surface step so the landing has visual rhythm without alternating
   every-other bg (which is fragile with conditionally-rendered sections
   like news/seminars/battles/crew/events). Picked because these three are
   ALWAYS rendered: thisweek (the product), where (the terminal CTA). */
.bs-landing-v2 .bs-landing-section#thisweek,
.bs-landing-v2 .bs-landing-section#where {
    background: color-mix(in srgb, var(--surface) 40%, var(--bg));
}
@media (max-width: 720px) {
    .bs-landing-v2 { --bs-landing-pad: 16px; }
    .bs-landing-v2 .bs-landing-section { padding: 40px 0; }
}

/* "This week" preview - ClassCards are horizontal atoms (ring + title + action).
   Cap each card so a 1600px-wide section doesn't stretch them into bars. */
.bs-landing-v2 #thisweek .bs-classcard,
.bs-landing-v2 #thisweek .bs-session-day,
.bs-landing-v2 #thisweek .bs-empty,
.bs-landing-v2 #thisweek .bs-skeleton { max-width: 960px; }

/* Scroll-reveal utility - used by m91 hero + record, extended in m92 */
.bs-reveal {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.8s cubic-bezier(0.2, 0.8, 0.2, 1),
                transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
    transition-delay: var(--bs-reveal-delay, 0ms);
    will-change: opacity, transform;
}
.bs-reveal.is-in { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
    .bs-reveal { opacity: 1; transform: none; transition: none; }
}

/* ==========================================================================
   Landing v2 sections (m92) - News / Who / Classes / Crew / Events / Where /
   Footer. Shared head + eyebrow + title primitives, then per-section grids.
   ========================================================================== */

/* Shared section head - eyebrow + big title + optional sub, "See all" pill
   floated to the right when there's an extra action. */
.bs-landing-v2 .bs-landing-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    margin-bottom: 40px;
}
.bs-landing-eyebrow-v2 {
    display: block;
    font-family: 'Manrope', system-ui, sans-serif;
    /* Section identifier ("BATTLES", "ΖΩΝΤΑΝΌ ΠΡΌΓΡΑΜΜΑ") - promoted from
       11-13px tag to 24-40px display header. Letter-spacing eased from
       0.16em → 0.10em because tight tracking on 40px text starts to look
       stretched. Weight/color/uppercase preserved. */
    font-size: clamp(1.5rem, 2.4vw, 2.5rem);
    font-weight: 800;
    letter-spacing: 0.10em;
    color: var(--accent-strong);
    text-transform: uppercase;
    margin-bottom: 16px;
    line-height: 1.05;
}
.bs-landing-title-v2 {
    font-family: 'Manrope', system-ui, sans-serif;
    /* display - 32px→68px. Bumped from 28→60 so the title clearly outweighs
       the catch line beneath it at every viewport, without crossing into hero
       territory (hero peaks at 104px). */
    font-size: clamp(2rem, 4.2vw, 4.25rem);
    font-weight: 800;
    letter-spacing: -0.028em;
    line-height: 1.02;
    margin: 0 0 12px;
    text-wrap: balance;
    color: var(--text);
}
.bs-landing-sub-v2 {
    color: var(--text-muted);
    /* body: 15px→18px. Trimmed from 16→20 so the catch line reads as
       supporting copy, not a second title. Reading line held at ≤60ch. */
    font-size: clamp(0.9375rem, 1vw, 1.125rem);
    font-weight: 400;
    line-height: 1.55;
    margin: 0;
    max-width: 60ch;
}
.bs-landing-more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    font-weight: 600;
    padding: 10px 18px;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    text-decoration: none;
}
.bs-landing-more:hover { border-color: var(--accent); color: var(--accent); }
@media (max-width: 720px) {
    .bs-landing-v2 .bs-landing-head { margin-bottom: 28px; }
}

/* Placeholder image tile - the wwwroot/img/landing/*.jpg source + a grade
   overlay (red tint on shadows + bottom vignette). Same treatment across
   who / crew / events so all imagery lands as one editorial world. */
.bs-landing-ph {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-card);
    /* Was `#0a0a0a` - raw hex that stayed near-black on the light theme's
       #ECEBE7 canvas, punching through the section as a stark rectangle.
       Token-driven so both themes get the same "underneath the photo" tone. */
    background: var(--bg);
    isolation: isolate;
}
.bs-landing-ph::before {
    content: "";
    position: absolute; inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: saturate(0.85) contrast(1.05);
    transition: transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
    z-index: 1;
}
.bs-landing-ph:hover::before { transform: scale(1.04); }
.bs-landing-ph::after {
    content: "";
    position: absolute; inset: 0;
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent 40%),
        repeating-linear-gradient(115deg, transparent 0 3px, rgba(255,255,255,0.018) 3px 4px),
        radial-gradient(140% 110% at 50% -20%, transparent 55%, rgba(0,0,0,0.5) 100%),
        linear-gradient(180deg, transparent 40%, rgba(0,0,0,0.82) 100%);
    z-index: 2;
    pointer-events: none;
}
.bs-landing-ph-tall     { aspect-ratio: 4 / 5; }
.bs-landing-ph-square   { aspect-ratio: 1 / 1; }
.bs-landing-ph-wide     { aspect-ratio: 16 / 9; }

.bs-landing-img-hero::before        { background-image: url("../img/landing/hero.jpg"); }
.bs-landing-img-who::before         {
    /* image-set: WebP first for supporting browsers (120 KB), JPEG fallback
       for older Safari / legacy Android (233 KB). The plain background-image
       above is the ultimate fallback if image-set is unsupported (Firefox
       ESR < 88, IE — neither is a target). */
    background-image: url("../img/landing/blackout_crew.jpg");
    background-image: image-set(
        url("../img/landing/blackout_crew.webp") type("image/webp"),
        url("../img/landing/blackout_crew.jpg") type("image/jpeg")
    );
}
.bs-landing-img-foundations::before { background-image: url("../img/landing/foundations.jpg"); }
.bs-landing-img-power::before       { background-image: url("../img/landing/power.jpg"); }
.bs-landing-img-crew::before        { background-image: url("../img/landing/crew.jpg"); }
.bs-landing-img-events::before      { background-image: url("../img/landing/events.jpg"); }

/* -------- News / announcements grid -------- */
/* Flex-wrap instead of grid (m94) so 2 items don't leave a broken empty
   3rd column on wide screens. Cards grow to a cap of 520px each, then
   wrap once row width is exhausted. With 2 items on 2K they read as a
   pair packed to the left, not "3-column layout with one missing". */
.bs-landing-news-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 24px;
}
.bs-landing-news-card-v2 {
    flex: 1 1 320px;
    max-width: 640px;
}
.bs-landing-news-card-v2 {
    display: grid;
    grid-template-rows: auto 1fr auto auto;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 24px;
    gap: 12px;
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.2s ease;
}
.bs-landing-news-card-v2:hover { border-color: var(--accent); }
.bs-landing-news-card-v2.is-pinned { border-color: var(--accent); }
/* Cover image variant - collapses the top padding so the image sits flush
   against the card border, then re-pads the meta/title/body block. Kept as
   a horizontal band, not full-bleed hero, so 3 cards stay a comfortable
   scroll on the landing. */
.bs-landing-news-card-v2.has-cover {
    padding: 0;
    overflow: hidden;
    grid-template-rows: auto auto 1fr auto auto;
}
.bs-landing-news-cover {
    aspect-ratio: 16 / 9;
    background: var(--surface-raised) center / cover no-repeat;
    border-bottom: 1px solid var(--surface-raised);
}
.bs-landing-news-card-v2.has-cover .bs-landing-news-meta,
.bs-landing-news-card-v2.has-cover .bs-landing-news-title-v2,
.bs-landing-news-card-v2.has-cover .bs-landing-news-body-v2,
.bs-landing-news-card-v2.has-cover .bs-landing-news-more {
    padding-left: 24px;
    padding-right: 24px;
}
.bs-landing-news-card-v2.has-cover .bs-landing-news-meta { padding-top: 20px; }
.bs-landing-news-card-v2.has-cover .bs-landing-news-more { padding-bottom: 24px; }
.bs-landing-news-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: clamp(11px, 0.75vw, 13px);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    line-height: 1;
}
.bs-landing-news-pinned-v2 {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--accent);
}
.bs-landing-news-title-v2 {
    font-family: 'Manrope', system-ui, sans-serif;
    /* title: 20px→26px so the headline holds up in a 640px-wide card on 2K. */
    font-size: clamp(1.25rem, 1.5vw, 1.625rem);
    font-weight: 700;
    letter-spacing: -0.015em;
    line-height: 1.22;
    color: var(--text);
    margin: 0;
}
.bs-landing-news-body-v2 {
    color: var(--text-muted);
    /* body: 15px→17px. Was 14 which reads as a caption inside a large card. */
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.55;
    margin: 0;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.bs-landing-news-more {
    color: var(--accent);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.8125rem, 0.85vw, 0.9375rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}
@media (min-width: 1600px) { .bs-landing-news-grid { gap: 32px; } }
@media (max-width: 600px)  { .bs-landing-news-card-v2 { max-width: none; } }

/* -------- Who we are (text + tall photo split) -------- */
/* Grid aligns with the section head via the new .bs-landing-section > *
   padding rule (m94). The PHOTO is what gets capped, not the grid - a
   photo at 1150px wide + 4/5 aspect is 1440px tall on 2K, which dwarfs
   the copy. Cap it at 520px so the split reads as "text + accompanying
   portrait" no matter how wide the viewport gets. */
/* Text-image split (m102) - text column bounded (the copy paragraphs already
   cap at 62ch so an infinite 1fr just left an ocean of whitespace on the
   right); image column fluid via clamp so it scales with viewport instead of
   sitting frozen at 520px on ultrawide; whole split capped at 1400px so the
   pair stays visually connected on 2K+ displays instead of the photo drifting
   to the far right edge of the section. */
.bs-landing-who-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(300px, 36vw, 560px);
    gap: clamp(32px, 4vw, 80px);
    /* Anchor both columns to the top. `center` used to re-vertical-center the
       image whenever the text column's height changed — and Greek text is
       ~15-20% longer than English, so switching language visibly moved the
       photo. With `start` the image's top edge sits at the same y-coordinate
       regardless of language; only the text column grows downward. */
    align-items: start;
    max-width: 1400px;
}
.bs-landing-who-copy p {
    color: var(--text-muted);
    /* body-large: 16px→19px so the crew story reads with authority next to
       a portrait, not as a caption (m95). */
    font-size: clamp(1rem, 1.15vw, 1.1875rem);
    line-height: 1.65;
    margin: 0 0 18px;
    max-width: 62ch;
}
.bs-landing-who-copy p b { color: var(--text); font-weight: 600; }
/* Bilingual editor pair (m101). When admin flips Edit mode on the landing,
   both language editors render stacked here - Greek on top, English below -
   so the pair can be edited in one visit without switching UI language.
   Simple column stack keeps the split responsive; the wider grid (photo on
   the right) isn't affected. */
.bs-landing-cms-lang-pair {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
.bs-landing-cms-lang-col > .bs-eyebrow {
    display: block;
    margin-bottom: 0.5rem;
}
/* Fills the grid cell (m102). Cell size is set by the split's fluid clamp
   column, so the image scales with viewport instead of freezing at 520px on
   ultrawide. width: 100% here NEVER collapses because the grid column is
   definite (clamp with px values) - the old collapse bug was `min(520px, 100%)`
   inside an `auto` track, which is no longer the situation. */
.bs-landing-who-media { width: 100%; }
/* Near-square (m102) - blackout_crew source is 1253×1204, so matching the
   container aspect keeps every outstretched hand in-frame and prevents the
   bottom vignette from landing on the crew's faces / bodies. */
.bs-landing-who-media .bs-landing-ph { aspect-ratio: 1 / 1; }
/* Real <img> variant of the landing-ph - sits at z-index 1 so the ::after
   editorial overlay (red tint + scanlines + vignette) still lands on top,
   matching every other landing photo without needing a CSS background rule. */
.bs-landing-who-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    z-index: 1;
    filter: saturate(0.85) contrast(1.05);
    transition: transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bs-landing-ph:hover > .bs-landing-who-photo { transform: scale(1.04); }
@media (max-width: 900px) {
    /* Stack vertically. Image caps at 520px and centers so it never blows up to
       full viewport on a portrait tablet or looks tacked-on to the copy edge. */
    .bs-landing-who-split { grid-template-columns: 1fr; gap: 32px; max-width: none; }
    .bs-landing-who-media { width: 100%; max-width: 520px; margin-inline: auto; }
}

/* -------- Crew portraits (4-across, full-card photo + text overlay bottom) -------- */
.bs-landing-crew-v2 { background: var(--surface); }
/* Same flex-wrap pattern (m94). Cap keeps a 4-portrait row from ballooning
   into 570×760px monster portraits on a 2K viewport. Also degrades gracefully
   when the crew list grows or shrinks - no fixed column count to fight. */
.bs-landing-crew-grid-v2 {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}
.bs-landing-crew-card-v2 {
    flex: 1 1 220px;
    max-width: 440px;
}
.bs-landing-crew-card-v2 {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    aspect-ratio: 3 / 4;
    isolation: isolate;
}
.bs-landing-crew-media-v2 {
    position: absolute; inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: saturate(0.85) contrast(1.05);
    z-index: 0;
    transition: transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bs-landing-crew-card-v2:hover .bs-landing-crew-media-v2 { transform: scale(1.04); }
.bs-landing-crew-card-v2::after {
    content: "";
    position: absolute; inset: 0;
    background:
        linear-gradient(180deg, transparent 40%, rgba(0,0,0,0.82) 100%);
    z-index: 1;
}
.bs-landing-crew-body-v2 {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 2;
    padding: 16px 18px 18px;
}
.bs-landing-crew-alias {
    font-family: 'Manrope', system-ui, sans-serif;
    /* title: 20px→26px so the name reads confidently over a portrait on 2K. */
    font-size: clamp(1.25rem, 1.5vw, 1.625rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--text);
    margin: 0 0 4px;
    line-height: 1.15;
}
.bs-landing-crew-role {
    color: color-mix(in srgb, var(--text) 72%, transparent);
    font-size: clamp(0.8125rem, 0.85vw, 0.9375rem);
    font-weight: 500;
    line-height: 1.4;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Card is now an <a> - kill the default link chrome and give it an
   accessible focus ring. The card-level hover already lifts the photo;
   the "read the story" caption below the bio makes the click affordance
   explicit without a second-accent violation (uses --text-muted). */
a.bs-landing-crew-card-v2 {
    text-decoration: none;
    color: inherit;
    display: block;
    cursor: pointer;
}
a.bs-landing-crew-card-v2:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.bs-landing-crew-more {
    display: block;
    margin-top: 6px;
    color: var(--accent);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.9;
    transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
a.bs-landing-crew-card-v2:hover .bs-landing-crew-more,
a.bs-landing-crew-card-v2:focus-visible .bs-landing-crew-more {
    opacity: 1;
    transform: translateX(2px);
}
@media (max-width: 600px) { .bs-landing-crew-card-v2 { max-width: none; } }

/* -------- Events (2-card grid, first wider) -------- */
/* Grid aligns with its section head. Each card is capped via minmax so 2
   items on a 2K viewport don't stretch into full-bleed banners - the first
   caps a bit wider than the second to preserve the 1.4/1 hierarchy. */
.bs-landing-events-grid {
    display: grid;
    grid-template-columns: minmax(0, 900px) minmax(0, 640px);
    justify-content: start;
    gap: clamp(20px, 2vw, 32px);
}
.bs-landing-event-card {
    position: relative;
    border-radius: var(--radius-card);
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    isolation: isolate;
}
.bs-landing-event-card:first-child .bs-landing-event-media { aspect-ratio: 16 / 10; }
.bs-landing-event-card:not(:first-child) .bs-landing-event-media { aspect-ratio: 4 / 3; }
.bs-landing-event-media {
    position: relative;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.bs-landing-event-media::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(180deg, transparent 40%, rgba(0,0,0,0.85) 100%);
}
.bs-landing-event-body {
    position: absolute;
    inset: 0;
    z-index: 2;
    padding: 28px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 8px;
}
.bs-landing-event-date {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    /* §1 no glassmorphism — was `backdrop-filter: blur(6px)` softening the
       date pill over the hero photo. Bumped plate opacity to 75% instead. */
    background: rgba(0,0,0,0.75);
    color: var(--text);
    border: 1px solid rgba(255,255,255,0.15);
    border-radius: var(--radius-pill);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: clamp(11px, 0.8vw, 13px);
    font-weight: 500;
    letter-spacing: 0.05em;
    align-self: flex-start;
    line-height: 1;
}
.bs-landing-event-title {
    font-family: 'Manrope', system-ui, sans-serif;
    /* title: 22px→36px so the event headline holds a 900px banner card on 2K. */
    font-size: clamp(1.375rem, 2.4vw, 2.25rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--text);
    margin: 0;
    text-wrap: balance;
}
.bs-landing-event-sub {
    color: color-mix(in srgb, var(--text) 78%, transparent);
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.5;
    margin: 0;
}
@media (max-width: 900px) { .bs-landing-events-grid { grid-template-columns: 1fr; } }

/* -------- Where to find us -------- */
/* Grid aligns with its section head. The MAP is what's capped, not the grid -
   at 1250px wide the map placeholder dominates the section; at 640px it reads
   as a companion element to the info column. */
.bs-landing-where-split {
    display: grid;
    grid-template-columns: minmax(320px, 520px) minmax(0, 640px);
    justify-content: start;
    gap: clamp(24px, 3vw, 64px);
}
.bs-landing-where-copy .bs-landing-where-line {
    display: grid;
    grid-template-columns: 32px 1fr;
    align-items: baseline;
    gap: 16px;
    padding: 16px 0;
    border-top: 1px solid var(--surface-raised);
}
.bs-landing-where-copy .bs-landing-where-line:first-child { border-top: 1px solid var(--surface-raised); }
.bs-landing-where-copy .mud-icon-root { color: var(--accent); }
.bs-landing-where-lbl {
    color: var(--text-muted);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(11px, 0.75vw, 13px);
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    display: block;
    margin-bottom: 6px;
    line-height: 1;
}
.bs-landing-where-val {
    color: var(--text);
    /* body: 15px→17px so the address/phone read comfortably on 2K. */
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.45;
    font-weight: 500;
}
.bs-landing-where-val a { color: var(--accent); font-weight: 600; text-decoration: none; }
.bs-landing-where-val a:hover { text-decoration: underline; }
.bs-landing-where-cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 18px;
    background: transparent;
    color: var(--accent);
    border: 1px solid var(--accent);
    border-radius: var(--radius-pill);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.8125rem, 0.9vw, 0.9375rem);
    font-weight: 600;
    cursor: pointer;
    margin-top: 6px;
}
.bs-landing-where-cta:hover { background: var(--accent); color: var(--accent-ink); }
/* Map column - the tile itself keeps its 4:3 frame, with the "open in native
   app" buttons sitting under it as a separate row. */
.bs-landing-where-map-col {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}
.bs-landing-where-map {
    position: relative;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-card);
    overflow: hidden;
    /* Was `linear-gradient(#16181a, #0d0f11)` - off-token darks that landed
       as a near-black slab on the light theme. Token pair keeps the same
       subtle vertical gradient in dark and stays inside the theme in light. */
    background: linear-gradient(180deg, var(--surface), var(--bg) 100%);
    display: grid;
    place-items: center;
    color: var(--text-muted);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: clamp(12px, 0.85vw, 14px);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    border: 1px solid var(--surface-raised);
}
.bs-landing-where-map:not(:has(.bs-landing-where-map-iframe))::before {
    /* §1 forbids concrete/brick/graph textures — was a 40px repeating-linear
       -gradient grid under the missing-map placeholder. Flat surface only,
       so the "map unavailable" state reads as intentional restraint rather
       than a broken texture. */
    content: "";
    position: absolute; inset: 0;
    background: var(--surface-raised);
    pointer-events: none;
}
.bs-landing-where-map-iframe {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    border: 0;
    display: block;
    /* Light theme: leave the map as-is. Dark theme: a mild filter so the bright
       Google canvas doesn't punch a hole through the dark section. */
}
:root[data-theme="dark"] .bs-landing-where-map-iframe,
:root:not([data-theme="light"]) .bs-landing-where-map-iframe {
    filter: invert(0.92) hue-rotate(180deg) saturate(0.85) brightness(0.95);
}
.bs-landing-where-map-placeholder {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    color: var(--text-muted);
    text-decoration: none;
}
.bs-landing-where-map-placeholder .mud-icon-root {
    color: var(--accent);
    font-size: 28px;
}
.bs-landing-where-map-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}
.bs-landing-where-map-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--surface-raised);
    background: var(--surface);
    color: var(--text);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.8125rem, 0.9vw, 0.9375rem);
    font-weight: 600;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
    flex: 1 1 auto;
    justify-content: center;
}
.bs-landing-where-map-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
}
.bs-landing-where-map-btn .mud-icon-root {
    color: var(--accent);
    font-size: 18px;
}
@media (max-width: 900px) { .bs-landing-where-split { grid-template-columns: 1fr; } }

/* -------- Footer v2 -------- */
.bs-landing-footer-v2 {
    padding: 48px 24px 40px;
    border-top: 1px solid var(--surface-raised);
    background: var(--bg);
}
.bs-landing-footer-inner {
    width: 100%;
    max-width: 1200px;
    margin-inline: auto;
    padding: 0 var(--bs-landing-pad, clamp(16px, 3vw, 24px));
    /* Centered stack: nav row on top, brand mark + copyright below.
       Frequent items stay here; the less-frequent ones (merch, useful
       links, legal) live in the top-right dropdown so the footer stays
       calm. */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    text-align: center;
    font-size: clamp(0.8125rem, 0.85vw, 0.9375rem);
    color: var(--text-muted);
    box-sizing: border-box;
}
.bs-landing-footer-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--text);
}
.bs-landing-footer-mark {
    width: 28px;
    height: 28px;
    border-radius: 8px;
}
.bs-landing-footer-nav {
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 8px;
    column-gap: 20px;
}
.bs-landing-footer-nav a {
    color: var(--text-muted);
    text-decoration: none;
}
.bs-landing-footer-nav a:hover { color: var(--text); }
/* Legal microtype strip — GDPR / ePrivacy audit culture expects privacy /
   terms / cookies in the footer, not tucked behind a header dropdown. Small,
   muted, centered, with · separators so it reads as a single legal line
   rather than competing with the primary nav. */
.bs-landing-footer-legal {
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: baseline;
    row-gap: 4px;
    column-gap: 8px;
    font-size: 12px;
    color: var(--text-muted);
    margin-top: 4px;
}
.bs-landing-footer-legal a {
    color: var(--text-muted);
    text-decoration: none;
}
.bs-landing-footer-legal a:hover { color: var(--text); }

/* -------- Global app footer (Components/AppFooter.razor) --------
   Minimal per-route footer rendered inside MudMainContent on every
   page. Brand mark + copyright + legal microtype strip — no primary
   nav, no social, nothing that would duplicate the header dropdowns
   or the member bottom-nav. Landing renders its own richer footer;
   the :has(.bs-landing-v2) rule at the bottom of this block hides
   this minimal variant there so we never stack two.

   Bottom clearance is layered:
     * base padding-bottom includes env(safe-area-inset-bottom) so an
       iPhone home-indicator doesn't clip the legal line
     * body:has(.bs-bottom-nav) adds ~80px of extra bottom padding so
       the strip stays above the member bottom-nav which floats
       fixed at viewport bottom (z-index 1200). Without this the
       tab bar would cover the strip on the scroll-to-bottom read. */
.bs-app-footer {
    border-top: 1px solid var(--surface-raised);
    padding: 20px clamp(16px, 3vw, 24px);
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    margin-top: 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-align: center;
    color: var(--text-muted);
    font-size: 12px;
    box-sizing: border-box;
}
body:has(.bs-bottom-nav) .bs-app-footer {
    padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
}
.bs-app-footer-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--text);
    text-decoration: none;
}
.bs-app-footer-mark {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    display: block;
}
.bs-app-footer-word {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 12px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text);
}
.bs-app-footer-legal {
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: baseline;
    row-gap: 4px;
    column-gap: 8px;
    font-size: 11px;
    color: var(--text-muted);
}
.bs-app-footer-legal a {
    color: var(--text-muted);
    text-decoration: none;
}
.bs-app-footer-legal a:hover { color: var(--text); }
/* Landing used to render its own richer footer (.bs-landing-footer-v2)
   with a primary-nav row; owner ask dropped that duplication, so
   AppFooter is now the single source of truth on / as well. The
   suppression rule that used to hide AppFooter on landing has been
   removed. */

/* -------- Skeleton primitives (see Components/Skeleton.razor) --------
   Layered shimmer over --surface-raised. Base colour is the raised surface so the
   loading state reads as "empty content ready to fill" rather than blocking chrome.
   The shimmer is a moving gradient across a wider background; --gradient-shimmer sets
   the highlight tint so themes can override it independently. */
.bs-skel {
    background: var(--surface-raised);
    border-radius: 4px;
    position: relative;
    overflow: hidden;
}
.bs-skel::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.06) 50%,
        transparent 100%
    );
    transform: translateX(-100%);
    animation: bs-skel-shimmer 1.4s ease-in-out infinite;
}
.bs-skel-circle { border-radius: 50%; }
.bs-skel-rect   { border-radius: var(--radius-input); }
.bs-skel-card   { pointer-events: none; opacity: 0.85; }
.bs-skel-tday   { position: relative; height: 100%; }
.bs-skel-tblock {
    position: absolute;
    left: 4px;
    right: 4px;
    border-radius: 6px;
}
@keyframes bs-skel-shimmer {
    100% { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
    .bs-skel::after { animation: none; }
}

/* -------- CypherRing - the signature primitive (see Components/CypherRing.razor) --------
   Sizing/stroke are computed in the component and inlined via CSS custom props on the
   wrapper. This block styles the wrapper (centring, aspect, count typography, full/empty
   states). Fill animation is defined inline via keyframes below. */
.bs-ring {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--bs-ring-size);
    height: var(--bs-ring-size);
    flex-shrink: 0;
}
.bs-ring svg {
    position: absolute;
    inset: 0;
}
.bs-ring-count {
    position: relative;
    font-family: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    line-height: 1;
    letter-spacing: 0.02em;
    /* Font size scales with the ring - clamp so tiny rings stay legible. */
    font-size: clamp(0.65rem, calc(var(--bs-ring-size) * 0.24), 1.2rem);
}
.bs-ring-count .sep {
    color: var(--text-muted);
    margin: 0 1px;
    font-weight: 400;
}
.bs-ring.is-full .bs-ring-count { color: var(--text-muted); }
.bs-ring.is-full svg circle:last-child { stroke: var(--text-muted) !important; }
.bs-ring.is-empty .bs-ring-count { color: var(--text-muted); }

/* One orchestrated moment: the ring's fill animation on mount. Respects
   prefers-reduced-motion - the CSS animation is discarded and the final
   stroke-dasharray value already renders the arc at its target length. */
@keyframes bs-ring-fill {
    from { stroke-dasharray: 0 var(--bs-ring-progress); }
}
@media (prefers-reduced-motion: reduce) {
    @keyframes bs-ring-fill {
        from { stroke-dasharray: var(--bs-ring-progress); }
    }
}

/* Selection uses the brand accent, subtly. */
::selection {
    background: color-mix(in srgb, var(--accent) 30%, transparent);
    color: var(--accent-ink);
}

/* Focus ring for keyboard nav - MudBlazor's default is a bit muted on dark. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Respect iOS safe-area for the bottom nav that sits above the home indicator. */
.safe-bottom {
    padding-bottom: env(safe-area-inset-bottom);
}

/* Utility: gradient backdrop for hero-like sections. */
.bs-hero-bg {
    background: var(--gradient-hero);
}

/* Scrollbar polish - chromium/webkit. Firefox uses scrollbar-color below. */
* {
    scrollbar-color: #2A2A38 transparent;
}
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: #2A2A38;
    border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover { background: #3A3A4A; }

/* Loading splash - wolf mark centred inside a red cypher-style progress ring,
   percentage in tabular monospace below. Whole thing matches the asphalt
   theme so there's no white flash before Blazor boots, and follows the same
   design-system tokens (--surface-raised for the empty track, --accent for
   the filled arc) that the app's signature cypher ring uses (CLAUDE.md §5). */
#app > .loading-mark {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 8rem;
    height: 8rem;
    transform: translate(-50%, -50%);
    border-radius: var(--radius-card);
    opacity: 0;
    animation: bs-boot-mark-in 320ms cubic-bezier(0.2, 0, 0, 1) 120ms forwards;
}
@keyframes bs-boot-mark-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.94); }
    to   { opacity: 0.95; transform: translate(-50%, -50%) scale(1); }
}
#app > .loading-progress {
    position: fixed;
    display: block;
    width: 11.5rem;
    height: 11.5rem;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    opacity: 0;
    animation: bs-boot-ring-in 320ms cubic-bezier(0.2, 0, 0, 1) forwards;
}
@keyframes bs-boot-ring-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
#app > .loading-progress circle {
    fill: none;
    stroke: var(--surface-raised);
    stroke-width: 0.5rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}
#app > .loading-progress circle:last-child {
    stroke: var(--accent);
    stroke-linecap: round;
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray 0.15s cubic-bezier(0.2, 0, 0, 1);
}
.loading-progress-text {
    position: fixed;
    text-align: center;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    top: calc(50% + 7.25rem);
    left: 0;
    right: 0;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    opacity: 0;
    animation: bs-boot-text-in 240ms cubic-bezier(0.2, 0, 0, 1) 180ms forwards;
}
@keyframes bs-boot-text-in {
    from { opacity: 0; transform: translateY(3px); }
    to   { opacity: 1; transform: translateY(0); }
}
.loading-progress-text::after {
    content: var(--blazor-load-percentage-text, "loading…");
}

@media (prefers-reduced-motion: reduce) {
    #app > .loading-mark,
    #app > .loading-progress,
    .loading-progress-text {
        animation: none;
        opacity: 1;
    }
    #app > .loading-mark { opacity: 0.95; }
}

/* Boot backdrop — asphalt sheet that sits behind the loading UI and stays
   there past Blazor mount so nothing blank flashes during the handoff. When
   boot.js finishes the FLIP that carries the wolf mark into the app bar, it
   adds .bs-boot-fade-out and the backdrop dissolves in the same 450ms window
   so the app arrives at the moment the mark lands. */
#bs-boot-backdrop {
    position: fixed;
    inset: 0;
    z-index: 9998;
    background: #101312;
    pointer-events: none;
    opacity: 1;
    /* Backdrop holds opaque past halfway through the travel, then fades
       over the remainder — mark is solo on dark for most of its flight,
       app arrives just as it settles. See BACKDROP_FADE_AT in boot.js. */
    transition: opacity 450ms cubic-bezier(0.2, 0, 0, 1);
}
#bs-boot-backdrop.bs-boot-fade-out {
    opacity: 0;
}

/* Loader (inside #app) sits above the backdrop and above the flyer during
   the load phase, so the ring and text render on top of the flyer. When
   Blazor mounts and removes the loader, the flyer takes over with no seam
   (same wolf, same position, same size). */
#app > .loading-mark,
#app > .loading-progress,
.loading-progress-text {
    z-index: 9999;
}

/* Dedicated overlay flyer — the wolf mark that actually travels from
   centre-of-screen up into the header. Lives inside #bs-boot-backdrop so
   its stacking is entirely predictable. Starts placed exactly on top of
   #app > .loading-mark so there's no visible handoff when the loader is
   removed. boot.js drives its transform (travel) and opacity (final fade)
   via the Web Animations API. */
#bs-boot-backdrop .bs-boot-flyer {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 8rem;
    height: 8rem;
    transform: translate(-50%, -50%);
    transform-origin: 50% 50%;
    opacity: 1;
    will-change: transform, opacity;
    pointer-events: none;
    /* Loader inside #app is drawn on top at z:9999 during boot, so the
       flyer is only revealed once Blazor mounts and clears #app's loader. */
}

/* Reduced motion — boot.js already handles this in JS (no travel, just fade),
   but keep the visual guarantee here too in case JS is disabled or delayed. */
@media (prefers-reduced-motion: reduce) {
    #bs-boot-backdrop { transition: opacity 200ms linear; }
}

/* Staggered boot reveal — while <body> still has .bs-booting the app-bar's
   secondary lockup pieces and the right-hand controls are held at opacity 0.
   boot.js clears the class the frame after the wolf mark lands, and each
   piece cascades in on the transition below, giving the moment a distinct
   "app assembles around the mark" beat. Wolf mark itself is untouched — it's
   already visible from the FLIP. */
body.bs-booting .bs-app-bar .bs-brand-divider,
body.bs-booting .bs-app-bar .bs-brand-word,
body.bs-booting .bs-app-bar-right > * {
    opacity: 0;
    transform: translateY(-3px);
}
.bs-app-bar .bs-brand-divider,
.bs-app-bar .bs-brand-word,
.bs-app-bar-right > * {
    transition:
        opacity 340ms cubic-bezier(0.2, 0, 0, 1),
        transform 340ms cubic-bezier(0.2, 0, 0, 1);
}
.bs-app-bar .bs-brand-divider { transition-delay: 60ms; }
.bs-app-bar .bs-brand-word    { transition-delay: 140ms; }
.bs-app-bar-right > *:nth-child(1) { transition-delay: 200ms; }
.bs-app-bar-right > *:nth-child(2) { transition-delay: 260ms; }
.bs-app-bar-right > *:nth-child(3) { transition-delay: 320ms; }
.bs-app-bar-right > *:nth-child(4) { transition-delay: 380ms; }
.bs-app-bar-right > *:nth-child(5) { transition-delay: 440ms; }

@media (prefers-reduced-motion: reduce) {
    body.bs-booting .bs-app-bar .bs-brand-divider,
    body.bs-booting .bs-app-bar .bs-brand-word,
    body.bs-booting .bs-app-bar-right > * {
        opacity: 1;
        transform: none;
    }
}

#blazor-error-ui {
    background: var(--surface);
    color: var(--text);
    border-top: 2px solid var(--accent);
    box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.5);
    padding: 1rem 1.25rem;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    display: none;
}
#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: 0.75rem;
    color: var(--text-muted);
}

/* MudBlazor overrides - a few places where the defaults don't match the vibe. */

/* Paper/cards get a subtle border so surfaces don't blur into the background. */
.mud-paper {
    border: 1px solid var(--surface-raised);
}

/* Primary button hover uses a small vertical shift + faint surface lift, no
   accent glow. CLAUDE.md §4 allows exactly one shadow - the bottom sheet -
   and §1 forbids glow effects. The transform gives the same "responds to me"
   feedback without the SaaS bling. */
.mud-button-filled.mud-button-filled-primary {
    transition: transform 0.15s ease, filter 0.15s ease;
}
.mud-button-filled.mud-button-filled-primary:hover:not(:disabled) {
    transform: translateY(-1px);
    filter: brightness(1.08);
}

/* Nav-links in the drawer: highlight the active one with a left accent bar. */
.mud-nav-link.active {
    border-left: 3px solid var(--accent);
    background: rgba(245, 36, 40, 0.06);
}

/* Chips: soften them slightly so they don't fight for attention. */
.mud-chip {
    letter-spacing: 0.01em;
}

/* -------- Alerts + Snackbars --------
   MudBlazor's default filled alerts render with fully saturated theme
   palette colors that clash with studio-cool surfaces (loud red / orange
   panels with white text). Retune to a soft tinted surface with a colored
   icon and a 3px left accent bar — same visual language as the drawer's
   active nav link. Border-based elevation per CLAUDE.md §4 (no shadow on
   in-page alerts); a single soft lift is allowed on the snackbar because
   it floats over the page. Colors are pulled through color-mix so the same
   rules track --danger/--warning/--success/--accent in both themes. */
.mud-alert {
    border-radius: var(--radius-input);
    border: 1px solid var(--surface-raised);
    box-shadow: none !important;
    color: var(--text);
    align-items: flex-start;
    padding: 12px 14px;
    line-height: 1.45;
}
.mud-alert .mud-alert-message {
    color: var(--text);
    font-weight: 500;
}

.mud-alert.mud-alert-filled-error,
.mud-alert.mud-alert-outlined-error {
    background: color-mix(in srgb, var(--danger) 12%, var(--surface)) !important;
    border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent) !important;
    border-left: 3px solid var(--danger) !important;
    color: var(--text) !important;
}
.mud-alert.mud-alert-filled-warning,
.mud-alert.mud-alert-outlined-warning {
    background: color-mix(in srgb, var(--warning) 14%, var(--surface)) !important;
    border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent) !important;
    border-left: 3px solid var(--warning) !important;
    color: var(--text) !important;
}
.mud-alert.mud-alert-filled-info,
.mud-alert.mud-alert-outlined-info {
    background: color-mix(in srgb, var(--accent) 10%, var(--surface)) !important;
    border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent) !important;
    border-left: 3px solid var(--accent) !important;
    color: var(--text) !important;
}
.mud-alert.mud-alert-filled-success,
.mud-alert.mud-alert-outlined-success {
    background: color-mix(in srgb, var(--success) 14%, var(--surface)) !important;
    border: 1px solid color-mix(in srgb, var(--success) 40%, transparent) !important;
    border-left: 3px solid var(--success) !important;
    color: var(--text) !important;
}

/* Icon color — MudBlazor by default paints the icon the same color as the
   text (white on the filled bg). Restore to the severity accent so the
   icon reads as the eyebrow of the alert. Cover both the SVG path and any
   MudIcon child so we hit MudBlazor's own icon component too. */
.mud-alert.mud-alert-filled-error   .mud-alert-icon,
.mud-alert.mud-alert-outlined-error .mud-alert-icon,
.mud-alert.mud-alert-filled-error   .mud-alert-icon svg,
.mud-alert.mud-alert-outlined-error .mud-alert-icon svg,
.mud-alert.mud-alert-filled-error   .mud-icon-root,
.mud-alert.mud-alert-outlined-error .mud-icon-root { color: var(--danger) !important; fill: var(--danger) !important; }

.mud-alert.mud-alert-filled-warning   .mud-alert-icon,
.mud-alert.mud-alert-outlined-warning .mud-alert-icon,
.mud-alert.mud-alert-filled-warning   .mud-alert-icon svg,
.mud-alert.mud-alert-outlined-warning .mud-alert-icon svg,
.mud-alert.mud-alert-filled-warning   .mud-icon-root,
.mud-alert.mud-alert-outlined-warning .mud-icon-root { color: var(--warning) !important; fill: var(--warning) !important; }

.mud-alert.mud-alert-filled-info   .mud-alert-icon,
.mud-alert.mud-alert-outlined-info .mud-alert-icon,
.mud-alert.mud-alert-filled-info   .mud-alert-icon svg,
.mud-alert.mud-alert-outlined-info .mud-alert-icon svg,
.mud-alert.mud-alert-filled-info   .mud-icon-root,
.mud-alert.mud-alert-outlined-info .mud-icon-root { color: var(--accent) !important; fill: var(--accent) !important; }

.mud-alert.mud-alert-filled-success   .mud-alert-icon,
.mud-alert.mud-alert-outlined-success .mud-alert-icon,
.mud-alert.mud-alert-filled-success   .mud-alert-icon svg,
.mud-alert.mud-alert-outlined-success .mud-alert-icon svg,
.mud-alert.mud-alert-filled-success   .mud-icon-root,
.mud-alert.mud-alert-outlined-success .mud-icon-root { color: var(--success) !important; fill: var(--success) !important; }

/* Snackbar — matches the alert treatment so a toast and an in-page alert
   for the same severity read as one system. Elevation via one soft shadow
   (toasts are the second explicitly-allowed shadow: they float above the
   page and need clear separation from whatever they cover). */
.mud-snackbar {
    border-radius: var(--radius-card);
    background: var(--surface) !important;
    color: var(--text) !important;
    border: 1px solid var(--surface-raised);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
    padding: 14px 16px;
    font-family: 'Commissioner', system-ui, -apple-system, sans-serif;
    font-weight: 500;
    line-height: 1.4;
}
.mud-snackbar.mud-alert-filled-error,
.mud-snackbar.mud-alert-outlined-error,
.mud-snackbar-filled-error,
.mud-snackbar-outlined-error {
    background: color-mix(in srgb, var(--danger) 10%, var(--surface)) !important;
    border-left: 3px solid var(--danger) !important;
}
.mud-snackbar.mud-alert-filled-warning,
.mud-snackbar.mud-alert-outlined-warning,
.mud-snackbar-filled-warning,
.mud-snackbar-outlined-warning {
    background: color-mix(in srgb, var(--warning) 12%, var(--surface)) !important;
    border-left: 3px solid var(--warning) !important;
}
.mud-snackbar.mud-alert-filled-info,
.mud-snackbar.mud-alert-outlined-info,
.mud-snackbar-filled-info,
.mud-snackbar-outlined-info {
    background: color-mix(in srgb, var(--accent) 8%, var(--surface)) !important;
    border-left: 3px solid var(--accent) !important;
}
.mud-snackbar.mud-alert-filled-success,
.mud-snackbar.mud-alert-outlined-success,
.mud-snackbar-filled-success,
.mud-snackbar-outlined-success {
    background: color-mix(in srgb, var(--success) 12%, var(--surface)) !important;
    border-left: 3px solid var(--success) !important;
}

/* -------- Mobile bottom nav -------- */
.bs-bottom-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1200;                        /* above MudMainContent, below dialogs */
    /* §1 no glassmorphism — was blur(12px) over a 92%-opaque plate. Now
       fully opaque; the border-top does the elevation lift. */
    background: var(--surface);
    border-top: 1px solid var(--surface-raised);
    /* Horizontal padding respects safe-area insets so icons don't sit
       under rounded corners on notched devices (iPhone 14+ Pro, 16 Pro). */
    padding-top: 6px;
    padding-inline-start: max(8px, env(safe-area-inset-left));
    padding-inline-end: max(8px, env(safe-area-inset-right));
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
    display: none;                         /* only shown at mobile breakpoint below */
}
@media (max-width: 899.98px) {
    .bs-bottom-nav { display: flex; }
    /* Reserve room so content doesn't hide behind the nav. */
    .bs-page-pad-bottom { padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
    /* On mobile we hide the drawer entirely for member routes; admin still uses hamburger. */
    .bs-hide-mobile { display: none !important; }
}
@media (min-width: 900px) {
    .bs-show-mobile-only { display: none !important; }
}

.bs-bottom-nav-inner {
    display: grid;
    grid-template-columns: repeat(4, 1fr);      /* 4 member tabs per spec §1 */
    width: 100%;
    max-width: 560px;
    margin: 0 auto;
}
.bs-bottom-nav a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 8px 4px;
    text-decoration: none;
    color: var(--text-muted);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    line-height: 1.1;
    border-radius: 12px;
    transition: color 0.15s ease, background 0.15s ease;
}
.bs-bottom-nav a:hover { color: var(--text); }
.bs-bottom-nav-relative { position: relative; }
.bs-bottom-nav-badge {
    position: absolute;
    top: 4px;
    right: calc(50% - 20px);
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.65rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.bs-bottom-nav-dot {
    position: absolute;
    top: 6px;
    right: calc(50% - 16px);
    width: 8px;
    height: 8px;
    border-radius: 999px;
    background: var(--accent);
}
.bs-bottom-nav a.active {
    background: rgba(245, 36, 40, 0.08);
    /* Label stays --text so it passes WCAG AA on small text (4.5:1). The active
       state is signalled by the icon color + tinted background - that gives us
       redundant cues without relying on an accent-on-bg contrast that only
       barely passes at 4.17:1 in dark. */
    color: var(--text);
}
.bs-bottom-nav a.active .mud-icon-root { color: var(--accent); }
.bs-bottom-nav .mud-icon-root {
    font-size: 22px;
}

/* MudBlazor's default app-bar override. §1 forbids glassmorphism, so the
   previous blur + 72% plate is now a solid surface; the border-bottom lifts
   the plane by itself. */
.mud-appbar {
    background-color: var(--surface);
    border-bottom: 1px solid var(--surface-raised);
}
.mud-appbar.mud-elevation-1,
.mud-appbar.mud-elevation-2 {
    box-shadow: none;                     /* the border does the visual lift */
}

/* -------- Landing hero -------- */
.bs-hero {
    position: relative;
    border-radius: var(--radius-card);
    background: var(--gradient-hero);
    padding: clamp(2.75rem, 8vh, 5.5rem) clamp(1.5rem, 5vw, 3rem);
    overflow: hidden;
    margin-bottom: 3rem;
    border: 1px solid var(--surface-raised);
}
.bs-hero::before {
    /* Subtle vignette to sink the corners without dulling the middle. */
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.35) 100%);
    pointer-events: none;
}
.bs-hero-content {
    position: relative;
    max-width: 780px;
    z-index: 1;
}
/* Style whatever <h1>/<h2>/<p> the admin puts in the hero HTML. */
.bs-hero-content :is(h1, h2) {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(2.5rem, 7vw, 4.5rem);
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1;
    margin: 0 0 1.25rem;
    /* Chalk-on-asphalt, per CLAUDE.md §2 - one accent-filled element per screen
       and no SaaS gradients on the public entry point. The previous pink text
       gradient was in the §1 forbidden list. */
    color: var(--text);
}
.bs-hero-content p {
    font-size: clamp(1rem, 1.6vw, 1.25rem);
    color: var(--text-muted);
    max-width: 60ch;
    margin: 0 0 2rem;
    line-height: 1.5;
}
.bs-hero-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.9rem 1.75rem;
    background: #ffffff;
    color: #101312;
    font-weight: 700;
    letter-spacing: 0.01em;
    border-radius: 999px;
    text-decoration: none;
    transition: transform 0.15s ease, filter 0.15s ease;
}
.bs-hero-cta:hover {
    transform: translateY(-2px);
    filter: brightness(1.08);
}
.bs-hero-cta-secondary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.9rem 1.5rem;
    color: var(--accent-ink);
    text-decoration: none;
    font-weight: 600;
    border-radius: 999px;
    margin-left: 0.75rem;
    transition: background 0.15s ease;
}
.bs-hero-cta-secondary:hover {
    background: rgba(255, 255, 255, 0.08);
}

/* -------- Section heading rhythm -------- */
.bs-section-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(1.5rem, 3vw, 2rem);
    letter-spacing: -0.015em;
    margin: 3rem 0 1.25rem;
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
}
.bs-section-title-accent {
    color: var(--accent);
}
.bs-eyebrow {
    display: inline-block;
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 0.75rem;
}

/* -------- Session cards (landing preview + /schedule) -------- */
.bs-session-day {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: 1rem 0 0.5rem;
}
.bs-session-day-sticky {
    position: sticky;
    top: 64px;                            /* under the app-bar */
    background: linear-gradient(180deg, var(--bg) 65%, transparent);
    z-index: 5;
    padding-top: 0.75rem;
}
.bs-session-card {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    transition: border-color 0.15s ease, transform 0.15s ease;
    margin-bottom: 0.75rem;
}
.bs-session-card:hover {
    border-color: rgba(245, 36, 40, 0.35);
    transform: translateY(-1px);
}
.bs-session-time {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
    line-height: 1;
    min-width: 3.5rem;
}
.bs-session-duration {
    color: var(--text-muted);
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    margin-top: 4px;
    font-weight: 500;
}
.bs-session-name {
    font-weight: 600;
    font-size: 1.05rem;
    line-height: 1.3;
    color: var(--text);
}
.bs-session-meta {
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.4;
    margin-top: 2px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.bs-session-meta .dot { color: var(--surface-raised); }
.bs-session-card.is-cancelled {
    opacity: 0.55;
    border-color: rgba(239, 68, 68, 0.3);
}
.bs-session-card.is-cancelled .bs-session-name {
    text-decoration: line-through;
}

/* Capacity indicator - small pill/bar showing X/Y booked. */
.bs-cap {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(160, 160, 184, 0.10);
    font-size: 0.75rem;
    color: var(--text-muted);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.bs-cap-bar {
    width: 42px;
    height: 4px;
    background: rgba(160, 160, 184, 0.20);
    border-radius: 2px;
    overflow: hidden;
}
.bs-cap-bar span {
    display: block;
    height: 100%;
    background: var(--success);
    transition: width 0.35s ease;
}
/* Was raw amber hex; now token-driven so light-theme swap re-evaluates cleanly. */
.bs-cap.is-full { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.bs-cap.is-full .bs-cap-bar span { background: var(--warning); }

/* -------- Instructor showcase -------- */
.bs-instructor-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 1rem;
}
.bs-instructor-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 1.5rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    transition: transform 0.15s ease, border-color 0.15s ease;
}
.bs-instructor-card:hover {
    transform: translateY(-3px);
    border-color: var(--surface-raised);
}
.bs-instructor-avatar {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    /* Solid surface-raised - the purple `#8B7BB8` in the previous gradient was
       off-palette (not a system token) and the gradient itself was §1's
       forbidden SaaS look. Initials render in text token, not raw white. */
    background: var(--surface-raised);
    color: var(--text);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.5rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    letter-spacing: -0.02em;
    margin-bottom: 0.75rem;
}
.bs-instructor-name {
    font-weight: 600;
    color: var(--text);
    line-height: 1.3;
}

/* -------- Schedule page header -------- */
.bs-schedule-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem 1.5rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}
.bs-schedule-range {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    font-weight: 700;
    letter-spacing: -0.015em;
    margin: 0.25rem 0 0;
    line-height: 1;
}
/* Discovery chip under the schedule header sub. Sits below .bs-page-hero-sub
   so the eyebrow / title / sub triangle stays intact. Ghost styling; the
   week nav's "Today" button owns the accent for this screen (CLAUDE.md §2). */
.bs-schedule-lb-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--surface-raised);
    background: var(--surface);
    color: var(--text);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.9375rem;
    font-weight: 600;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease);
}
.bs-schedule-lb-chip:hover,
.bs-schedule-lb-chip:focus-visible {
    border-color: color-mix(in srgb, var(--accent) 40%, var(--surface-raised));
    background: var(--surface-raised);
}
.bs-schedule-lb-chip .mud-icon-root { font-size: 18px; color: var(--text-muted); }
.bs-week-nav {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;                      /* let density toggle wrap under nav buttons when tight */
    justify-content: flex-end;
}
.bs-week-nav-divider {
    display: inline-block;
    width: 1px;
    height: 22px;
    background: var(--surface-raised);
    margin: 0 0.35rem;
}
.bs-session-action {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-width: 100px;
}
.bs-my-state {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

/* Cards collapse to a stacked layout on very narrow screens so nothing overflows. */
@media (max-width: 479.98px) {
    .bs-session-card {
        grid-template-columns: auto 1fr;
    }
    .bs-session-action {
        grid-column: 1 / -1;
        justify-content: flex-start;
        border-top: 1px dashed var(--surface-raised);
        padding-top: 0.75rem;
        margin-top: 0.25rem;
    }
}

/* -------- Live calendar (7-day grid on desktop, day list on mobile) -------- */
.bs-cal-live {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 3px 10px 3px 8px;
    border-radius: 999px;
    background: rgba(53, 192, 138, 0.10);
    color: var(--success);
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    font-weight: 700;
    text-transform: uppercase;
}
.bs-cal-live .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--success);
    box-shadow: 0 0 0 0 rgba(53, 192, 138, 0.6);
    animation: bs-cal-pulse 1.8s ease-out infinite;
}
@keyframes bs-cal-pulse {
    0%   { box-shadow: 0 0 0 0    rgba(53, 192, 138, 0.55); }
    70%  { box-shadow: 0 0 0 8px  rgba(53, 192, 138, 0);    }
    100% { box-shadow: 0 0 0 0    rgba(53, 192, 138, 0);    }
}
@media (prefers-reduced-motion: reduce) {
    .bs-cal-live .dot { animation: none; }
}
/* -------- Time grid (Teams-style: fixed hour scale, absolute-positioned blocks) --------
   Layout: 8-col CSS grid (hour rail + 7 equal day columns), two rows (day-head strip +
   body). Day columns are position:relative and blocks are position:absolute using
   inline top/height/left/width derived in CalendarLayout.LayOutDay. The density
   modifier class (`.is-compact`/`.is-normal`/`.is-large`) sets --bs-cal-hour-px;
   Schedule.razor's HourPx constant MUST match the density it renders with, or block
   positions will drift. */

/* Full-bleed wrapper - used to escape MudContainer via a 100vw margin trick, but
   that overflowed when a scrollbar was present (100vw != 100% + scrollbar). Now we
   let the ancestor MudContainer expand itself when this page is on-screen (see the
   :has() rule below). This wrapper stays as a passive width:100% container so
   markup structure is unchanged. */
.bs-cal-fullbleed {
    width: 100%;
    box-sizing: border-box;
}

/* Unified content column (m101): two-lane rule, matching how modern app+marketing
   hybrids (Vercel, Stripe, Linear) split their canvas.
   - App-shell pages (member dashboards, admin pages, list views) cap at 1440px
     so tables/rosters/kanbans breathe on 27"+ monitors without going edge-to-edge.
   - Marketing/reader pages (landing, news article, session/event detail, public
     profile) opt into a 1200px cap via `.bs-page-narrow` so text lines stay in
     the readable 60-80ch range.
   - Schedule calendar body is the sole full-viewport exception (needs viewport
     width for 7-day density); Landing hero photograph goes edge-to-edge too but
     its inner text/CTA anchors to the 1200 column via the rules further down.
   - Gutter fluid 16→32px so wide desktops get breathing room without losing
     mobile compactness. */
.mud-container.mud-container-maxwidth-lg {
    max-width: min(1440px, 100%);
    padding-left: clamp(16px, 3vw, 32px);
    padding-right: clamp(16px, 3vw, 32px);
}
.mud-container.mud-container-maxwidth-lg:has(.bs-page-narrow) {
    max-width: min(1200px, 100%);
}

/* When the schedule page is rendered, expand the surrounding MudContainer to fill
   its parent so the calendar can use the whole content area. Scoped by :has() so
   no other page is affected - .mud-container-maxwidth-lg keeps its normal cap
   everywhere else. Padding trimmed to breathe against the app edges without
   fighting the viewport. */
.mud-container.mud-container-maxwidth-lg:has(.bs-cal-fullbleed) {
    max-width: 100%;
    padding-left: 1rem;
    padding-right: 1rem;
}
/* Landing v2 (m91): same `:has()` trick - expand the surrounding MudContainer
   to viewport width AND kill its default top margin so the hero can pull up
   under the sticky transparent header. Sections beneath the hero take the
   viewport's full width and manage their own fluid gutter (m94). */
.mud-container.mud-container-maxwidth-lg:has(.bs-landing-v2) {
    max-width: 100%;
    padding: 0;
    margin-top: 0 !important;
}
/* MudMainContent bakes in `padding-top: var(--mud-appbar-height)` (64px) to
   reserve space for a MudAppBar we don't render - we ship our own <header>.
   On non-landing pages that padding is invisible (surface matches), but on
   the landing the transparent-over-hero header lets the body asphalt bg
   show through that 64px strip, reading as a grey band above the hero.
   Zero it out on landing so the hero can truly full-bleed to viewport top. */
.mud-main-content:has(.bs-landing-v2) {
    padding-top: 0;
}
.bs-cal-density-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 2px 3px;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: 999px;
}
.bs-cal-density-btn {
    background: transparent;
    color: var(--text-muted);
    border: none;
    padding: 4px 12px;
    border-radius: 999px;
    font: inherit;
    font-size: 0.72rem;
    letter-spacing: 0.03em;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}
.bs-cal-density-btn:hover { color: var(--text); }
.bs-cal-density-btn.is-active {
    background: var(--accent);
    color: #ffffff;
    font-weight: 600;
}

.bs-cal-timegrid {
    --bs-cal-hour-px: 72px;
    --bs-cal-head-h: 48px;
    --bs-cal-hours: 15;
    --bs-cal-title-fs: 0.9rem;
    --bs-cal-sub-fs: 0.75rem;
    --bs-cal-time-fs: 0.72rem;
    display: grid;
    grid-template-columns: 64px repeat(7, minmax(0, 1fr));
    grid-template-rows: var(--bs-cal-head-h) auto;
    margin-top: 1rem;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    overflow: hidden;
    background: rgba(25, 29, 28, 0.35);
}
.bs-cal-timegrid.is-compact {
    --bs-cal-hour-px: 44px;
    --bs-cal-title-fs: 0.75rem;
    --bs-cal-sub-fs: 0.65rem;
    --bs-cal-time-fs: 0.62rem;
    grid-template-columns: 52px repeat(7, minmax(0, 1fr));
}
.bs-cal-timegrid.is-standard {
    --bs-cal-hour-px: 72px;
    --bs-cal-title-fs: 0.9rem;
    --bs-cal-sub-fs: 0.75rem;
    --bs-cal-time-fs: 0.72rem;
    grid-template-columns: 64px repeat(7, minmax(0, 1fr));
}
.bs-cal-timegrid.is-roomy {
    --bs-cal-hour-px: 104px;
    --bs-cal-title-fs: 1.05rem;
    --bs-cal-sub-fs: 0.85rem;
    --bs-cal-time-fs: 0.8rem;
    grid-template-columns: 72px repeat(7, minmax(0, 1fr));
}
.bs-cal-corner {
    background: var(--bg);
    border-bottom: 1px solid var(--surface-raised);
    position: sticky;
    top: 64px;
    z-index: 5;
}
.bs-cal-tday-head {
    background: var(--bg);
    border-bottom: 1px solid var(--surface-raised);
    border-left: 1px solid var(--surface-raised);
    padding: 6px 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    position: sticky;
    top: 64px;
    z-index: 5;
}
.bs-cal-tday-head.is-today {
    box-shadow: inset 0 -2px 0 var(--accent);
}
.bs-cal-tday-day {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 600;
}
.bs-cal-tday-date {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--text);
}
.bs-cal-tday-head.is-today .bs-cal-tday-date { color: var(--accent); }
.bs-cal-hour-rail {
    display: flex;
    flex-direction: column;
    background: rgba(10, 10, 15, 0.25);
}
.bs-cal-hour {
    height: var(--bs-cal-hour-px);
    font-size: var(--bs-cal-time-fs);
    color: var(--text-muted);
    padding: 2px 8px 0 0;
    border-top: 1px dashed var(--surface-raised);
    text-align: right;
    font-family: 'Manrope', system-ui, sans-serif;
    letter-spacing: 0.03em;
    font-weight: 600;
}
.bs-cal-hour:first-child { border-top: none; }
.bs-cal-tday {
    position: relative;
    height: calc(var(--bs-cal-hours) * var(--bs-cal-hour-px));
    border-left: 1px solid var(--surface-raised);
    /* Hour-line background - one line every HourPx from the top. */
    background-image: linear-gradient(
        to bottom,
        var(--surface-raised) 1px,
        transparent 1px
    );
    background-size: 100% var(--bs-cal-hour-px);
}
.bs-cal-tday.is-today { background-color: rgba(245, 36, 40, 0.04); }
/* Time-grid block (m102 redesign) - every kind (Session / Event / Closed) uses
   the same internal grid so alignment reads consistent across the week even when
   parallel-session columns squeeze a card to a fraction of its neighbour's width.
   Distinct kinds keep their identity through border-left colour + a whisper-tint
   background, not through layout changes. */
.bs-cal-tblock {
    position: absolute;
    border-radius: 8px;
    padding: 6px 8px;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 4px solid var(--success);
    cursor: pointer;
    /* §4: elevation from borders, not shadows. `border-color` + hover lift
       does the perceived-depth work the previous drop-shadow was doing. */
    transition: transform 0.12s ease,
                border-color 0.12s ease;
    text-align: left;
    color: var(--text);
    font: inherit;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 3px;
}
.bs-cal-tblock:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
    z-index: 3;
}
/* Kind variants - border colour + a 6% surface tint so cards feel unified but
   still readable at a glance. Session (Regular) keeps the plain --surface bg. */
.bs-cal-tblock.is-event {
    border-left-color: var(--text-muted);
    background: color-mix(in srgb, var(--text-muted) 6%, var(--surface));
}
.bs-cal-tblock.is-closed {
    border-left-color: var(--surface-raised);
    background: color-mix(in srgb, var(--text-muted) 8%, transparent);
}
.bs-cal-tblock.is-practice {
    border-left-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 5%, var(--surface));
}
.bs-cal-tblock.is-cancelled { opacity: 0.5; }
.bs-cal-tblock.is-cancelled .bs-cal-tblock-title { text-decoration: line-through; }
.bs-cal-tblock.is-mine {
    /* §4: elevation via borders. The previous inset-shadow ring + drop
       shadow moves to a solid coloured border, keeping the "mine" cue
       distinct from the standard hover/border-left highlight. */
    border-color: color-mix(in srgb, var(--accent) 30%, transparent);
    border-left-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}
/* Past sessions in the desktop time-grid (m97). Same treatment as the mobile
   agenda ClassCard's .is-past so members can tell at a glance which classes
   are still bookable. Not disabled - tap still opens the detail sheet, which
   now explains "this session already happened". */
.bs-cal-tblock.is-past { opacity: 0.55; }

/* Topline: time on the left, kind chip on the right. Locks the top edge of
   every card to the same visual line regardless of kind or width - this is
   the change that fixes the "events look centered vs classes look cramped"
   misread. */
.bs-cal-tblock-topline {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    min-width: 0;
}
.bs-cal-tblock-time {
    /* JetBrains Mono per design spec §3 - times / counts / timers all render
       with tabular figures so "19:00" and "0/32" align across cards. */
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums;
    font-size: var(--bs-cal-time-fs);
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
}
.bs-cal-tblock-title {
    font-weight: 600;
    font-size: var(--bs-cal-title-fs);
    line-height: 1.2;
    color: var(--text);
    overflow-wrap: anywhere;
    /* Clamp long class names to keep the block layout stable at short heights. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.bs-cal-tblock-sub {
    font-size: var(--bs-cal-sub-fs);
    color: var(--text-muted);
    line-height: 1.25;
    /* Same tabular-nums as time so instructor · 0/32 style subs stay aligned
       across the column when the counts change. */
    font-variant-numeric: tabular-nums;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.bs-cal-tblock-series {
    color: var(--success);
    font-size: 0.85rem;
    line-height: 1;
    opacity: 0.85;
}

/* Kind chip - micro-badge, top-right of the topline. Uppercase Manrope so it
   reads as a label, not content. Colour by kind: Seminar = accent tint,
   Battle = warning tint, Event = muted. Regular sessions render no chip so
   the common case stays visually quiet. */
.bs-cal-tblock-chip {
    flex-shrink: 0;
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.58rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1;
    padding: 3px 6px;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--text-muted) 18%, transparent);
    color: var(--text);
    white-space: nowrap;
    max-width: 55%;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bs-cal-tblock-chip.is-seminar {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    color: var(--accent);
}
.bs-cal-tblock-chip.is-battle {
    background: color-mix(in srgb, var(--warning) 18%, transparent);
    color: var(--warning);
}
.bs-cal-tblock-chip.is-event {
    background: color-mix(in srgb, var(--text-muted) 18%, transparent);
    color: var(--text-muted);
}

/* Legacy column-agenda desktop grid (kept but unused; mobile agenda still lives below). */
.bs-cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 0.75rem;
    margin-top: 1rem;
}
.bs-cal-col {
    background: rgba(25, 29, 28, 0.35);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 0.5rem;
    min-height: 240px;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
/* Today marker: a 2px accent border (border-based elevation per §4) instead
   of a glow ring. Still reads as "this column is today". */
.bs-cal-col.is-today {
    border-color: var(--accent);
    border-width: 2px;
}
.bs-cal-col-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 0.35rem 0.5rem 0.5rem;
    border-bottom: 1px dashed var(--surface-raised);
    margin-bottom: 0.35rem;
}
.bs-cal-col-day {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 600;
}
.bs-cal-col-date {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
}
.bs-cal-col.is-today .bs-cal-col-date { color: var(--accent); }

.bs-cal-entry {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.55rem 0.65rem;
    border-radius: var(--radius-input);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--success);
    cursor: pointer;
    transition: transform 0.12s ease, border-color 0.12s ease;
    text-align: left;
    color: var(--text);
    font: inherit;
    width: 100%;
}
.bs-cal-entry:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
}
/* Events / closed / mine use system tokens; the previous raw hex greens and
   purples were off-palette and never re-evaluated for light theme. */
.bs-cal-entry.is-event { border-left-color: var(--text-muted); }
.bs-cal-entry.is-closed {
    border-left-color: var(--surface-raised);
    background: color-mix(in srgb, var(--text-muted) 6%, transparent);
    opacity: 0.85;
}
.bs-cal-entry.is-cancelled { opacity: 0.5; }
.bs-cal-entry.is-cancelled .bs-cal-entry-title { text-decoration: line-through; }
.bs-cal-entry.is-mine { border-left-color: var(--success); box-shadow: 0 0 0 1px color-mix(in srgb, var(--success) 15%, transparent); }
.bs-cal-entry-time {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.bs-cal-entry-series {
    color: var(--success);
    font-size: 0.85rem;
    line-height: 1;
    opacity: 0.85;
}
.bs-cal-entry-title {
    font-weight: 600;
    font-size: 0.9rem;
    line-height: 1.25;
}
.bs-cal-entry-sub {
    font-size: 0.72rem;
    color: var(--text-muted);
    line-height: 1.3;
}
.bs-cal-empty-col {
    color: var(--text-muted);
    font-size: 0.75rem;
    text-align: center;
    padding: 1.5rem 0.25rem;
}
.bs-cal-agenda {
    display: none;
}

@media (max-width: 899.98px) {
    .bs-cal-grid { display: none; }
    .bs-cal-timegrid { display: none; }
    .bs-cal-agenda { display: block; margin-top: 1rem; }
    /* Density only affects the time grid - pointless in the mobile agenda view. */
    .bs-cal-density-toggle,
    .bs-week-nav-divider { display: none; }
    /* Give the schedule page normal container padding on mobile - no fullbleed expansion. */
    .mud-container.mud-container-maxwidth-lg:has(.bs-cal-fullbleed) {
        padding-left: 12px;
        padding-right: 12px;
    }
}

/* -------- Admin drawer (see MainLayout.razor) --------
   Custom nav inside a MudDrawer - we drop MudNavMenu because its defaults don't
   let us group with quiet section eyebrows or nest sub-items cleanly. Uses plain
   <a> so the active state is fully in our hands (compared against location).

   The drawer runs ClipMode="Never" so it stretches to the viewport top and owns
   the top-left corner. Its brand block sits in the same visual slot as the
   header brand, which is hidden on desktop when the drawer is present (see
   :has() rule below) to avoid duplicating identity. */
.bs-admin-drawer .mud-drawer {
    background: var(--bg);
    /* Force top: 0 so the drawer is flush with the viewport top instead of
       inheriting MudBlazor's default AppBar offset (64px), which used to
       leave the drawer starting halfway down our 96–144px header. */
    top: 0 !important;
    height: 100vh !important;
    border-right: 1px solid var(--surface-raised);
    z-index: 70; /* above sticky header (z-index 60) */
    transition: width 0.24s var(--ease);
    overflow: hidden;
}
/* Drawer inner uses CSS Grid (m101 revised): grid-template-rows pins the
   footer with a fixed track so it can NEVER be pushed off-screen, even if
   MudBlazor's wrapper height chain (mud-drawer > mud-drawer-content >
   bs-admin-drawer-inner) doesn't cascade cleanly. Grid track sizes are
   evaluated against the grid container's own height (100vh), independent
   of child intrinsic size - which is why a flex-based version was fragile:
   flex needs min-height:0 to cascade through every ancestor, grid doesn't. */
.bs-admin-drawer-inner {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    height: 100%;
    padding: 0.75rem 0.6rem 1rem;
    gap: 0.35rem;
}
/* MudBlazor's drawer content wrapper needs an explicit height chain so our
   Grid container (bs-admin-drawer-inner) resolves to viewport height. */
.bs-admin-drawer .mud-drawer-content {
    height: 100%;
    display: block;
}
/* Brand row - identity <a> + collapse toggle as siblings inside the drawer.
   Sizes to its content (m101) instead of the old --bs-app-bar-h stretch,
   which left 20–40px of dead dark space above the logo because the logo
   was much smaller than the header height it was matching. */
.bs-admin-drawer-brand-row {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0 0.15rem;
    flex-shrink: 0;
}
.bs-admin-drawer-brand-row > .bs-admin-drawer-brand {
    flex: 1;
    min-width: 0;
}
.bs-admin-drawer-brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.55rem;
    text-decoration: none;
    color: var(--text);
    border-radius: var(--radius-input);
    transition: background 0.15s ease;
    flex-shrink: 0;
    box-sizing: border-box;
}
.bs-admin-drawer-brand:hover { background: var(--surface); }
.bs-admin-drawer-brand .bs-brand-mark {
    width: 44px;
    height: 44px;
    flex-shrink: 0;
}
.bs-admin-drawer-brand-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    overflow: hidden;
}
.bs-admin-drawer-brand-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--text);
    line-height: 1.1;
    font-size: 1rem;
    white-space: nowrap;
}
.bs-admin-drawer-brand-role {
    color: var(--text-muted);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 600;
    white-space: nowrap;
}

/* Mini state (drawer collapsed to icons) - labels hide, brand text hides,
   badges hide, group headings collapse, single-column layout. */
.bs-admin-drawer.is-mini .bs-admin-drawer-brand-row {
    flex-direction: column;
    gap: 0.4rem;
    padding: 0;
}
.bs-admin-drawer.is-mini .bs-admin-drawer-brand {
    justify-content: center;
    padding: 0.35rem;
}
.bs-admin-drawer.is-mini .bs-admin-drawer-brand-text { display: none; }
.bs-admin-drawer.is-mini .bs-admin-nav-link {
    grid-template-columns: 1fr;
    justify-items: center;
    padding: 0.6rem 0;
}
.bs-admin-drawer.is-mini .bs-admin-nav-link > span,
.bs-admin-drawer.is-mini .bs-admin-nav-link .bs-admin-nav-badge,
.bs-admin-drawer.is-mini .bs-admin-nav-group-head { display: none; }
.bs-admin-drawer.is-mini .bs-admin-nav-link.is-sub { padding-left: 0; }
/* Compact bullet on active items instead of the accent bar tab. */
.bs-admin-drawer.is-mini .bs-admin-nav-link.is-active::before { display: none; }

/* Header brand stays visible for admins too (m101). The prior rule hid it
   on desktop to avoid duplicating the wolf mark next to the drawer's own
   brand block, but on the landing (transparent header, no solid app-shell
   surface behind it) that stripped the header of any identity - admins
   opening the site saw a blank strip where the mark should be.
   To keep the drawer's brand from competing visually with the header's,
   the drawer's brand text is muted below (mark-only compact treatment). */
@media (min-width: 721px) {
    .mud-layout:has(.bs-admin-drawer) .bs-admin-drawer-brand-text {
        display: none;
    }
}

/* Legacy eyebrow (drawer used to render "ΔΙΑΧΕΙΡΙΣΗ ΣΧΟΛΗΣ" as a standalone
   row) - role text now lives inside the brand block, so the eyebrow just
   takes up space. Hide it wherever it might still render. */
.bs-admin-drawer .bs-admin-drawer-eyebrow { display: none; }

/* Small field-label + helper block for dialogs that host a non-MudTextField
   custom input (e.g. <MediaUpload>). Matches the look of MudBlazor's label
   + helper text so the media picker doesn't stand out awkwardly next to
   the rest of the dialog fields. */
.bs-field-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    margin-bottom: 6px;
}
.bs-field-help {
    font-size: 0.75rem;
    color: var(--text-muted);
    margin-bottom: 8px;
    line-height: 1.4;
}

/* -------- Media upload (image picker for admin dialogs) --------
   Rendered as a <label> wrapping a visually-hidden <InputFile> so the whole
   preview area is clickable (opens the file picker) with no JS interop.
   Non-empty Url = background-image on the preview; empty = dashed drop-zone
   with a hint. Uploads run through /api/media and the server-side ImageSharp
   resize keeps files predictably small before writing to the mounted volume. */
.bs-media-upload {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
}
.bs-media-upload-preview {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    width: 100%;
    background: var(--surface) center / cover no-repeat;
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-input);
    cursor: pointer;
    overflow: hidden;
    transition: border-color 0.15s ease, background-color 0.15s ease;
    color: var(--text-muted);
}
.bs-media-upload.is-compact {
    /* Positioning context for the overlaid actions bar. Compact = avatar
       column; the two icon actions sit ON the image bottom (Instagram
       pattern) instead of stacked below, so they can never overflow into
       sibling content and the whole widget reads as one atomic control. */
    position: relative;
    align-items: center;
}
.bs-media-upload.is-compact .bs-media-upload-preview {
    aspect-ratio: 1 / 1;
    max-width: 160px;
}
.bs-media-upload.is-compact .bs-media-upload-actions {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-width: 160px;
    margin: 0 auto;
    padding: 24px 4px 4px;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
    border-bottom-left-radius: var(--radius-input);
    border-bottom-right-radius: var(--radius-input);
    /* pointer-events off on the strip itself so the empty top of the
       gradient doesn't swallow taps meant for the image; buttons re-enable
       pointer-events so they stay interactive. */
    pointer-events: none;
}
.bs-media-upload.is-compact .bs-media-upload-actions > * {
    pointer-events: auto;
}
.bs-media-upload.is-compact .bs-media-upload-actions .mud-icon-button {
    color: #fff;
}
.bs-media-upload-preview:hover { border-color: var(--accent); color: var(--text); }
.bs-media-upload-preview.is-empty { background-color: color-mix(in srgb, var(--surface) 90%, var(--surface-raised)); }
/* Visually-hidden but focusable file input - the <label for=""> wrapping
   the preview drives the picker open on click. */
.bs-media-upload-input {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0);
    white-space: nowrap; border: 0;
}
.bs-media-upload-hint {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 16px;
    text-align: center;
    pointer-events: none;
}
.bs-media-upload-hint .mud-icon-root {
    color: var(--text-muted);
    font-size: 32px;
}
.bs-media-upload-sub {
    font-size: 0.75rem;
    color: var(--text-muted);
    max-width: 30ch;
}
.bs-media-upload-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: rgba(0, 0, 0, 0.55);
    color: var(--text);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.bs-media-upload-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    /* nowrap - the two Small MudIconButtons together are ~80px which fits
       under even the tightest (96px mobile) avatar column. Wrap here was
       what caused the earlier overflow into the display-name block. */
    flex-wrap: nowrap;
}
/* The Replace button is a <label for="…"> so clicking it opens the hidden
   InputFile - keep the pointer cursor to make that discoverable. */
.bs-media-upload-actions label { cursor: pointer; }

/* -------- Panel-toggle - collapses the admin drawer, lives INSIDE it --------
   Modern shell pattern (Cursor / Vercel / shadcn / Linear): the toggle is a
   quiet ghost button in the drawer's brand row, not a floating pill on the
   edge. Icon is a Lucide PanelLeftClose / PanelLeftOpen SVG so the arrow
   direction is a real swap, not a mirrored rotate. */
.bs-admin-drawer-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: transparent;
    color: var(--text-muted);
    border: 1px solid transparent;
    border-radius: 10px;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
    flex-shrink: 0;
}
.bs-admin-drawer-toggle:hover {
    background: var(--surface-raised);
    color: var(--text);
}
.bs-admin-drawer-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-admin-drawer-toggle > svg {
    display: block;
    width: 20px;
    height: 20px;
}

/* Mobile drawer / horizontal-scroll fix --------
   Drawer has bs-hide-mobile which hides it visually via display:none, but
   MudBlazor still sets --mud-drawer-width-left on .mud-layout to reserve
   the mini rail's 72px slot for MudMainContent's left margin. On mobile
   that produced (a) horizontal scroll after login and (b) an empty gap
   where the drawer would sit. Kill both by zeroing the offsets on mobile
   and enforcing display:none on the drawer + toggle. */
@media (max-width: 899.98px) {
    .mud-layout {
        --mud-drawer-width-left: 0px !important;
        --mud-drawer-width-mini-left: 0px !important;
        --mud-drawer-width-right: 0px !important;
        --mud-drawer-width-mini-right: 0px !important;
    }
    .mud-main-content { margin-left: 0 !important; padding-left: 0 !important; }
    .bs-admin-drawer { display: none !important; }
    /* ...but when admin opens the drawer overlay via the header hamburger,
       let it render (overrides the display:none above). Also hide the
       bottom-nav while the overlay is open - the drawer IS the primary
       nav at that moment, so bottom-nav is redundant AND covers the
       drawer's footer since bottom-nav sits at z-index 1200 vs drawer's 70. */
    .bs-admin-drawer.is-mobile-open { display: block !important; }
    body:has(.bs-admin-drawer.is-mobile-open) .bs-bottom-nav { display: none; }
    /* Horizontal-clip guard now lives in the global block above so it applies
       at ALL viewport sizes, not just mobile. Kept the comment stub here as
       a signpost for future maintainers. */
    /* MudLayout is display:flex on flex children default min-width:auto; the
       MudMainContent inside can therefore grow to fit min-content and drag
       the whole layout wider than viewport. Zeroing min-width here (the
       existing block already zeroes drawer offsets and paddings above) is
       what actually stops the reflow. */
    .mud-layout, .mud-main-content { min-width: 0; max-width: 100%; }
}

/* Non-admin gap fix - when there's no admin drawer in the DOM at all
   (member / non-admin views), MudMainContent should have no left offset.
   MudBlazor can leave the CSS custom prop dangling from a previous route. */
.mud-layout:not(:has(.bs-admin-drawer)) {
    --mud-drawer-width-left: 0px;
    --mud-drawer-width-mini-left: 0px;
}
.mud-layout:not(:has(.bs-admin-drawer)) .mud-main-content {
    margin-left: 0 !important;
    padding-left: 0 !important;
}

.bs-admin-nav {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    overflow-y: auto;
    /* Grid parent (.bs-admin-drawer-inner) sizes the middle row via
       `minmax(0, 1fr)` so this nav gets exactly the leftover height
       between brand-row and footer, then scrolls internally when nav
       content exceeds it. Footer track stays pinned to the bottom row. */
    min-height: 0;
    padding-top: 0.5rem;
    border-top: 1px solid var(--surface-raised);
    margin-top: 0.35rem;
}
.bs-admin-nav-group-head {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-muted);
    padding: 1rem 0.9rem 0.35rem;
    line-height: 1;
}
.bs-admin-nav-link {
    display: grid;
    grid-template-columns: 24px 1fr auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.85rem;
    border-radius: var(--radius-input);
    color: var(--text-muted);
    text-decoration: none;
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 500;
    font-size: 0.92rem;
    letter-spacing: -0.005em;
    transition: background 0.12s ease, color 0.12s ease;
    background: transparent;
    border: none;
    text-align: left;
    font: inherit;
    cursor: pointer;
    width: 100%;
}
.bs-admin-nav-link .mud-icon-root { color: currentColor; font-size: 20px; }
.bs-admin-nav-link:hover {
    background: var(--surface);
    color: var(--text);
}
.bs-admin-nav-link.is-active {
    background: rgba(245, 36, 40, 0.10);
    color: var(--accent);
    font-weight: 600;
}
.bs-admin-nav-link.is-active::before {
    content: "";
    position: absolute;
    left: -0.75rem;
    top: 8px;
    bottom: 8px;
    width: 3px;
    background: var(--accent);
    border-radius: 0 3px 3px 0;
}
.bs-admin-nav-link { position: relative; }
.bs-admin-nav-link.is-sub {
    padding-left: 2.5rem;
    font-size: 0.88rem;
    color: var(--text-muted);
}
.bs-admin-nav-link.is-sub .mud-icon-root { font-size: 18px; }
.bs-admin-nav-link.is-sub.is-active { background: rgba(245, 36, 40, 0.06); }

.bs-admin-nav-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.bs-admin-nav-footer {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--surface-raised);
    /* margin-top:auto removed - grid parent (.bs-admin-drawer-inner) now
       pins this element into its own `auto` track at the bottom, so the
       flex-column push-to-bottom trick is no longer needed. */
    background: var(--bg);
}
.bs-admin-nav-signout { color: var(--text-muted); }
.bs-admin-nav-signout:hover { color: var(--danger); }

/* Edit-mode chip at top of the admin nav (m101). Minimal: quiet outline
   when off, subtle accent-tinted fill when on. Icon swap (Edit / EditOff)
   is the primary state cue - no separate on/off text needed. Sits above
   the routing nav so admins spot it first without hunting. */
.bs-admin-nav-editmode-chip {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.55rem 0.85rem;
    margin: 0 0 0.5rem;
    border-radius: var(--radius-input, 12px);
    border: 1px solid var(--surface-raised);
    background: transparent;
    color: var(--text-muted);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.85rem;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background 140ms var(--ease), border-color 140ms var(--ease), color 140ms var(--ease);
}
.bs-admin-nav-editmode-chip .mud-icon-root {
    color: currentColor;
    font-size: 18px;
    flex-shrink: 0;
}
.bs-admin-nav-editmode-chip:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--surface-raised));
    color: var(--text);
}
.bs-admin-nav-editmode-chip.is-active {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    border-color: var(--accent);
    color: var(--accent);
}
/* Mini/collapsed drawer: chip becomes a 40×40 icon button, still keeps
   the outline so it reads as "an action" rather than another routing icon. */
.bs-admin-drawer.is-mini .bs-admin-nav-editmode-chip {
    justify-content: center;
    padding: 0;
    margin: 0 auto 0.5rem;
    width: 40px;
    height: 40px;
    border-radius: 10px;
}
.bs-admin-drawer.is-mini .bs-admin-nav-editmode-label { display: none; }

/* -------- Content editor primitives - shared by InlineSectionEditor
   (Landing/Privacy) and StudioContactEditor. Previously included card
   scaffolding for the removed /admin/content page (card head, hint,
   actions-head, live-link) - those were dropped when the page went away.
   Retained: state chip, tabs, textarea, help block, dirty indicator,
   preview shell, updated-at footer (StudioContactEditor). -------- */
.bs-content-updated {
    color: var(--text-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
}

.bs-content-tabs {
    display: inline-flex;
    padding: 3px;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    gap: 0.2rem;
    margin-bottom: 0.75rem;
}
.bs-content-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: none;
    color: var(--text-muted);
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}
.bs-content-tab:hover:not(.is-active) { color: var(--text); }
.bs-content-tab.is-active {
    background: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
}
.bs-content-tab.is-active .mud-icon-root { color: currentColor; }
/* Spec §8: 2px accent ring w/ 2px offset on every interactive element,
   visible on keyboard focus but not on mouse click (:focus-visible). */
.bs-content-tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.bs-content-textarea {
    width: 100%;
    min-height: 240px;
    resize: vertical;
    padding: 1rem 1.15rem;
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    font-family: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: 0.9rem;
    line-height: 1.55;
    transition: border-color 0.15s ease;
}
.bs-content-textarea:focus { border-color: var(--accent); }
.bs-content-textarea:focus:not(:focus-visible) { outline: none; }
.bs-content-textarea::placeholder { color: var(--text-muted); }

.bs-content-help {
    margin-top: 0.6rem;
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.6;
}
.bs-content-help code {
    background: var(--surface-raised);
    color: var(--text);
    padding: 1px 6px;
    border-radius: 4px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem;
}

.bs-content-preview {
    padding: 1.25rem 1.5rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    min-height: 240px;
    line-height: 1.65;
    color: var(--text);
    /* The preview is a stand-in for real page render - resemble the landing body. */
}
.bs-content-preview :where(h2, h3, h4) {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 1.25rem 0 0.5rem;
    line-height: 1.2;
}
.bs-content-preview h2 { font-size: 1.75rem; }
.bs-content-preview h3 { font-size: 1.35rem; }
.bs-content-preview h4 { font-size: 1.1rem; }
.bs-content-preview p  { margin: 0 0 1rem; }
.bs-content-preview a  { color: var(--accent); }
.bs-content-preview ul, .bs-content-preview ol { padding-left: 1.5rem; margin: 0 0 1rem; }
.bs-content-preview-empty {
    color: var(--text-muted);
    font-style: italic;
    text-align: center;
    padding: 3rem 0;
}

.bs-content-dirty {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.bs-content-dirty::before {
    content: "";
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-muted);
}
.bs-content-dirty.is-dirty { color: var(--warning); }
.bs-content-dirty.is-dirty::before { background: var(--warning); }

/* -------- Announcement dialog broadcast controls (spec §6.4) -------- */
.bs-broadcast-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 4px 10px 4px 8px;
    border-radius: 999px;
    background: rgba(53, 192, 138, 0.10);
    color: var(--success);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-bottom: 1rem;
}
.bs-broadcast-chip .mud-icon-root { font-size: 14px; }

.bs-broadcast-toggle {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    margin-top: 0.75rem;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.bs-broadcast-toggle:hover { border-color: var(--text-muted); }
.bs-broadcast-toggle.is-hot {
    border-color: var(--danger);
    background: rgba(229, 72, 77, 0.05);
}
.bs-broadcast-toggle-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--text);
    letter-spacing: -0.005em;
}
.bs-broadcast-toggle-desc {
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.45;
    margin-top: 3px;
}
.bs-broadcast-toggle-warn {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--warning);
    font-size: 0.82rem;
    font-weight: 600;
    margin-top: 0.5rem;
    padding: 0.4rem 0.6rem;
    background: rgba(242, 183, 5, 0.08);
    border-radius: var(--radius-input);
}
.bs-broadcast-toggle-warn .mud-icon-root { color: var(--warning); }

/* -------- Members list (/admin/users) -------- */
.bs-members-filters {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.bs-members-search {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    padding: 6px 14px;
    min-width: 280px;
    flex: 1;
    max-width: 460px;
    transition: border-color 0.15s ease;
}
.bs-members-search:focus-within { border-color: var(--accent); }
.bs-members-search .mud-icon-root { color: var(--text-muted); }
.bs-members-search-input {
    flex: 1;
    background: transparent;
    border: none;
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
    padding: 6px 0;
}
.bs-members-search-input::placeholder { color: var(--text-muted); }
.bs-members-search-input:focus:not(:focus-visible) { outline: none; }
.bs-members-search-clear {
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px;
    display: inline-flex;
    align-items: center;
}
.bs-members-search-clear:hover { color: var(--text); }

.bs-members-list { display: grid; gap: 0.5rem; }
.bs-member-row {
    /* Two children now (NavLink primary + action buttons), not three.
       The NavLink owns its own inner grid for avatar + body. */
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    color: var(--text);
    text-decoration: none;
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}
.bs-member-row:hover { transform: translateY(-1px); border-color: var(--accent); }
.bs-member-row.is-inactive { opacity: 0.65; }
/* Primary click target inside a member row - carries the avatar + body columns
   as its own grid so the row stays a plain container and action buttons don't
   nest inside a link (invalid HTML + AT confusion). */
.bs-member-row-primary {
    display: grid;
    grid-template-columns: 40px 1fr;
    align-items: center;
    gap: 1rem;
    text-decoration: none;
    color: inherit;
    min-width: 0;
}
.bs-member-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: -0.01em;
    color: var(--text);
}
.bs-member-body { min-width: 0; }
.bs-member-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 1rem;
    color: var(--text);
    letter-spacing: -0.005em;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bs-member-role-chip {
    display: inline-block;
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 2px 8px;
    border-radius: 999px;
    background: rgba(245, 36, 40, 0.12);
    color: var(--accent);
}
.bs-member-role-chip.is-inactive {
    background: rgba(140, 148, 143, 0.12);
    color: var(--text-muted);
}
.bs-member-meta {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 3px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-member-phone {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.bs-member-actions {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    opacity: 0.6;
    transition: opacity 0.15s ease;
}
.bs-member-row:hover .bs-member-actions { opacity: 1; }

@media (max-width: 599.98px) {
    /* Row is already `1fr auto` at all widths since m80. The mobile override
       used to squish avatar+body into 40px+1fr - no longer needed. Keep only
       the actions-below-row treatment. */
    .bs-member-actions {
        grid-column: 1 / -1;
        justify-content: flex-end;
        border-top: 1px dashed var(--surface-raised);
        padding-top: 0.5rem;
        opacity: 1;
    }
}

/* -------- Fill indicator in the admin schedule table -------- */
.bs-admin-fill {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    justify-content: center;
}
.bs-admin-fill-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.15;
}
.bs-admin-fill-count {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 600;
    font-size: 0.85rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.bs-admin-fill-wait {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem;
    color: var(--warning);
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
}

/* -------- Instructor shareable roster page (spec §7 Gap 8, /i/{token}) -------- */
.bs-iroster {
    max-width: 720px;
    margin: 0 auto;
    padding: 2rem 1rem 4rem;
    display: grid;
    gap: 1rem;
}
.bs-iroster-hero { display: grid; gap: 0.4rem; margin-bottom: 0.5rem; }
.bs-iroster-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.5rem, 4vw, 2rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin: 0.25rem 0 0;
}
.bs-iroster-sub {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    margin: 0.25rem 0 0;
    max-width: 60ch;
}
.bs-iroster-card {
    display: grid;
    gap: 0.75rem;
    padding: 1.25rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--surface-raised);
    border-radius: var(--radius-card);
}
.bs-iroster-card.is-today { border-left-color: var(--accent); background: rgba(245, 36, 40, 0.04); }
.bs-iroster-card-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
}
.bs-iroster-when {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--text-muted);
}
.bs-iroster-class {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.25rem;
    letter-spacing: -0.01em;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 4px 0 2px;
}
.bs-iroster-meta {
    color: var(--text-muted);
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.bs-iroster-time {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.bs-iroster-count { text-align: right; flex-shrink: 0; }
.bs-iroster-count-num {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.75rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--accent);
    line-height: 1;
}
.bs-iroster-count-label {
    color: var(--text-muted);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    display: block;
    margin-top: 4px;
}
.bs-iroster-wait {
    color: var(--warning);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.75rem;
    margin-top: 6px;
    font-variant-numeric: tabular-nums;
}

.bs-iroster-names {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 0.35rem 1rem;
}
.bs-iroster-names li {
    color: var(--text);
    font-size: 0.95rem;
    padding: 4px 0;
    border-bottom: 1px dashed var(--surface-raised);
}
.bs-iroster-names.is-waitlist li { color: var(--text-muted); font-style: italic; }
.bs-iroster-initial { color: var(--text-muted); font-weight: 500; }
.bs-iroster-wl-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem;
    color: var(--warning);
    margin-right: 0.35rem;
    font-variant-numeric: tabular-nums;
}
.bs-iroster-waitlist-head {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    color: var(--warning);
    margin-top: 0.85rem;
}
.bs-iroster-empty {
    color: var(--text-muted);
    font-style: italic;
    padding: 0.75rem 0;
}
.bs-iroster-footer {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.78rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--surface-raised);
    margin-top: 1rem;
}

/* Admin instructor editor share-link block */
.bs-share-link {
    display: grid;
    gap: 0.75rem;
    padding: 1rem 1.15rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
}
.bs-share-link-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}
.bs-share-link-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text);
}
.bs-share-link-status {
    color: var(--text-muted);
    font-size: 0.72rem;
    font-family: 'JetBrains Mono', monospace;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
}
.bs-share-link-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.4;
}
.bs-share-link-url {
    padding: 0.55rem 0.75rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.82rem;
    color: var(--accent);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    user-select: all;
}
.bs-share-link-actions {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
}

/* -------- Session roster dialog (admin, spec §4.2) -------- */
.bs-roster-dialog .mud-dialog { max-width: 560px; }
.bs-roster-title-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    width: 100%;
}
.bs-roster-when {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-top: 4px;
    font-variant-numeric: tabular-nums;
}
.bs-roster-counts {
    display: flex;
    gap: 0.75rem;
    flex-shrink: 0;
}
.bs-roster-count-block {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
}
.bs-roster-count-num {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.75rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--accent);
    line-height: 1;
}
.bs-roster-count-block.is-waitlist .bs-roster-count-num { color: var(--warning); font-size: 1.35rem; }
.bs-roster-count-label {
    color: var(--text-muted);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    margin-top: 3px;
}

.bs-roster-actions-row {
    display: flex;
    gap: 0.5rem;
    padding: 0.35rem 0 0.75rem;
}
.bs-roster-copy {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    color: var(--text);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.bs-roster-copy:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.bs-roster-copy:disabled { opacity: 0.5; cursor: default; }

.bs-roster-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    color: var(--text-muted);
    padding: 1rem 0 0.5rem;
    border-bottom: 1px dashed var(--surface-raised);
    margin-bottom: 0.5rem;
}
.bs-roster-section-head.is-waitlist { color: var(--warning); }
.bs-roster-section-count {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text);
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
}

.bs-roster-list { display: grid; gap: 0.25rem; }
.bs-roster-row {
    display: grid;
    grid-template-columns: 36px 1fr auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.5rem;
    border-radius: var(--radius-input);
    transition: background 0.15s ease;
}
.bs-roster-row:hover { background: rgba(255, 255, 255, 0.02); }
.bs-roster-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.75rem;
    color: var(--text);
    letter-spacing: -0.01em;
}
.bs-roster-avatar.is-waitlist {
    background: rgba(242, 183, 5, 0.12);
    color: var(--warning);
}
.bs-roster-wl-num { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; }
.bs-roster-body { min-width: 0; }
.bs-roster-name { font-weight: 600; color: var(--text); font-size: 0.95rem; line-height: 1.2; }
.bs-roster-meta {
    color: var(--text-muted);
    font-size: 0.78rem;
    margin-top: 2px;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
}
.bs-roster-phone {
    color: var(--text);
    text-decoration: none;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    transition: color 0.15s ease;
}
.bs-roster-phone:hover { color: var(--accent); text-decoration: underline; }
.bs-roster-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Attendance tri-state button (spec §7 Gap 4) */
.bs-roster-attend {
    background: transparent;
    border: 1px solid var(--surface-raised);
    color: var(--text-muted);
    width: 32px;
    height: 32px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
    margin-right: 0.25rem;
}
.bs-roster-attend .mud-icon-root { font-size: 20px !important; color: currentColor; }
.bs-roster-attend:hover { border-color: var(--text); color: var(--text); }
.bs-roster-attend.is-present {
    border-color: var(--success);
    color: var(--success);
    background: rgba(53, 192, 138, 0.10);
}
.bs-roster-attend.is-absent {
    border-color: var(--danger);
    color: var(--danger);
    background: rgba(229, 72, 77, 0.08);
}
.bs-roster-attend:disabled { opacity: 0.5; cursor: default; }
.bs-roster-attend-checkin { border-color: rgba(53, 192, 138, 0.35); color: var(--success); }
.bs-roster-attend-checkin:hover:not(:disabled) {
    border-color: var(--success);
    background: rgba(53, 192, 138, 0.10);
}

/* Present-count chip in the header - mirrors the reserved/waitlist counts */
.bs-roster-count-block.is-present .bs-roster-count-num {
    color: var(--success);
    font-size: 1.35rem;
}

/* Walk-in attendance search (παρουσιολόγιο). Sits above the copy-phones row
   only while the session is live. One-tap adds anyone who's not already on
   the roster and marks them present. */
.bs-roster-walkin {
    display: grid;
    gap: 0.6rem;
    padding: 0.75rem;
    margin: 0.5rem 0 0.85rem;
    background: color-mix(in oklab, var(--accent) 6%, var(--surface));
    border: 1px solid color-mix(in oklab, var(--accent) 20%, var(--surface-raised));
    border-radius: var(--radius-card);
}
.bs-roster-walkin-head { display: grid; gap: 3px; }
.bs-roster-walkin-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    color: var(--text);
}
.bs-roster-walkin-hint {
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1.35;
}
.bs-roster-walkin-input-wrap {
    position: relative;
    display: flex;
    align-items: center;
}
.bs-roster-walkin-icon {
    position: absolute;
    left: 0.65rem;
    color: var(--text-muted);
    pointer-events: none;
}
.bs-roster-walkin-input {
    flex: 1;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
    padding: 0.55rem 2.25rem 0.55rem 2.25rem;
    outline: none;
    transition: border-color 0.15s ease;
    min-height: 44px;
}
.bs-roster-walkin-input:focus {
    /* §8 focus ring spec: 2px accent + 2px offset via native `outline`
       (was a translucent box-shadow, which counted against the §4
       one-shadow rule). */
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-roster-walkin-clear {
    position: absolute;
    right: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: none;
    color: var(--text-muted);
    padding: 6px;
    border-radius: 50%;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* WCAG 2.5.5 minimum target: 44×44px. Was 32×32 which failed the
       audit and made the walk-in row edit awkward on phones. */
    min-width: 44px;
    min-height: 44px;
}
.bs-roster-walkin-clear:hover { color: var(--text); background: var(--surface-raised); }
.bs-roster-walkin-note {
    color: var(--text-muted);
    font-size: 0.82rem;
    padding: 0.25rem 0.15rem;
}
.bs-roster-walkin-list { display: grid; gap: 0.25rem; }
.bs-roster-walkin-row {
    display: grid;
    grid-template-columns: 36px 1fr auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.6rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-input);
    text-align: left;
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    min-height: 44px;
}
.bs-roster-walkin-row:hover:not(:disabled) {
    background: var(--surface);
    border-color: color-mix(in oklab, var(--accent) 35%, var(--surface-raised));
}
.bs-roster-walkin-row:disabled { opacity: 0.6; cursor: default; }
.bs-roster-walkin-body { min-width: 0; display: grid; }
.bs-roster-walkin-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.4rem 0.75rem;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.bs-roster-walkin-cta .mud-icon-root { font-size: 16px !important; }

/* -------- PWA install prompt bar (spec §6.8) -------- */
.bs-install-bar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-card);
    margin-bottom: 1rem;
    animation: bs-install-slide 260ms var(--ease);
}
@keyframes bs-install-slide {
    from { transform: translateY(-8px); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .bs-install-bar { animation: none; }
}
.bs-install-mark {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    flex-shrink: 0;
}
.bs-install-body { flex: 1; min-width: 0; }
.bs-install-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text);
    letter-spacing: -0.005em;
    line-height: 1.2;
}
.bs-install-sub {
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-top: 2px;
    line-height: 1.4;
}
.bs-install-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}
.bs-install-dismiss {
    background: transparent;
    border: none;
    color: var(--text-muted);
    padding: 6px 10px;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    border-radius: var(--radius-input);
}
.bs-install-dismiss:hover { color: var(--text); }
.bs-install-cta {
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    padding: 8px 16px;
    border-radius: var(--radius-pill);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.bs-install-cta:hover {
    transform: translateY(-1px);
    filter: brightness(1.08);
}
@media (max-width: 599.98px) {
    .bs-install-bar { flex-wrap: wrap; }
    .bs-install-actions { width: 100%; justify-content: flex-end; }
}

/* -------- Cookie notice (first-visit dismissible bar) --------
   Fixed at the bottom, sits above the mobile bottom-nav (~76px tall) via a
   generous offset so it never overlaps for authed members. On desktop it
   just floats slightly higher than the viewport bottom, which reads as
   intentional rather than pinned. Non-blocking - members can scroll and
   interact with the page underneath. */
.bs-cookie-notice {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
    z-index: 1200;
    width: min(560px, calc(100vw - 32px));
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-card);
    /* §4: only the bottom-sheet gets a shadow. Border + accent stripe carry
       the lift on their own. */
    animation: bs-cookie-notice-in 220ms var(--ease);
}
@keyframes bs-cookie-notice-in {
    from { transform: translate(-50%, 8px); opacity: 0; }
    to   { transform: translate(-50%, 0);   opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .bs-cookie-notice { animation: none; }
}
.bs-cookie-notice-body {
    flex: 1;
    min-width: 0;
}
.bs-cookie-notice-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.2;
    color: var(--text);
}
.bs-cookie-notice-sub {
    color: var(--text-muted);
    font-size: 0.8rem;
    margin-top: 2px;
    line-height: 1.4;
}
.bs-cookie-notice-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}
.bs-cookie-notice-link {
    color: var(--text-muted);
    font-size: 0.82rem;
    text-decoration: underline;
    padding: 6px 4px;
    border-radius: var(--radius-input);
}
.bs-cookie-notice-link:hover,
.bs-cookie-notice-link:focus-visible {
    color: var(--text);
}
.bs-cookie-notice-cta {
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    padding: 8px 16px;
    border-radius: var(--radius-pill);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    min-height: 36px;
    min-width: 60px;
    transition: transform 0.15s ease, filter 0.15s ease;
}
.bs-cookie-notice-cta:hover {
    transform: translateY(-1px);
    filter: brightness(1.08);
}
.bs-cookie-notice-cta:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
@media (max-width: 599.98px) {
    .bs-cookie-notice {
        flex-wrap: wrap;
        bottom: calc(env(safe-area-inset-bottom, 0px) + 84px);
    }
    .bs-cookie-notice-actions {
        width: 100%;
        justify-content: flex-end;
    }
}

/* -------- Contact-the-studio sheet (spec §6.3) -------- */
.bs-contact-sheet .mud-dialog { max-width: 480px; }
.bs-contact-lead {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    margin: 0 0 1rem;
}
.bs-contact-rows { display: grid; gap: 0.5rem; }
.bs-contact-row {
    display: grid;
    grid-template-columns: 44px 1fr auto;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--text);
    text-decoration: none;
    transition: transform 0.12s ease, border-color 0.15s ease;
}
.bs-contact-row:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
}
.bs-contact-icon {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-raised);
    border-radius: 12px;
}
.bs-contact-icon .mud-icon-root { font-size: 22px; }
.bs-contact-body { min-width: 0; }
.bs-contact-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.2;
}
.bs-contact-value {
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-contact-open { opacity: 0.35; font-size: 18px !important; }
.bs-contact-row:hover .bs-contact-open { opacity: 0.85; color: var(--accent); }

/* -------- Bottom-sheet-on-mobile / modal-on-desktop (spec §3.2) --------
   MudDialog with .bs-sheet-dialog class gets slid up from the bottom on mobile
   with a rounded top and a grabber affordance; desktop keeps the centred modal. */
.bs-sheet-dialog .mud-dialog {
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    background: var(--surface);
    max-width: 520px;
}
.bs-sheet-title-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    width: 100%;
}
.bs-cal-detail-time {
    display: block;
    margin-top: 4px;
    font-family: 'JetBrains Mono', monospace;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
}
.bs-cal-detail-cap {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.bs-cal-detail-cap .sep { color: var(--text-muted); margin: 0 2px; }
.bs-cal-detail-waitlist {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-left: 0.5rem;
}

/* "Who's in" strip under the cypher - one row per session sheet, tight and
   quiet so it never fights the ring. Names use body-strong weight so they read
   as identities rather than metadata; "+N more" drops to muted. */
.bs-cypher-whosin {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-top: 1rem;
}
.bs-cypher-whosin-names {
    color: var(--text);
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.4;
}
.bs-cypher-whosin-more {
    color: var(--text-muted);
    font-weight: 400;
    margin-left: 0.5rem;
}
.bs-cypher-whosin-link {
    color: inherit;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease);
}
.bs-cypher-whosin-link:hover,
.bs-cypher-whosin-link:focus-visible {
    color: var(--accent);
    outline: none;
}
.bs-cypher-whosin-sep { color: var(--text-muted); font-weight: 400; }
.bs-cal-detail-note.is-danger {
    background: rgba(229, 72, 77, 0.08);
    color: var(--danger);
    border-left: 3px solid var(--danger);
    /* Louder in the no-subscription case so members can't miss the "you need
       a sub, hit the button below" explanation next to a big accent CTA (m97). */
    font-weight: 500;
    padding: 1rem 1.15rem;
}

@media (max-width: 599.98px) {
    .bs-sheet-dialog .mud-dialog {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        top: auto;
        max-width: 100%;
        max-height: 88vh;
        border-radius: var(--radius-card) var(--radius-card) 0 0;
        border-bottom: none;
        margin: 0;
        animation: bs-sheet-up var(--dur-base) var(--ease);
    }
    /* Drag-affordance grabber above the title */
    .bs-sheet-dialog .mud-dialog::before {
        content: "";
        display: block;
        width: 36px;
        height: 4px;
        border-radius: 999px;
        background: var(--surface-raised);
        margin: 8px auto 4px;
    }
    .bs-sheet-dialog .mud-dialog-actions {
        border-top: 1px solid var(--surface-raised);
        padding: 1rem;
        background: var(--surface);
    }
}
@keyframes bs-sheet-up {
    from { transform: translateY(20%); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    @keyframes bs-sheet-up { from { opacity: 1; } to { opacity: 1; } }
}

/* Detail dialog body */
.bs-cal-detail dt {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-top: 0.75rem;
    margin-bottom: 0.2rem;
    font-weight: 600;
}
.bs-cal-detail dd {
    margin: 0;
    color: var(--text);
    font-size: 0.95rem;
}
.bs-cal-detail-note {
    margin-top: 1rem;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-input);
    background: rgba(160, 160, 184, 0.06);
    color: var(--text-muted);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    line-height: 1.5;
}

/* -------- Page hero (compact top-of-page header, used everywhere except Landing) -------- */
.bs-page-hero {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: clamp(1.25rem, 2vw, 2rem);
}
.bs-page-hero-title {
    font-family: 'Manrope', system-ui, sans-serif;
    /* Fluid title (m96): 28→52px so page titles pull weight on 2K, not stay
       stuck at ~40px in a 1600px column. Line-height tightened for lockup. */
    font-size: clamp(1.75rem, 3vw, 3.25rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.05;
    margin: 0.25rem 0 0;
}
.bs-page-hero-sub {
    color: var(--text-muted);
    /* Body: 14→17px so sub-copy is readable at 2K instead of caption-sized. */
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.5;
    margin-top: 0.5rem;
    max-width: 62ch;
}
.bs-page-hero-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
}

/* -------- Empty state -------- */
.bs-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 3rem 1rem;
    text-align: center;
    background: var(--surface);
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-card);
    color: var(--text-muted);
}
.bs-empty .mud-icon-root {
    font-size: 42px;
    opacity: 0.6;
    margin-bottom: 0.75rem;
}
.bs-empty-title {
    font-family: 'Manrope', system-ui, sans-serif;
    color: var(--text);
    font-weight: 700;
    font-size: clamp(1rem, 1.15vw, 1.1875rem);
    letter-spacing: -0.005em;
    margin-bottom: 0.4rem;
}
.bs-empty-sub {
    color: var(--text-muted);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    line-height: 1.5;
    max-width: 48ch;
    margin: 0 auto;
}

/* -------- Section (title + optional description + slot) -------- */
.bs-section { margin-top: 2.5rem; }
.bs-section:first-child { margin-top: 0; }
.bs-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}
.bs-section-head h2 {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.25rem, 1.5vw, 1.625rem);
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
    margin: 0;
}
.bs-section-desc {
    color: var(--text-muted);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    line-height: 1.55;
    margin: -0.25rem 0 1rem;
}

/* -------- Auth form frame (login / register / set-password / forgot-password) --------
   Full-viewport dark shell with a subtle asphalt-with-torchlight gradient, centred
   card with generous padding. The eyebrow above the title is where the "sign in |
   register | set your password" mini-context lives so the title itself stays crisp
   Manrope display. */
.bs-auth-shell {
    min-height: calc(100vh - 96px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.5rem 1rem 4rem;
    background:
        radial-gradient(700px 500px at 15% 0%,   rgba(245, 36, 40, 0.14), transparent 60%),
        radial-gradient(600px 500px at 100% 100%, rgba(245, 36, 40, 0.08), transparent 60%);
}

/* -------- Split-panel auth (login) --------
   Two-column shell on desktop: left panel carries the hero photo + red grade
   + editorial pull-quote + credibility stat row + wolf watermark. Right
   panel holds the form, reveal-animated on mount. Collapses to a single
   column on narrow viewports where the brand panel becomes a top strip.

   The whole split is pulled up under a transparent header (EmptyLayout
   adds `is-transparent` on /login etc.) so the brand photo extends
   edge-to-edge to the viewport top with no ugly gap between header and
   panel. Both panels compensate with padding-top so their content stays
   clear of the header line. */
.bs-auth-split {
    min-height: 100vh;
    margin-top: calc(-1 * var(--bs-app-bar-h));
    display: grid;
    grid-template-columns: 1fr;
    isolation: isolate;
}
/* MudMainContent bakes in `padding-top: var(--mud-appbar-height)` (64px)
   for a MudAppBar we don't render. Zero it out on auth split pages so the
   negative-margin pull-up starts from a clean top edge. */
.mud-main-content:has(.bs-auth-split) { padding-top: 0; }
@media (min-width: 900px) {
    .bs-auth-split { grid-template-columns: minmax(380px, 1.1fr) minmax(440px, 1fr); }
}
.bs-auth-brand-panel {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    min-height: min(720px, 92vh);
    padding-top: var(--bs-app-bar-h);
    display: flex;
    align-items: flex-end;
    color: var(--text);
}
@keyframes bs-auth-kenburns {
    from { transform: scale(1.06) translate(0, 0); }
    to   { transform: scale(1.16) translate(-1.2%, -1.6%); }
}
.bs-auth-brand-image {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: url("../img/landing/hero.jpg");
    background-size: cover;
    background-position: center 35%;
    filter: saturate(0.85) contrast(1.05);
    transform-origin: 30% 40%;
    animation: bs-auth-kenburns 26s ease-in-out infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
    .bs-auth-brand-image { animation: none; transform: scale(1.06); }
}
.bs-auth-brand-grade {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
        radial-gradient(58% 55% at 18% 20%, color-mix(in srgb, var(--accent) 28%, transparent), transparent 55%),
        /* Top vignette darker so the transparent-header brand wordmark stays
           readable against the photo. */
        linear-gradient(180deg, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0.2) 22%, transparent 40%, rgba(0, 0, 0, 0.85) 100%);
}
/* Wolf watermark, faint, bottom-right of the brand panel. Kept small so it
   reinforces identity without competing with the copy. */
.bs-auth-brand-mark {
    position: absolute;
    bottom: clamp(20px, 3vw, 40px);
    right: clamp(20px, 3vw, 40px);
    width: clamp(72px, 7vw, 120px);
    aspect-ratio: 1;
    background: url("../brand/blackout_logo.svg") center / contain no-repeat;
    opacity: 0.42;
    mix-blend-mode: screen;
    z-index: 2;
    pointer-events: none;
}
@keyframes bs-auth-copy-reveal {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
}
.bs-auth-brand-copy {
    position: relative;
    z-index: 3;
    padding: clamp(28px, 5vw, 72px);
    padding-left: calc(clamp(28px, 5vw, 72px) + 20px);
    max-width: 56ch;
    animation: bs-auth-copy-reveal 0.7s var(--ease) both;
    animation-delay: 0.1s;
}
/* Accent bar to the left of the copy - pull-quote marker. Ties the block
   to the brand red without adding a second accent-filled surface. */
.bs-auth-brand-copy::before {
    content: "";
    position: absolute;
    left: clamp(28px, 5vw, 72px);
    top: clamp(28px, 5vw, 72px);
    bottom: clamp(28px, 5vw, 72px);
    width: 3px;
    background: var(--accent);
    border-radius: 3px;
}
@media (prefers-reduced-motion: reduce) {
    .bs-auth-brand-copy { animation: none; }
}
.bs-auth-brand-eyebrow {
    display: inline-block;
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(11px, 0.75vw, 13px);
    font-weight: 700;
    letter-spacing: 0.18em;
    color: color-mix(in srgb, var(--text) 90%, transparent);
    margin-bottom: 20px;
    text-transform: uppercase;
    line-height: 1;
}
.bs-auth-brand-quote {
    font-family: 'Manrope', system-ui, sans-serif;
    /* Big editorial pull-quote - 32px mobile → 64px on 2K. Same tightness
       as the landing hero title (letter-spacing -0.03em, line-height 1.15). */
    font-size: clamp(2rem, 3.6vw, 3.75rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--text);
    margin: 0;
    text-wrap: balance;
}
.bs-auth-brand-attr {
    margin-top: clamp(16px, 1.4vw, 24px);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: clamp(13px, 0.85vw, 15px);
    font-weight: 500;
    letter-spacing: 0.01em;
    color: color-mix(in srgb, var(--text) 65%, transparent);
}
.bs-auth-brand-stats {
    display: flex;
    gap: clamp(20px, 2.4vw, 36px);
    margin-top: clamp(28px, 3vw, 44px);
    padding-top: clamp(20px, 2vw, 28px);
    border-top: 1px solid color-mix(in srgb, var(--text) 15%, transparent);
    flex-wrap: wrap;
}
.bs-auth-brand-stat { display: flex; flex-direction: column; gap: 6px; }
.bs-auth-brand-stat-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(1.5rem, 1.9vw, 2rem);
    font-weight: 500;
    color: var(--text);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.bs-auth-brand-stat-label {
    font-family: 'Manrope', system-ui, sans-serif;
    color: color-mix(in srgb, var(--text) 62%, transparent);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-weight: 700;
    line-height: 1;
}

.bs-auth-form-panel {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Extra top padding so the vertically-centred form doesn't slip under
       the transparent header on tall viewports. */
    padding: calc(var(--bs-app-bar-h) + clamp(1.5rem, 3vw, 3rem)) clamp(1rem, 4vw, 3.5rem) clamp(2rem, 5vw, 5rem);
    background:
        radial-gradient(600px 500px at 90% 10%, rgba(245, 36, 40, 0.08), transparent 65%),
        radial-gradient(500px 400px at 10% 100%, rgba(245, 36, 40, 0.05), transparent 65%),
        var(--bg);
}
/* Card treatment - elevation via surface step + border only (spec §4:
   elevation from borders and surface steps, not shadows). Reveal on mount
   with a short fade-up so the form feels landed, not dropped. */
@keyframes bs-auth-card-reveal {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}
.bs-auth-form-panel .bs-auth-card {
    box-shadow: none;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    max-width: 440px;
    padding: clamp(2rem, 3.4vw, 3rem);
    animation: bs-auth-card-reveal 0.5s var(--ease) both;
}
/* Form input polish - accent-red border-color on focus, kept subtle at rest
   so the form panel reads as quiet copy until interacted with. */
.bs-auth-form-panel .mud-input-outlined-border {
    border-color: var(--surface-raised);
    border-radius: var(--radius-input);
    transition: border-color 0.18s var(--ease);
}
.bs-auth-form-panel .mud-input-outlined:hover:not(.mud-input-focused) .mud-input-outlined-border {
    border-color: color-mix(in srgb, var(--text-muted) 55%, var(--surface-raised));
}
.bs-auth-form-panel .mud-input-outlined.mud-input-focused .mud-input-outlined-border,
.bs-auth-form-panel .mud-input-focused .mud-input-outlined-border {
    border-color: var(--accent) !important;
    border-width: 2px !important;
}
.bs-auth-form-panel .mud-input-label { color: var(--text-muted); }
.bs-auth-form-panel .mud-input-label.mud-input-label-shrink,
.bs-auth-form-panel .mud-input-focused .mud-input-label { color: var(--accent); }

/* Primary CTA - pill button matching the landing hero primary. Bigger tap
   target (56px), stronger typography, subtle lift on hover. No shadow-glow
   (spec §1: forbidden glow effects) - depth comes from the transform +
   brightness delta. */
.bs-auth-form-panel .mud-button-root.mud-button-filled {
    height: 56px;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1rem;
    letter-spacing: -0.005em;
    text-transform: none;
    transition: transform 0.15s var(--ease), filter 0.15s var(--ease), background 0.15s var(--ease);
}
.bs-auth-form-panel .mud-button-root.mud-button-filled:not(:disabled):hover {
    transform: translateY(-1px);
    filter: brightness(1.08);
}
.bs-auth-form-panel .mud-button-root.mud-button-filled:disabled {
    background: color-mix(in srgb, var(--surface-raised) 80%, var(--surface));
    color: var(--text-muted);
    opacity: 1;
}

/* "or" divider between forgot-password and register alt link. Hairline
   both sides so the alt CTA reads as a distinct secondary path, not a
   footnote. */
.bs-auth-or {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 1.5rem 0 1rem;
    color: color-mix(in srgb, var(--text-muted) 80%, transparent);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-weight: 700;
}
.bs-auth-or::before, .bs-auth-or::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--surface-raised);
}
.bs-auth-form-panel .bs-auth-alt {
    border-top: none;
    padding-top: 0;
    margin-top: 0;
}
.bs-auth-card {
    width: 100%;
    max-width: 460px;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: clamp(1.75rem, 4vw, 2.75rem);
    /* §4: elevation via border + surface step; only bottom-sheet keeps a shadow. */
    display: grid;
    gap: 0.25rem;
}
.bs-auth-card-wide {
    max-width: 600px;
}
.bs-auth-eyebrow {
    color: var(--accent);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 700;
    margin-bottom: 0.35rem;
}
.bs-auth-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 3vw, 2.25rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--text);
    margin: 0 0 0.35rem;
}
.bs-auth-sub {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.5;
    margin: 0 0 1.5rem;
}
.bs-auth-alt {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-top: 1.25rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--surface-raised);
}
/* Always-underlined so the link is discoverable even at contrast ratios that only
   pass AA large (dark theme --accent → --bg is 4.17:1, below 4.5:1 for small text).
   The underline is a redundant cue that meets WCAG 1.4.1 (do not rely on color alone). */
.bs-auth-alt a { color: var(--accent); text-decoration: underline; font-weight: 600; }

/* -------- Landing page (anonymous /) per design-spec §5 -------- */
.bs-landing {
    display: grid;
    gap: 4rem;
    padding-bottom: 4rem;
    /* Grid child min-width defaults to auto (= min-content), which lets a
       single wide grid-item drag the whole grid track wider than the phone
       viewport. Force the wrapper's grid track to shrink to parent width
       regardless. See .bs-landing-v2 above for the full rationale. */
    min-width: 0;
}
.bs-landing > * {
    /* Same reason — each section is a grid child, so its own min-width
       needs to be 0 for the parent track to shrink cleanly. */
    min-width: 0;
}
/* Legacy m91 hero rules removed - .bs-landing-hero{,-glow,-inner,-title,-sub,
   -cta-row,-primary,-secondary} were unused by markup (v2 rewrite uses the
   -v2 / -image / -grade / -content variants defined above at ~L851) but
   were shadowing the m94/m95 hero rules due to later cascade position. That
   is why the max-width + watermark fixes appeared to have no effect. */

.bs-landing-section {
    display: grid;
    gap: 1rem;
}
.bs-landing-section-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 0.25rem;
}
.bs-landing-section-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 3.5vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    margin: 0.25rem 0 0;
}
.bs-landing-section-sub {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.5;
    max-width: 60ch;
    margin: 0.5rem 0 0;
}
.bs-landing-see-all {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
    font-size: 0.95rem;
    transition: transform 0.15s ease;
}
.bs-landing-see-all:hover { transform: translateX(2px); }

/* Admin table cell helpers - two-line rows (primary + muted sub) used by the
   rewards queue and any other list where a member/entity needs a subtitle. */
.bs-admin-cell-primary { color: var(--text); font-weight: 500; }
.bs-admin-cell-sub {
    color: var(--text-muted);
    font-size: 0.8rem;
    margin-top: 2px;
}

/* Dialog body wrapper - pads the DialogContent so nested MudTextField / prose
   doesn't butt up against the border. Lead paragraph reads as intro copy. */
.bs-dialog-body { padding-top: 0.25rem; }
.bs-dialog-lead {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    margin: 0 0 0.85rem;
}

/* -------- Streak card on Schedule (owner ask 2026-08-10 - daily check-in) ---------
   Design leans on cypher-ring vocabulary (spec §5) rather than the streak-flame
   chrome the design spec bans (CLAUDE.md §1). The ring fills clockwise as the
   member's streak grows; count is centred in JetBrains Mono; state changes are
   subtle (border tint, accent glow when reached) - no confetti, no badges. */
.bs-streak-card {
    display: grid;
    grid-template-columns: 88px 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.15rem;
    margin: 0 16px 12px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    transition: border-color var(--dur-base) var(--ease),
                box-shadow var(--dur-base) var(--ease);
}
.bs-streak-card.is-loading { padding: 0; margin: 0 16px 12px; }
/* Reached-milestone state: a 2px accent border (like an outline ring) marks
   the moment without adding the accent glow §1 forbids. §4 caps elevation
   to borders + surface steps outside the bottom sheet. */
.bs-streak-card.is-reached {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}
.bs-streak-card.is-broken { border-style: dashed; }

.bs-streak-ring {
    position: relative;
    width: 72px;
    height: 72px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.bs-streak-count {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: baseline;
    gap: 1px;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    line-height: 1;
    pointer-events: none;
}
.bs-streak-count-num {
    font-size: 1.35rem;
    font-weight: 700;
}
.bs-streak-count-goal {
    font-size: 0.7rem;
    color: var(--text-muted);
    font-weight: 500;
}
.bs-streak-card.is-reached .bs-streak-count-num { color: var(--accent); }

.bs-streak-body { min-width: 0; display: grid; gap: 3px; }
.bs-streak-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.005em;
    color: var(--text);
    line-height: 1.2;
}
.bs-streak-sub {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.4;
}
.bs-streak-card.is-broken .bs-streak-title { color: var(--text-muted); }

.bs-streak-action { display: flex; align-items: center; }
.bs-streak-done {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--success);
    font-size: 0.85rem;
    font-weight: 600;
    padding: 6px 12px;
    background: rgba(53, 192, 138, 0.08);
    border-radius: var(--radius-pill);
}

@media (max-width: 479.98px) {
    .bs-streak-card {
        grid-template-columns: 64px 1fr;
        grid-template-rows: auto auto;
        gap: 0.65rem 0.85rem;
    }
    .bs-streak-ring { width: 64px; height: 64px; }
    .bs-streak-action { grid-column: 1 / -1; justify-content: flex-end; }
}

/* -------- Latest news strip on landing -------- */
.bs-landing-news-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}
.bs-landing-news-card {
    display: block;
    padding: 1rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    color: var(--text);
    text-decoration: none;
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}
.bs-landing-news-card:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
}
.bs-landing-news-pinned {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--accent);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 0.35rem;
}
.bs-landing-news-title {
    font-weight: 700;
    font-size: 1.05rem;
    line-height: 1.3;
    color: var(--text);
    margin-bottom: 0.35rem;
}
.bs-landing-news-date {
    color: var(--text-muted);
    font-size: 0.8rem;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
}

/* -------- Landing CMS-authored sections (owner-authored HTML) --------
   The admin writes arbitrary sanitized HTML via the inline "Edit content"
   toggle on the landing/privacy pages. Give it a readable prose column,
   tame heading sizes, wrap images to the section width so a wide upload
   doesn't blow out the layout, and lay out anything that looks like a card
   grid via flex when the admin uses <ul>/<li> or <div>s. */
.bs-landing-cms-body {
    color: var(--text);
    font-size: 1rem;
    line-height: 1.65;
    max-width: 68ch;
}
.bs-landing-cms-body p { margin: 0 0 1rem; }
.bs-landing-cms-body h2, .bs-landing-cms-body h3 {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    letter-spacing: -0.005em;
    margin: 1.5rem 0 0.5rem;
}
.bs-landing-cms-body h2 { font-size: 1.35rem; }
.bs-landing-cms-body h3 { font-size: 1.15rem; }
.bs-landing-cms-body ul, .bs-landing-cms-body ol { margin: 0 0 1rem 1.25rem; padding: 0; }
.bs-landing-cms-body li { margin: 0 0 0.25rem; }
.bs-landing-cms-body a {
    color: var(--accent);
    text-decoration: underline;
}
.bs-landing-cms-body img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-card);
    margin: 0.5rem 0;
}
.bs-landing-cms-body iframe {
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--radius-card);
    margin: 0.5rem 0;
}
/* Sponsors block: laid out as a horizontal logo strip when the admin uses
   <ul> or a flex-friendly container. Center each item, tone down the logo
   contrast so they don't dominate the page. */
.bs-landing-sponsors .bs-landing-cms-body {
    max-width: none;
}
.bs-landing-sponsors .bs-landing-cms-body ul {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem 2rem;
    align-items: center;
    justify-content: flex-start;
    list-style: none;
    margin: 0;
    padding: 0;
}
.bs-landing-sponsors .bs-landing-cms-body img {
    max-height: 56px;
    width: auto;
    opacity: 0.85;
    filter: grayscale(1);
    transition: opacity 0.15s ease, filter 0.15s ease;
}
.bs-landing-sponsors .bs-landing-cms-body a:hover img {
    opacity: 1;
    filter: grayscale(0);
}

/* Auto-linked "coming up" list under the Kidz Battles content section. Each
   item deep-links to /trial with the session id pre-selected. */
.bs-landing-upcoming-battles { margin-top: 1.25rem; }
.bs-landing-upcoming-head {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}
.bs-landing-upcoming-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.5rem;
}
.bs-landing-upcoming-item a {
    display: grid;
    grid-template-columns: minmax(120px, auto) 1fr;
    gap: 0.85rem;
    align-items: center;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-input);
    text-decoration: none;
    color: var(--text);
    transition: transform 0.15s ease, border-color 0.15s ease;
}
.bs-landing-upcoming-item a:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
}
.bs-landing-upcoming-date {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 0.85rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.bs-landing-upcoming-name {
    font-weight: 600;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bs-landing-upcoming-time {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 0.8rem;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.bs-landing-anxiety-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1rem;
}
.bs-landing-anxiety-card {
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 1.5rem;
    display: grid;
    gap: 0.75rem;
    transition: border-color 0.15s ease, transform 0.15s ease;
}
.bs-landing-anxiety-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.bs-landing-anxiety-q {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--text);
    letter-spacing: -0.005em;
    line-height: 1.25;
}
.bs-landing-anxiety-a {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.55;
}

.bs-landing-where {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}
.bs-landing-where-card {
    display: grid;
    grid-template-columns: 24px 1fr;
    gap: 1rem;
    padding: 1.25rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
}
.bs-landing-where-card .mud-icon-root { color: var(--accent); margin-top: 3px; }
.bs-landing-where-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text);
    margin-bottom: 4px;
}
.bs-landing-where-value {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
}
.bs-landing-where-link {
    display: inline-block;
    margin-top: 0.5rem;
    color: var(--accent);
    font-weight: 600;
    font-size: 0.9rem;
    text-decoration: none;
}
.bs-landing-where-link:hover { text-decoration: underline; }

.bs-landing-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-top: 2rem;
    border-top: 1px solid var(--surface-raised);
    gap: 1.5rem;
    flex-wrap: wrap;
}
.bs-landing-footer-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.bs-landing-footer-mark {
    width: 40px;
    height: 40px;
    border-radius: 8px;
}
.bs-landing-footer-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--text);
}
.bs-landing-footer-line {
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-top: 2px;
}
.bs-landing-footer-nav {
    display: flex;
    gap: 1.25rem;
    flex-wrap: wrap;
}
.bs-landing-footer-nav a {
    color: var(--text-muted);
    font-size: 0.9rem;
    text-decoration: none;
    transition: color 0.15s ease;
}
.bs-landing-footer-nav a:hover { color: var(--accent); }

/* Social icon strip in the landing footer - admin writes <a>[<img>]</a> markup;
   we style it as a horizontal row and mute logo-style images at rest. */
.bs-landing-footer-social {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1rem;
}
.bs-landing-footer-social a {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    text-decoration: none;
    transition: color 0.15s ease;
}
.bs-landing-footer-social a:hover { color: var(--accent); }
.bs-landing-footer-social img {
    max-height: 22px;
    width: auto;
    opacity: 0.75;
    filter: grayscale(1);
    transition: opacity 0.15s ease, filter 0.15s ease;
}
.bs-landing-footer-social a:hover img {
    opacity: 1;
    filter: grayscale(0);
}

/* Trial-highlighted list card (used by the admin registrations queue) */
.bs-list-card.is-trial {
    border-left: 3px solid var(--accent);
    background: linear-gradient(90deg, rgba(245, 36, 40, 0.05), transparent 40%);
}
.bs-list-card-trial-detail {
    margin-top: 0.5rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    color: var(--text);
    font-size: 0.85rem;
}
.bs-list-card-trial-detail .dot { color: var(--surface-raised); }
.bs-list-card-trial-label {
    color: var(--text-muted);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}
.bs-list-card-trial-link {
    font-family: 'JetBrains Mono', monospace;
    color: var(--accent);
    font-size: 0.82rem;
    text-decoration: none;
}
.bs-list-card-trial-link:hover { text-decoration: underline; }
.bs-list-card-trial-flex {
    color: var(--text-muted);
    font-style: italic;
    font-size: 0.85rem;
}

/* "Keep me signed in" checkbox row. Left-aligns with the fields above so
   the tap target lands where the eye already is; margin-top nudges it away
   from the last input without adding a full 16px gap. */
.bs-auth-remember {
    margin-top: 0.75rem;
    display: flex;
    align-items: center;
}
.bs-auth-remember .mud-checkbox .mud-typography {
    font-size: 0.9rem;
    color: var(--text-muted);
}

/* Forgot-password link + confirmation note on the auth shell */
.bs-auth-forgot {
    margin-top: 0.75rem;
    text-align: right;
    font-size: 0.82rem;
}
.bs-auth-forgot a {
    color: var(--text-muted);
    text-decoration: none;
    transition: color 0.15s ease;
}
.bs-auth-forgot a:hover { color: var(--accent); text-decoration: underline; }

.bs-forgot-note {
    margin-top: 1.25rem;
    padding: 0.85rem 1rem;
    background: rgba(140, 148, 143, 0.08);
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.4;
    border-radius: var(--radius-input);
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
}
.bs-forgot-note .mud-icon-root { color: var(--text-muted); margin-top: 2px; flex-shrink: 0; }

/* -------- Password checklist ---------------------------------------------
   Rendered under a password field so the member can see, live, which of the
   four Identity rules (length, lowercase, uppercase, digit) their input has
   already passed. Muted dot for pending, --success tick for met. Kept as a
   flat list with generous line-height so a 15px-body reader can scan it in
   one glance without confusing "0 met" with "no requirements".
*/
.bs-pwd-checklist {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0.75rem 0.9rem;
    background: var(--surface-raised);
    border-radius: var(--radius-input);
    display: grid;
    gap: 0.35rem;
    font-size: 0.85rem;
}
.bs-pwd-checklist li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    line-height: 1.35;
    transition: color var(--dur-fast) var(--ease);
}
.bs-pwd-checklist li.is-pending { color: var(--text-muted); }
.bs-pwd-checklist li.is-met { color: var(--success); }
.bs-pwd-checklist li .mud-icon-root {
    width: 18px;
    height: 18px;
    font-size: 18px;
    flex-shrink: 0;
}
.bs-pwd-checklist li.is-pending .mud-icon-root { color: var(--text-muted); }
.bs-pwd-checklist li.is-met .mud-icon-root { color: var(--success); }

/* -------- /trial public page (spec §7 Gap 5) -------- */
/* -------- Trial-request page ---------------------------------------------
   Public-facing, no auth. Structured as a numbered 4-step scaffold so the
   form reads as a guided flow instead of a bureaucratic checklist. Hero
   picks up landing's typographic weight (Manrope 800, tight tracking) so
   /trial reads as a natural extension of / rather than an afterthought.
   Cards use border-only elevation (§4) and reserve --accent for picked
   state, focus rings, and the primary CTA only (§2 - 6%-of-pixels rule).
*/

.bs-trial {
    max-width: 820px;
    margin: 0 auto;
    padding: 2rem 1.25rem 4rem;
    display: grid;
    gap: 2.5rem;
}

/* Hero. Bigger title ceiling than the old 3rem cap - trial is a real
   surface, not a form on a page. Tight rhythm: eyebrow → title → sub. */
.bs-trial-hero { display: grid; gap: 0.5rem; }
.bs-trial-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(2.25rem, 5.5vw, 3.75rem);
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1.05;
    margin: 0.5rem 0 0;
    max-width: 20ch;
}
.bs-trial-sub {
    color: var(--text-muted);
    font-size: clamp(1rem, 1.15vw, 1.125rem);
    line-height: 1.55;
    max-width: 58ch;
    margin: 0.75rem 0 0;
}

.bs-trial-form-shell {
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    padding: clamp(1.5rem, 3vw, 2.25rem);
}
.bs-trial-form { display: grid; gap: 2rem; }

/* Each field is a numbered step. The step-num pill sits in a fixed 40px
   column so the field titles all line up on the same left edge, giving
   the form a visible spine down the left side without adding chrome. */
.bs-trial-field { display: grid; gap: 0.85rem; }
.bs-trial-field-head {
    display: grid;
    grid-template-columns: 40px 1fr;
    gap: 0.9rem;
    align-items: start;
}
.bs-trial-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-pill);
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    /* Smooth flip from muted-outline to accent-filled as the step completes.
       Spec §2 permits accent for live/state signalling - this IS that. */
    transition: background var(--dur-base) var(--ease),
                border-color var(--dur-base) var(--ease),
                color var(--dur-base) var(--ease),
                transform var(--dur-base) var(--ease);
}
.bs-trial-step-num.is-done {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    /* Subtle scale on the transition so the flip has a tiny "landing"
       moment - reads as tactile without becoming Duolingo. */
    transform: scale(1.04);
}
.bs-trial-step-num.is-done .mud-icon-root {
    color: var(--accent-ink);
    font-size: 18px !important;
}
@media (prefers-reduced-motion: reduce) {
    .bs-trial-step-num { transition: none; }
    .bs-trial-step-num.is-done { transform: none; }
}
.bs-trial-field-head-body { display: grid; gap: 0.25rem; }
.bs-trial-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.125rem;
    color: var(--text);
    letter-spacing: -0.01em;
    line-height: 1.25;
}
.bs-trial-hint {
    color: var(--text-muted);
    font-size: 0.875rem;
    line-height: 1.5;
}
.bs-trial-input {
    width: 100%;
    padding: 0.85rem 1rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--text);
    font: inherit;
    font-size: 1rem;
    transition: border-color var(--dur-fast) var(--ease);
}
.bs-trial-input::placeholder { color: var(--text-muted); opacity: 0.7; }
.bs-trial-input:hover { border-color: color-mix(in srgb, var(--surface-raised) 55%, var(--text-muted)); }
.bs-trial-input:focus-visible {
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Sub-field: optional email under the required phone in step 04. Indented
   under the step-num column so it visibly nests, with a quieter label. */
.bs-trial-subfield {
    display: grid;
    gap: 0.4rem;
    margin-top: 0.25rem;
}
.bs-trial-sublabel {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text);
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
}
.bs-trial-optional {
    font-weight: 400;
    font-size: 0.78rem;
    color: var(--text-muted);
    letter-spacing: 0.005em;
}

/* Session picker. Two-column-ish grid, dense on desktop, single column on
   mobile. No accent-tinted background on picked state - just an accent
   border + a filled left rail (spec §2 - accent is 6% of pixels). */
.bs-trial-session-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 0.6rem;
}
.bs-trial-session {
    display: grid;
    gap: 4px;
    padding: 0.9rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--text);
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}
.bs-trial-session:hover {
    border-color: color-mix(in srgb, var(--surface-raised) 40%, var(--text-muted));
    transform: translateY(-1px);
}
.bs-trial-session:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-trial-session:active { transform: scale(0.985); }
.bs-trial-session.is-picked {
    border-color: var(--accent);
    border-left-color: var(--accent);
}
/* Session-shaped skeleton for the pre-fetch state - matches the real card
   metrics so the layout doesn't shift when the fetch resolves (spec §9). */
.bs-trial-session-skel {
    cursor: default;
    pointer-events: none;
    background: var(--surface);
    border-left-color: var(--surface-raised);
}
.bs-trial-session-skel:hover {
    transform: none;
    border-color: var(--surface-raised);
}
.bs-trial-session-when {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-muted);
    margin-bottom: 2px;
}
.bs-trial-session-time {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500;
    font-size: 1.05rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.bs-trial-session-name {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 500;
    font-size: 0.85rem;
    color: var(--text-muted);
    line-height: 1.35;
}
.bs-trial-session.is-picked .bs-trial-session-name { color: var(--text); }
.bs-trial-flex-link {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    text-align: left;
    padding: 0.5rem 0.25rem;
    cursor: pointer;
    justify-self: start;
    border-radius: 4px;
    transition: color var(--dur-fast) var(--ease);
}
.bs-trial-flex-link:hover { color: var(--text); text-decoration: underline; }
.bs-trial-flex-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    color: var(--text);
}

/* Experience cards. Fixed: previous minmax(180px, 1fr) forced a 2+1 wrap
   on mid-width viewports where the third card orphaned; different desc
   lengths also made rows uneven. Now: 3 columns above 640px (never
   orphans), 1 column below (never overflows). Uniform min-height keeps
   the row visually even regardless of Greek vs English description
   length differences. */
.bs-trial-experience {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.6rem;
}
@media (min-width: 640px) {
    .bs-trial-experience { grid-template-columns: repeat(3, 1fr); }
}
.bs-trial-exp {
    display: grid;
    gap: 0.35rem;
    align-content: start;
    min-height: 96px;
    padding: 1rem 1.1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    text-align: left;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}
.bs-trial-exp:hover {
    border-color: color-mix(in srgb, var(--surface-raised) 40%, var(--text-muted));
    transform: translateY(-1px);
}
.bs-trial-exp:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-trial-exp:active { transform: scale(0.985); }
.bs-trial-exp.is-picked {
    border-color: var(--accent);
    box-shadow: inset 3px 0 0 0 var(--accent);
    padding-left: calc(1.1rem + 3px);
}
@media (prefers-reduced-motion: reduce) {
    .bs-trial-session, .bs-trial-exp { transition: none; }
    .bs-trial-session:hover, .bs-trial-exp:hover { transform: none; }
    .bs-trial-session:active, .bs-trial-exp:active { transform: none; }
}
.bs-trial-exp-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1rem;
    color: var(--text);
    letter-spacing: -0.01em;
}
.bs-trial-exp-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

/* Submit row: primary CTA sits left, privacy note underneath on mobile,
   beside on wider viewports. The button uses --accent (§2 - the single
   accent-filled control per screen). */
.bs-trial-submit-row {
    display: grid;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.5rem;
}
@media (min-width: 640px) {
    .bs-trial-submit-row {
        grid-template-columns: auto 1fr;
        gap: 1.25rem;
    }
}
.bs-trial-submit {
    padding: 0.95rem 1.75rem;
    background: var(--accent);
    color: var(--accent-ink);
    border: 0;
    border-radius: var(--radius-pill);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.005em;
    cursor: pointer;
    justify-self: start;
    min-width: 180px;
    transition: transform var(--dur-fast) var(--ease),
                filter var(--dur-fast) var(--ease);
}
.bs-trial-submit:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.08); }
.bs-trial-submit:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.bs-trial-submit:disabled { opacity: 0.5; cursor: not-allowed; }
.bs-trial-privacy-note {
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.5;
    max-width: 40ch;
}

/* Marketing opt-in row - keeps checkbox anchored to the label, generous 44x44
   tap target, muted description underneath. Deliberately quiet: not accented,
   the primary action stays the submit button. */
.bs-trial-consent-row {
    margin-top: 0.5rem;
}
.bs-trial-consent {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    padding: 0.75rem 0.5rem;
    cursor: pointer;
    border-radius: var(--radius-input);
    transition: background var(--dur-fast) var(--ease);
}
.bs-trial-consent:hover { background: var(--surface-raised); }
.bs-trial-consent input[type="checkbox"] {
    margin: 0.15rem 0 0;
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--accent);
    cursor: pointer;
    flex-shrink: 0;
}
/* Spec §8: keyboard focus ring — 2 px accent, 2 px offset. */
.bs-trial-consent input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 3px;
}
/* Ring also visible around the wrapping label so it's clear which control
   has focus even when the checkbox is small. */
.bs-trial-consent:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Global MudCheckBox spec §8 focus ring — MudBlazor's default is a subtle
   ripple that fades; §8 requires the visible 2 px accent ring at 2 px offset
   for keyboard reachability. Applied at the input element so the ring
   tightly wraps the box. */
.mud-checkbox input[type="checkbox"]:focus-visible + span,
.mud-checkbox input[type="checkbox"]:focus-visible ~ span {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Global MudSwitch spec §8 focus ring — the switch track is what gets the
   ring since it's the visual anchor. MudSwitch's own ripple is 40 px wide
   and hides the state; §8 wants a tight 2 px outline so keyboard users see
   exactly which control has focus. */
.mud-switch input:focus-visible ~ .mud-switch-track,
.mud-switch input:focus-visible ~ .mud-switch-base {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 999px;
}

/* Category rows on /profile/notifications. Padding-left aligns the description
   with the label text of the MudSwitch above without hardcoding the switch's
   pixel width (previously margin-left:52px, brittle across MudBlazor releases). */
.bs-notif-category {
    display: block;
    padding-block: 0.35rem 0.6rem;
}
.bs-notif-category-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.5;
    padding-left: 3.25rem;
    margin-top: 2px;
}

/* Admin announcement dialog — collapsible English translation section.
   Reads as clearly optional rather than as two extra required fields. */
.bs-announcement-english {
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-input);
    padding: 0.75rem 1rem;
}
.bs-announcement-english[open] {
    padding-bottom: 1rem;
}
.bs-announcement-english > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
    user-select: none;
}
.bs-announcement-english > summary::-webkit-details-marker { display: none; }
.bs-announcement-english > summary::before {
    content: "▸";
    color: var(--text-muted);
    font-size: 0.75rem;
    transition: transform var(--dur-fast) var(--ease);
}
.bs-announcement-english[open] > summary::before {
    transform: rotate(90deg);
}
.bs-announcement-english > summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}
.bs-trial-consent-label {
    display: block;
    font-weight: 500;
    color: var(--text);
    font-size: 0.95rem;
}
.bs-trial-consent-desc {
    display: block;
    margin-top: 2px;
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.45;
}

/* Inbox list. Card-shaped rows with an icon rail, subject + preview column,
   and a right-aligned monospace timestamp. Mirrors the visual rhythm of
   .bs-profile-row so members feel oriented moving between the two lists. */
.bs-inbox-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 0.5rem;
}
.bs-inbox-row {
    display: grid;
    grid-template-columns: 40px 1fr auto;
    gap: 0.75rem;
    align-items: start;
    padding: 0.75rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    min-height: 60px;
}
.bs-inbox-icon {
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    padding-top: 2px;
}
.bs-inbox-body {
    min-width: 0;
}
.bs-inbox-subject {
    font-weight: 600;
    color: var(--text);
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.bs-inbox-preview {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.4;
    margin-top: 4px;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.bs-inbox-when {
    color: var(--text-muted);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    padding-top: 2px;
    white-space: nowrap;
}
.bs-inbox-skel-stack {
    display: grid;
    gap: 0.5rem;
}
.bs-inbox-skel {
    height: 68px;
    background: linear-gradient(90deg,
        var(--surface) 0%,
        var(--surface-raised) 50%,
        var(--surface) 100%);
    background-size: 200% 100%;
    border-radius: var(--radius-input);
    animation: bs-skel-shimmer 1.4s var(--ease) infinite;
}
@keyframes bs-skel-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}
/* Spec §8: honour prefers-reduced-motion. Static muted surface reads as
   "loading" without the infinite shimmer that triggers vestibular sensitivity. */
@media (prefers-reduced-motion: reduce) {
    .bs-inbox-skel {
        animation: none;
        background: var(--surface-raised);
    }
}

/* Confirm state. Success rail on the left, checkmark eyebrow, meta list
   with tabular labels. Keeps the same rhythm as the form so submission
   feels like the same page relaxing, not a redirect. */
.bs-trial-confirm {
    background: var(--surface);
    border: var(--border);
    border-left: 3px solid var(--success);
    border-radius: var(--radius-card);
    padding: clamp(1.75rem, 3vw, 2.5rem);
    display: grid;
    gap: 1rem;
}
.bs-trial-confirm-mark {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--success);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
}
.bs-trial-confirm-mark .mud-icon-root {
    color: var(--success);
    font-size: 20px !important;
}
.bs-trial-confirm-sub {
    color: var(--text-muted);
    font-size: 1.05rem;
    line-height: 1.6;
    max-width: 60ch;
    margin: 0;
}
.bs-trial-confirm-list {
    display: grid;
    gap: 0;
    margin: 1rem 0 0.5rem;
    border-top: 1px dashed var(--surface-raised);
}
.bs-trial-confirm-item {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 1rem;
    align-items: baseline;
    padding: 0.9rem 0;
    border-bottom: 1px dashed var(--surface-raised);
}
.bs-trial-confirm-label {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 700;
    color: var(--text-muted);
}
.bs-trial-confirm-cta-row {
    display: flex;
    justify-content: flex-start;
    margin-top: 1rem;
}

.bs-trial-anxiety { margin-top: 0; }

/* -------- Admin dashboard (Home.razor rebranded to /admin) tiles + pending queue -------- */
.bs-admin-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
}
.bs-admin-dashboard-section {
    display: grid;
    gap: 0.75rem;
}
.bs-admin-pending-list {
    display: grid;
    gap: 0.5rem;
}
.bs-admin-pending-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 1.25rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-input);
    color: var(--text);
    text-decoration: none;
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}
.bs-admin-pending-row:hover { transform: translateY(-1px); border-color: var(--accent); }
.bs-admin-pending-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.95rem;
}
.bs-admin-pending-meta {
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-top: 2px;
}

/* -------- Segmented control (Upcoming/Past on /my-bookings, etc.) -------- */
.bs-segmented {
    display: inline-flex;
    padding: 3px;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
    gap: 0.2rem;
}
.bs-segmented-btn {
    background: transparent;
    color: var(--text-muted);
    border: none;
    padding: 6px 16px;
    border-radius: var(--radius-pill);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    transition: background 0.15s ease, color 0.15s ease;
}
.bs-segmented-btn:hover:not(.is-active) { color: var(--text); }
.bs-segmented-btn.is-active {
    background: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
}
.bs-segmented-count {
    background: rgba(255, 255, 255, 0.18);
    color: var(--accent-ink);
    font-size: 0.7rem;
    font-weight: 600;
    padding: 1px 6px;
    border-radius: var(--radius-pill);
    min-width: 20px;
    text-align: center;
}
.bs-segmented-btn:not(.is-active) .bs-segmented-count {
    background: var(--surface-raised);
    color: var(--text-muted);
}

.bs-booking-group-head {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 600;
    padding: 1rem 0 0.5rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.bs-booking-group-head::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--surface-raised);
}

/* -------- News list + detail (spec §3.4) -------- */
.bs-news-section {
    display: grid;
    gap: 0.4rem;
    margin-top: 0.5rem;
}
.bs-news-divider {
    height: 1px;
    background: var(--surface-raised);
    margin: 1rem 0;
}
.bs-news-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 1rem;
    align-items: flex-start;
    padding: 1rem 1.25rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    color: var(--text);
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
    width: 100%;
}
.bs-news-row:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
}
.bs-news-row.is-pinned { border-left: 3px solid var(--accent); }
.bs-news-unread {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--accent);
    margin-top: 0.55rem;
    flex-shrink: 0;
}
.bs-news-row:not(.is-unread) .bs-news-unread { display: none; }
.bs-news-body { min-width: 0; display: grid; gap: 0.25rem; }
.bs-news-title-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.bs-news-pin { color: var(--accent); }
.bs-news-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 600;
    font-size: 1.05rem;
    line-height: 1.25;
    letter-spacing: -0.005em;
    color: var(--text);
}
.bs-news-row.is-unread .bs-news-title { font-weight: 700; }
.bs-news-preview {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.4;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.bs-news-date {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}
.bs-news-caret {
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
    align-self: center;
    transition: color 0.15s ease, transform 0.15s ease;
}
.bs-news-row:hover .bs-news-caret { color: var(--accent); transform: translateX(2px); }

/* Detail page */
.bs-news-detail {
    max-width: 720px;
    display: grid;
    gap: 0.75rem;
}
.bs-news-article {
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 2rem 2.25rem;
}
.bs-news-pinned-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--accent);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 0.5rem;
}
.bs-news-article-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin: 0.25rem 0 0.5rem;
}
.bs-news-article-date {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-bottom: 1.5rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid var(--surface-raised);
}
.bs-news-article-body {
    color: var(--text);
    font-size: 1rem;
    line-height: 1.7;
    white-space: pre-wrap;
    max-width: 65ch;
}

/* -------- Booking timeline card (used by /my-bookings) -------- */
.bs-booking-card {
    display: grid;
    grid-template-columns: 72px 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--success);
    transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
    margin-bottom: 0.75rem;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    width: 100%;
}
.bs-booking-card:hover:not(:disabled) {
    transform: translateY(-1px);
    border-color: var(--accent);
}
.bs-booking-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-booking-card.is-today { border-left-color: var(--accent); background: rgba(245, 36, 40, 0.04); }
.bs-booking-card.is-waitlisted { border-left-color: var(--warning); border-left-style: dashed; }
.bs-booking-card.is-past { opacity: 0.7; border-left-color: var(--surface-raised); }
.bs-booking-card.is-cancelled { opacity: 0.55; border-left-color: var(--danger); border-left-style: dashed; }
.bs-booking-card.is-cancelled .bs-booking-class { text-decoration: line-through; }
.bs-booking-day-chip {
    display: inline-block;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    margin-left: 0.5rem;
    vertical-align: middle;
}
.bs-booking-day-chip.is-tomorrow { background: var(--warning); color: var(--bg); }
/* "For Anna" chip when the booking is on behalf of a dependent - muted so it
   sits below the day-of-week chip, still legible at a glance. */
.bs-booking-for-chip {
    display: inline-block;
    background: var(--surface-raised);
    color: var(--text);
    font-size: 0.7rem;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    margin-left: 0.5rem;
    vertical-align: middle;
}
.bs-booking-time {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500;
    color: var(--text);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    font-variant-numeric: tabular-nums;
}
.bs-booking-status-row {
    margin-top: 8px;
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bs-booking-cancel-hint {
    display: block;
    color: var(--text-muted);
    font-size: 0.7rem;
    letter-spacing: 0.02em;
    font-family: 'JetBrains Mono', monospace;
    font-variant-numeric: tabular-nums;
    text-align: right;
    margin-bottom: 2px;
}
.bs-booking-cancel-locked {
    color: var(--text-muted);
    font-size: 0.78rem;
    font-style: italic;
    max-width: 160px;
    text-align: right;
    line-height: 1.3;
}

.bs-date-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    padding: 8px 0;
    line-height: 1;
}
.bs-date-badge-day {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.5rem, 1.8vw, 1.875rem);
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.02em;
}
.bs-date-badge-month {
    font-size: clamp(0.6875rem, 0.75vw, 0.8125rem);
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    margin-top: 2px;
}

.bs-booking-body { min-width: 0; }
.bs-booking-class {
    font-weight: 600;
    color: var(--text);
    /* Booking title (m96): 16→19px so the class name reads confidently
       inside a widened booking card on 2K. */
    font-size: clamp(1rem, 1.1vw, 1.1875rem);
    line-height: 1.3;
}
.bs-booking-meta {
    color: var(--text-muted);
    font-size: clamp(0.85rem, 0.9vw, 0.9375rem);
    line-height: 1.45;
    margin-top: 3px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.bs-booking-meta .dot { color: var(--surface-raised); }

.bs-booking-action {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

@media (max-width: 479.98px) {
    .bs-booking-card { grid-template-columns: 56px 1fr; }
    .bs-booking-action {
        grid-column: 1 / -1;
        justify-content: flex-start;
        border-top: 1px dashed var(--surface-raised);
        padding-top: 0.75rem;
    }
    .bs-date-badge-day { font-size: 1.2rem; }
}

/* -------- Announcement card -------- */
.bs-announcement-card {
    position: relative;
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    margin-bottom: 1rem;
    transition: border-color 0.15s ease;
}
.bs-announcement-card:hover { border-color: var(--accent); }
/* Pinned card: 2px accent border marks it, no glow drop-shadow. */
.bs-announcement-card.is-pinned {
    border-color: var(--accent);
    border-width: 2px;
}
.bs-announcement-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}
.bs-announcement-title {
    font-family: 'Manrope', system-ui, sans-serif;
    /* Announcement headline (m96): 18→24px so News reads as a proper feed
       on 2K instead of settings-list density. */
    font-size: clamp(1.125rem, 1.4vw, 1.5rem);
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
    margin: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.bs-announcement-pin {
    color: var(--accent);
    font-size: 16px;
}
.bs-announcement-date {
    color: var(--text-muted);
    font-size: clamp(0.8125rem, 0.85vw, 0.9375rem);
    letter-spacing: 0.04em;
}
.bs-announcement-body {
    color: var(--text);
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.6;
    white-space: pre-wrap;
}

/* -------- Stat tile (admin dashboard) -------- */
.bs-stat-tile {
    padding: 1.75rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid var(--surface-raised);
    transition: border-color 0.15s ease, transform 0.15s ease, background 0.15s ease;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-height: 148px;
    text-decoration: none;
    color: var(--text);
    position: relative;
    overflow: hidden;
}
.bs-stat-tile::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(360px 240px at 100% 0%, rgba(245, 36, 40, 0.05), transparent 60%);
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
}
.bs-stat-tile:hover {
    transform: translateY(-2px);
    border-color: var(--accent);
    border-left-color: var(--accent);
}
.bs-stat-tile:hover::after { opacity: 1; }
.bs-stat-eyebrow {
    color: var(--text-muted);
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(0.6875rem, 0.75vw, 0.8125rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    line-height: 1;
    position: relative;
    z-index: 1;
}
.bs-stat-value {
    font-family: 'Manrope', system-ui, sans-serif;
    /* Dashboard stat (m96): 52→80px so tile numerals carry weight on a
       wide admin dashboard, not sit lonely in a 200px column. */
    font-size: clamp(2.75rem, 4vw, 5rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1;
    margin: 0.75rem 0 0.5rem;
    color: var(--text);
    position: relative;
    z-index: 1;
}
/* Dashboard stat numerals stay in --text - the previous gradient on ::first-letter
   put the accent on all three tile numbers at once, breaking §2 "one accent per
   screen" and reading as gamification bling forbidden by §1. */
.bs-stat-sub {
    color: var(--text-muted);
    font-size: clamp(0.875rem, 0.95vw, 1rem);
    line-height: 1.5;
    margin: 0;
    position: relative;
    z-index: 1;
}

/* -------- Shortcut card (Home member view) -------- */
.bs-shortcut {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease;
    height: 100%;
}
.bs-shortcut:hover { transform: translateY(-2px); border-color: var(--accent); }
.bs-shortcut-icon {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    /* Solid surface-raised - was accent-to-purple gradient with an off-palette
       purple. Icon glyph carries the accent tint on its own. */
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);
    flex-shrink: 0;
}
.bs-shortcut-title {
    font-weight: 600;
    font-size: clamp(0.9375rem, 1.05vw, 1.125rem);
    line-height: 1.3;
}
.bs-shortcut-sub {
    color: var(--text-muted);
    font-size: clamp(0.8125rem, 0.9vw, 0.9375rem);
    line-height: 1.4;
    margin-top: 4px;
}

/* -------- Next-booking hero (member Home) -------- */
.bs-next-booking {
    background: var(--gradient-hero);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 1.75rem 2rem;
    margin-bottom: 2rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    flex-wrap: wrap;
}
.bs-next-booking-eyebrow {
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    margin-bottom: 0.5rem;
}
.bs-next-booking-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.5rem, 3vw, 2rem);
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--text);
    line-height: 1.1;
    margin-bottom: 0.5rem;
}
.bs-next-booking-when {
    color: var(--text-muted);
    font-size: 0.95rem;
    font-weight: 500;
}

/* -------- Subscription hero (My subscription) -------- */
.bs-sub-hero {
    background: var(--gradient-hero);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    padding: 2rem 2.25rem;
    margin-bottom: 2rem;
}
/* All hero text uses --text / --text-muted tokens - the previous hardcoded
   #ffffff / rgba(255,255,255,…) values rendered invisible in light theme
   (near-white text on a near-white surface). */
.bs-sub-hero-plan {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--text);
    line-height: 1.1;
    margin: 0.25rem 0 0.75rem;
}
.bs-sub-hero-dates {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-bottom: 1.25rem;
}
.bs-sub-hero-days {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    color: var(--text);
}
.bs-sub-hero-days-num {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 3.5rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1;
}
.bs-sub-hero-days-label {
    color: var(--text-muted);
    font-size: 0.9rem;
}

/* -------- Admin: filter bar (search + selects above a table) -------- */
.bs-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    margin-bottom: 1rem;
}
.bs-filter-bar .mud-input-control {
    margin-top: 0 !important;
}
.bs-filter-bar-spacer { flex: 1; }

/* -------- Admin: polished table (applied via Class="bs-admin-table") -------- */
.bs-admin-table.mud-table {
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    overflow: hidden;
}
.bs-admin-table .mud-table-container { background: transparent; }
.bs-admin-table .mud-table-toolbar { background: transparent; }
.bs-admin-table .mud-table-head .mud-table-cell {
    background: rgba(16, 19, 18, 0.5);
    color: var(--text-muted);
    font-weight: 700;
    font-size: clamp(0.6875rem, 0.75vw, 0.8125rem);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    padding: 14px 18px;
    border-bottom: 1px solid var(--surface-raised);
}
.bs-admin-table .mud-table-body .mud-table-row {
    transition: background 0.15s ease;
}
.bs-admin-table .mud-table-body .mud-table-row:hover {
    background: rgba(245, 36, 40, 0.04);
}
.bs-admin-table .mud-table-body .mud-table-cell {
    padding: 16px 18px;
    border-bottom: 1px solid var(--surface-raised);
    color: var(--text);
    /* Admin table row (m96): 14→16px so dense admin data reads on 2K,
       not stuck at a caption size when there's room for real body text. */
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.4;
}
.bs-admin-table .mud-table-body .mud-table-row:last-child .mud-table-cell {
    border-bottom: none;
}

/* Icon action stack at the right end of a table row. */
.bs-inline-actions {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    justify-content: flex-end;
}
.bs-inline-actions .mud-icon-button {
    transition: background 0.15s ease, color 0.15s ease;
}

/* Small helper text under an admin form field. */
.bs-form-hint {
    color: var(--text-muted);
    font-size: 0.78rem;
    margin-top: 0.35rem;
    line-height: 1.5;
}

/* "For Anna" sub-line under a plan name in the admin subscriptions table. */
.bs-admin-sub-forwho {
    color: var(--text-muted);
    font-size: 0.75rem;
    margin-top: 2px;
}

/* -------- Admin: form-actions row (right-aligned cancel + primary) -------- */
.bs-form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 2rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--surface-raised);
}
@media (max-width: 479.98px) {
    .bs-form-actions {
        flex-direction: column-reverse;    /* primary on top on mobile */
        align-items: stretch;
    }
    .bs-form-actions > * { width: 100%; }
}

/* -------- Admin: dialog header/actions rhythm -------- */
.bs-dialog-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0;
    line-height: 1.2;
}
.bs-dialog-sub {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0.25rem 0 0;
    line-height: 1.4;
}
.mud-dialog { border: 1px solid var(--surface-raised); }
.mud-dialog-title {
    padding: 1.25rem 1.5rem 0.75rem;
    border-bottom: 1px solid var(--surface-raised);
}
.mud-dialog-content { padding: 1.5rem !important; }
.mud-dialog-actions {
    padding: 1rem 1.5rem !important;
    border-top: 1px solid var(--surface-raised);
    background: rgba(16, 19, 18, 0.4);
}

/* -------- Admin: card variants for small lists (plans/instructors/registrations) -------- */
.bs-list-card {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    margin-bottom: 0.75rem;
    transition: border-color 0.15s ease;
}
.bs-list-card:hover { border-color: rgba(245, 36, 40, 0.35); }
.bs-list-card.is-inactive { opacity: 0.6; }
.bs-list-card-title {
    font-weight: 600;
    color: var(--text);
    font-size: clamp(1rem, 1.1vw, 1.1875rem);
    line-height: 1.3;
}
.bs-list-card-meta {
    color: var(--text-muted);
    font-size: clamp(0.85rem, 0.9vw, 0.9375rem);
    line-height: 1.4;
    margin-top: 4px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.bs-list-card-meta .dot { color: var(--surface-raised); }
.bs-list-card-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

/* Expenses page - total card sits above the log, with a big monospace amount
   so the range's number is unmissable at a glance. Row amounts also use the
   mono treatment so scanning down the list stays aligned. */
.bs-expenses-total {
    border-left: 3px solid var(--accent);
}
.bs-expenses-total-value {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: 1.75rem;
    letter-spacing: -0.01em;
    color: var(--text);
    margin-top: 4px;
    font-variant-numeric: tabular-nums;
}
.bs-expenses-amount {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.bs-expenses-reason {
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 32ch;
}

/* -------- Achievements: badge tier puck + admin editor -------- */
/* Bronze / silver / gold puck behind the badge icon — the ONE deliberate
   exception to §1's "no metallic medals". Not shiny chrome, not Duolingo
   gloss; a matte tier-tint (18% diagonal patina) + a solid ring stroke in
   the tier hue, over the same --surface-raised puck the rest of the app
   uses. Reads as "aged bronze / gunmetal silver / patinated gold" — a
   category signal that scans at a glance without going trophy-cabinet. */
.bs-badge-tierpuck {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    box-sizing: border-box;
    background: var(--surface-raised);
    color: var(--text-muted);
    border: 2px solid transparent;
    /* Positioned so absolute-positioned children (mint-mark date on the
       XL front face) can anchor inside the ring without shifting the
       icon out of centre. */
    position: relative;
    /* Room to breathe on hover / focus surfaces without a shadow */
    transition: transform var(--dur-fast) var(--ease);
}
.bs-badge-tierpuck .mud-icon-root { font-size: 22px; }

/* Per-tier tint. Only --tier-hue is set per tier; the shared rule below
   picks it up for the diagonal patina, the ring stroke, and the icon
   colour. Gold gets a slightly thicker ring (2.5px vs 2px) so the eye
   reads it as the loudest of the three without needing a second cue. */
.bs-badge-tierpuck.is-bronze { --tier-hue: var(--tier-bronze); }
.bs-badge-tierpuck.is-silver { --tier-hue: var(--tier-silver); }
.bs-badge-tierpuck.is-gold {
    --tier-hue: var(--tier-gold);
    border-width: 2.5px;
}
.bs-badge-tierpuck.is-bronze,
.bs-badge-tierpuck.is-silver,
.bs-badge-tierpuck.is-gold {
    /* Two stacked backgrounds: a diagonal tier-tint (18% at top-left,
       fading to transparent by 60%) sits on top of the base surface.
       No gradients on the surface itself — matte, not chrome. */
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--tier-hue) 18%, transparent) 0%,
            transparent 60%),
        var(--surface-raised);
    border-color: var(--tier-hue);
    color: var(--tier-hue);
}

/* Slug rendered as a small code pill so it reads as identity, not copy. */
.bs-badge-slug {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 0.8rem;
    padding: 2px 6px;
    background: var(--surface-raised);
    border-radius: 6px;
    color: var(--text);
}

/* Bilingual field pair - side-by-side on desktop, stacked on narrow. */
.bs-badge-bilingual {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
    margin-top: 0.75rem;
}
@media (max-width: 600px) {
    .bs-badge-bilingual { grid-template-columns: 1fr; }
}

/* Larger puck for the award-dialog preview and the badge-detail dialog.
   Ring scales up a touch so the tier read stays proportional to the puck
   (2px→3px small→large; gold keeps its half-step bump). */
.bs-badge-tierpuck-lg {
    width: 72px;
    height: 72px;
    border-width: 3px;
}
.bs-badge-tierpuck-lg.is-gold { border-width: 3.5px; }
.bs-badge-tierpuck-lg .mud-icon-root { font-size: 34px; }

/* The XL puck (240px) used to render the medal face here in CSS. The
   spotlight now uses Three.js (see .bs-medal-canvas), so the XL variant
   is gone. Puck sizes still in use: 48px (base) and 72px (-lg for the
   dialog previews). */

/* -------- Badge medal spotlight (BadgeCoinShowcase overlay) --------
   Full-viewport dark backdrop with a big 3D medal centre-stage. The
   coin lives inside a perspective container; --bs-coin-rx and
   --bs-coin-ry (written by js/badge-coin.js) drive rotateX + rotateY
   on the inner transform. Pointer capture keeps the drag alive across
   the whole viewport, so a release outside the coin still fires
   pointerup on it and momentum kicks in.

   Two faces (translateZ ±8px) give the medal thin-object depth. When
   the coin is edge-on (near 90° / 270° rotation) it reads as a sliver,
   which is what a real medal looks like on-edge — no faked cylindrical
   shell needed. */
.bs-medal-spotlight {
    position: fixed;
    inset: 0;
    z-index: 1400;
    /* MudBlazor dialog uses ~1300; this sits above so the coin isn't
       trapped under a stray backdrop. */
    display: grid;
    place-items: center;
    background: rgba(0, 0, 0, 0.86);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    animation: bs-medal-fadein var(--dur-base) var(--ease);
    outline: none;
}
@keyframes bs-medal-fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.bs-medal-spotlight-close {
    position: absolute;
    top: max(16px, env(safe-area-inset-top));
    right: max(16px, env(safe-area-inset-right));
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: 50%;
    transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.bs-medal-spotlight-close:hover,
.bs-medal-spotlight-close:focus-visible {
    color: var(--text);
    background: color-mix(in srgb, var(--text) 8%, transparent);
    outline: none;
}
.bs-medal-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 32px;
    padding: 24px;
    max-width: 480px;
    text-align: center;
}
/* -------- Medal canvas (Three.js renders here) --------
   The spotlight medal is a real 3D object drawn by js/badge-coin.js
   into a WebGL <canvas> that Three.js appends to this container. We
   just reserve the layout box and let the JS side handle everything
   inside it. Fixed pixel size so the renderer picks up dimensions at
   attach time without a resize observer. */
.bs-medal-canvas {
    /* Desktop 360px feels like a "medal displayed in front of you";
       mobile 300px is still comfortable one-handed. The Three.js
       renderer reads clientWidth/clientHeight at attach time, so
       changes here propagate without any JS knob. */
    width: 360px;
    height: 360px;
    flex: 0 0 auto;
    cursor: grab;
    touch-action: none;
    margin: 8px 0;
    outline: none;
    border-radius: 50%;
    /* Focus ring for keyboard reach — the canvas itself has role="img"
       on the wrapper so screen readers announce it, and this ring
       matches every other focusable object in the spotlight. */
}
.bs-medal-canvas:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 8px;
}
.bs-medal-canvas.is-grabbing { cursor: grabbing; }
@media (max-width: 600px) {
    .bs-medal-canvas { width: 300px; height: 300px; }
}
@media (max-width: 380px) {
    /* Tiny phones — clamp so the medal never eats the meta text
       below it or the safe-area gutters on either side. */
    .bs-medal-canvas { width: 260px; height: 260px; }
}
/* Hidden source for the icon SVG — MudIcon renders inside here and the
   JS scraper grabs its outerHTML. Kept in the DOM (not display:none) so
   the SVG stays queryable; `visibility: hidden` + zero size takes it out
   of paint and layout without breaking querySelector. */
.bs-medal-icon-source {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
}
.bs-medal-meta {
    color: var(--text);
    max-width: 380px;
}
.bs-medal-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: clamp(24px, 4vw, 32px);
    letter-spacing: -0.01em;
    line-height: 1.15;
    margin: 6px 0 10px;
    color: var(--text);
}
.bs-medal-awarded {
    color: var(--text-muted);
    font-size: 14px;
}
.bs-medal-note {
    margin-top: 20px;
    padding-top: 16px;
    border-top: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
    font-size: 14px;
    line-height: 1.55;
    text-align: left;
}
.bs-medal-note p { margin: 4px 0 0; }
.bs-medal-hint {
    margin-top: 12px;
    font-size: 12px;
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
/* Respect user's reduced-motion preference: skip the fade-in and let
   the coin sit at its initial rest tilt. Drag rotation still works —
   the transform is live-driven from JS per-frame, so we can't turn it
   off without breaking the interaction; but the fade + startup polish
   are what motion-sensitive users flag as unpleasant, and those we
   can remove. */
@media (prefers-reduced-motion: reduce) {
    .bs-medal-spotlight { animation: none; }
}

/* -------- Public badge shelf (BadgeShelf component) -------- */
/* Renders on /profile + /u/{id} below the bio. Grid of small tiles so the
   shelf reads as "a wall of trophies" rather than a list of rows. Wraps
   naturally, mobile-friendly. Keyboard focus + hover both surface the
   native tooltip carrying description + awarded date. */
.bs-badge-shelf {
    margin-top: 1.25rem;
}
.bs-badge-shelf-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.5rem;
}
.bs-badge-shelf-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    width: 84px;
    padding: 0.5rem 0.4rem;
    /* Button reset - tiles are <button> so keyboard + screen-reader can
       reach the detail dialog naturally. */
    background: transparent;
    border: 0;
    color: inherit;
    font: inherit;
    border-radius: var(--radius-input);
    text-align: center;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}
.bs-badge-shelf-item:active { transform: scale(0.97); }
.bs-badge-shelf-item:hover,
.bs-badge-shelf-item:focus-visible {
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    outline: none;
}
.bs-badge-shelf-item.is-inactive { opacity: 0.55; }
/* Tier-count summary above the tile grid. Each tier picks up its own tint
   on the count digits so the eye reads "gold count" vs "silver count" without
   parsing the label. Data-style typography (JetBrains Mono) on the digits. */
.bs-badge-shelf-stats {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem;
    margin-top: 0.5rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}
.bs-badge-shelf-stats-item {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
}
.bs-badge-shelf-stats-count {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.bs-badge-shelf-stats-label { text-transform: lowercase; }
.bs-badge-shelf-stats-sep { color: var(--surface-raised); }
/* Tier tokens carry into the count digits so "3 gold · 5 silver · 12 bronze"
   scans by colour alone — matches the puck ring hues. Same tokens = same
   perceived tier across every surface. */
.bs-badge-shelf-stats-item.is-gold   .bs-badge-shelf-stats-count { color: var(--tier-gold); }
.bs-badge-shelf-stats-item.is-silver .bs-badge-shelf-stats-count { color: var(--tier-silver); }
.bs-badge-shelf-stats-item.is-bronze .bs-badge-shelf-stats-count { color: var(--tier-bronze); }

/* Icon picker in the badge editor - dropdown + live preview puck side-by-side. */
.bs-badge-icon-picker {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
}
.bs-badge-icon-preview {
    width: 56px;
    height: 56px;
    display: grid;
    place-items: center;
    background: var(--surface-raised);
    border-radius: var(--radius-input);
    color: var(--accent);
    flex: 0 0 auto;
}
.bs-badge-icon-preview .mud-icon-root { font-size: 28px; }
/* Row inside each dropdown option - icon + label. */
.bs-badge-icon-option {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

/* -------- Batch award dialog -------- */
.bs-batch-picker {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.bs-batch-selected {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    padding: 0.5rem 0;
}
.bs-batch-results {
    max-height: 320px;
    overflow-y: auto;
    border: var(--border);
    border-radius: var(--radius-input);
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.bs-batch-hint {
    padding: 1.25rem 0.75rem;
    color: var(--text-muted);
    text-align: center;
}
/* Each row is a <button> so the whole hit target is one focusable element -
   the checkbox is decorative visual, the click toggles selection. */
.bs-batch-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.5rem 0.75rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius-input);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease);
}
.bs-batch-row:hover,
.bs-batch-row:focus-visible {
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    outline: none;
}
.bs-batch-row.is-selected { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.bs-batch-row.is-selected .mud-icon-root { color: var(--accent); }
.bs-batch-row.is-muted { opacity: 0.65; }
.bs-batch-row-body { min-width: 0; }
.bs-batch-row-name {
    color: var(--text);
    font-weight: 500;
    line-height: 1.25;
}
.bs-batch-row-meta {
    color: var(--text-muted);
    font-size: 0.8rem;
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bs-badge-shelf-item-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: -0.005em;
    font-size: 0.7rem;
    line-height: 1.15;
    color: var(--text);
    /* Two-line clamp - keeps every tile the same height even when titles
       vary. Longer titles gracefully truncate with an ellipsis. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

/* -------- Award UI (admin) -------- */
.bs-award-shelf {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.bs-award-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    min-height: 72px;
}
.bs-award-row.is-inactive { opacity: 0.65; }
.bs-award-row-body { min-width: 0; }
.bs-award-row-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    color: var(--text);
    font-size: 1rem;
    line-height: 1.2;
}
.bs-award-row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 4px;
}
.bs-award-row-meta .dot { color: var(--surface-raised); }
.bs-award-row-note {
    color: var(--text);
    font-size: 0.85rem;
    margin-top: 6px;
    max-width: 60ch;
    line-height: 1.45;
    font-style: italic;
}
.bs-award-row-actions { display: inline-flex; align-items: center; }

/* Preview panel inside the Award dialog - appears once a badge is picked so
   the admin sees the actual shelf treatment before hitting Confirm. */
.bs-award-preview {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 1rem;
    padding: 1rem;
    margin-top: 1rem;
    background: var(--surface-raised);
    border-radius: var(--radius-card);
    align-items: center;
}
.bs-award-preview-body { min-width: 0; }
.bs-award-preview-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    color: var(--text);
    text-transform: uppercase;
    letter-spacing: -0.01em;
    font-size: 1.15rem;
    line-height: 1.15;
    margin-top: 2px;
}

/* Row inside the Autocomplete's dropdown. Same puck as the shelf so the
   picker and the awarded list feel like the same object at different scales. */
.bs-award-picker-row {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.25rem 0;
}
.bs-award-picker-body { min-width: 0; }
.bs-award-picker-title {
    color: var(--text);
    font-weight: 600;
    line-height: 1.2;
}
.bs-award-picker-sub {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    margin-top: 2px;
}

/* Series-scope dialog (spec §6.1). Each radio option carries a caption line
   under the label with the real count; the "This and following" split warning
   appears inline only when that option is selected. */
.bs-scope-meta {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 2px;
}
.bs-scope-warn {
    color: var(--warning);
    font-size: 0.82rem;
    margin-top: 4px;
    padding-left: 10px;
    border-left: 2px solid var(--warning);
}

/* Landing "The crew" grid - spec §5, the "second-most-read section on the site."
   Real photos, real bios; empty state (no instructor with both bio+photo) is
   handled by hiding the section entirely in the razor. */
.bs-landing-crew-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 16px;
}
.bs-landing-crew-card {
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.bs-landing-crew-photo {
    width: 100%;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    background: var(--surface-raised);
}
.bs-landing-crew-body {
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.bs-landing-crew-name {
    font-family: 'Manrope', sans-serif;
    font-weight: 800;
    font-size: 1.1rem;
    letter-spacing: -0.02em;
    text-transform: uppercase;
}
.bs-landing-crew-bio {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    margin: 0;
    white-space: pre-wrap;
}

/* The Contact-the-studio trigger inside "Where to find us" - styled as a
   link, but rendered as a button so the click opens the sheet instead of
   navigating. */
.bs-landing-where-linkbtn {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font: inherit;
    text-align: left;
}

/* Section head with an inline action button - used on the admin EditUser page's
   dependents section (title on the left, "Add dependent" button on the right). */
.bs-page-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
.bs-page-section-title {
    font-family: 'Manrope', sans-serif;
    font-weight: 800;
    font-size: 1.1rem;
    letter-spacing: -0.01em;
    margin: 0;
    text-transform: uppercase;
}
.bs-dep-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
}
.bs-dep-actions {
    display: flex;
    gap: 4px;
}

/* Spec §9 Error state - sibling of .bs-empty but shaped for "broken, not empty".
   Danger-tinted icon + border, keep the Try-again button reachable at 44px. */
.bs-error-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 3rem 1rem;
    text-align: center;
    background: var(--surface);
    border: 1px solid var(--danger);
    border-radius: var(--radius-card);
    color: var(--text-muted);
}
.bs-error-block .mud-icon-root {
    font-size: 42px;
    color: var(--danger);
    margin-bottom: 0.75rem;
}
.bs-error-block-title {
    font-family: 'Manrope', system-ui, sans-serif;
    color: var(--text);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.005em;
    margin-bottom: 0.35rem;
}
.bs-error-block-sub {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    max-width: 48ch;
    margin: 0 auto;
}

/* Admin list skeleton wrapper - stacks a handful of Skeleton block rows with
   consistent vertical rhythm so the loading state previews the real layout
   (spec §9: skeletons matching the real layout, never a centred spinner on
   a full page). */
.bs-adminlist-skel {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 12px;
}

/* "Substitute" chip on the session detail sheet - a small warning-tinted pill
   next to the instructor name so members know it's not the usual teacher
   (spec §3.2). */
.bs-substitute-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    margin-left: 8px;
    border-radius: 999px;
    background: rgba(242, 183, 5, 0.15);
    color: var(--warning);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* Content editor section chip - surfaces "will this render on landing?" at
   a glance so the admin doesn't have to open each card. Empty HTML = hidden. */
.bs-content-state-chip {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: middle;
}
.bs-content-state-chip.is-live {
    background: rgba(53, 192, 138, 0.15);
    color: var(--success);
}
.bs-content-state-chip.is-hidden {
    background: rgba(140, 148, 143, 0.15);
    color: var(--text-muted);
}

/* ==================================================================
   Inline CMS section editor (Landing / Privacy)
   ------------------------------------------------------------------
   Replaces the standalone /admin/content page: admins flip "Edit
   content" on the page itself, and each CMS block becomes an inline
   editor card right where it will render. Only visible when the
   surrounding page has toggled EditMode = true on the component.
   ================================================================== */
.bs-inline-editor {
    margin: 0.75rem 0;
    padding: 1rem 1.15rem 0.85rem;
    background: var(--surface);
    border: 1px dashed var(--accent);
    border-radius: var(--radius-card);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    /* Sits inside narrow prose columns (e.g. bs-landing-who-copy). Without
       min-width:0 the textarea's implicit min-content pushes the flex/grid
       parent wider than intended and blows out the two-column split. */
    min-width: 0;
    max-width: 100%;
}
.bs-inline-editor .bs-content-textarea {
    min-width: 0;
    /* Shorter default height in narrow contexts - admins can still drag the
       resize handle if they need more room. */
    min-height: 180px;
}
.bs-inline-editor-skel {
    padding: 1rem;
}
.bs-inline-editor-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.bs-inline-editor-head-left {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    min-width: 0;
}
/* Rebuild the tab pill scale so the whole head can share a single row in
   narrow columns - the default .bs-content-tabs padding lifts the pill above
   the eyebrow line, which reads as two separate blocks. */
.bs-inline-editor .bs-content-tabs {
    margin: 0;
    flex-shrink: 0;
}
.bs-inline-editor-key {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text);
}
/* Preview shell hosts the same .bs-landing-cms-body wrapper the published
   page uses, so what the admin sees IS what visitors will see. Padded card
   frame + min-height + empty-state slot are the only editor-specific
   ornament; typography comes from bs-landing-cms-body. */
.bs-inline-editor-preview {
    padding: 1.25rem 1.5rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    min-height: 180px;
}
/* "Last updated" footer for legal/policy pages (privacy). Mono digits so a
   date reads as a date, muted colour so it doesn't compete with the body. */
.bs-inline-editor-updated {
    margin-top: 1.5rem;
    color: var(--text-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.78rem;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
}
.bs-inline-editor-hint {
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.5;
    max-width: 68ch;
}
.bs-inline-editor-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--surface-raised);
    margin-top: 0.25rem;
    flex-wrap: wrap;
    row-gap: 0.4rem;
}
.bs-inline-editor-actions > .mud-flex-spacer {
    flex: 1 1 auto;
    min-width: 8px;
}
/* Explainer text next to the toggle in the page-level admin bar. Muted
   so the button remains the primary affordance. */
.bs-inline-editor-explainer {
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.4;
    max-width: 60ch;
}
/* Landing places the admin bar inside the max-width landing wrapper -
   pull it into the same rhythm as the other landing sections and give
   it breathing room from the hero directly below. */
.bs-landing-adminbar {
    margin: 0 auto 1rem;
    max-width: 1200px;
    width: min(100%, 1200px);
    /* Sit above the hero image in paint order. Without this the hero's
       negative margin-top (pull-under-header) makes it overlap the admin
       bar, and because the hero is later in DOM it wins the paint -
       swallowing the toggle whole. */
    position: relative;
    z-index: 2;
}
/* Cancel the hero's pull-under-header trick when the admin bar is present.
   Trade-off: signed-in admins lose the full-bleed edge-to-edge hero effect
   in exchange for a hero that starts cleanly below the admin bar instead of
   floating up behind it. Members and anonymous visitors still get the
   original effect because they never render .bs-landing-adminbar. */
.bs-landing-v2:has(.bs-landing-adminbar) .bs-landing-hero-v2 {
    margin-top: 0;
    padding-top: 0;
}

/* Dashboard "today's roster" widget - one row per session with time, name,
   instructor, room, and a compact ring on the right so the owner sees which
   classes need attention at a glance. */
.bs-admin-roster-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 12px;
}
.bs-admin-roster-row {
    display: grid;
    grid-template-columns: 64px 1fr auto;
    align-items: center;
    gap: 16px;
    padding: 12px 16px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    text-decoration: none;
    color: var(--text);
    transition: border-color 0.15s ease, background 0.15s ease;
}
.bs-admin-roster-row:hover {
    border-color: var(--text-muted);
}
.bs-admin-roster-row.is-cancelled {
    opacity: 0.55;
}
.bs-admin-roster-time {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 600;
    font-size: 1.05rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.bs-admin-roster-name {
    font-weight: 600;
}
.bs-admin-roster-meta {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 2px;
}
.bs-admin-roster-fill {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
}
.bs-admin-roster-cancelled-chip {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    background: rgba(229, 72, 77, 0.15);
    color: var(--danger);
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* ==================================================================
   Session detail page (/sessions/{id})
   ------------------------------------------------------------------
   Rich, deep-linkable page that replaces the ScheduleEntryDetailDialog
   for Session-kind entries (Regular / Seminar / Battle). Layout is
   editorial - full-bleed hero (when the class or session carries a
   HeroImageUrl), name + meta, then a "signature strip" pairing the
   cypher ring with the primary action, then stackable content sections
   (about, practical, teaching, next sessions). All sections render
   only when they have data behind them; a bare class still reads
   clean, just tighter. One accent moment per screen (the ring's fill
   + the Book button - the button is the accent-filled control).
   ================================================================== */
.bs-session-page {
    /* Fill the outer MudContainer (MaxWidth.Large = 1280 px) so wide
       content - the bracket, instructor grids, next-sessions strip -
       gets the room it needs. Individual prose blocks re-cap themselves
       at a readable line length below so we don't get 130-char body
       paragraphs sprawling to the edges. */
    width: 100%;
    max-width: 100%;
    margin: 1rem auto 3rem;
    padding: 0 1rem;
    display: flex;
    flex-direction: column;
    gap: 2rem;
}
@media (min-width: 641px) {
    .bs-session-page { padding: 0 1.5rem; gap: 2.5rem; }
}

/* Hero - image sits on top when present (200/280px), body underneath.
   When no image is set the whole card collapses to just the body block
   so the layout doesn't leave a phantom slab of empty color. */
.bs-session-hero {
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
/* Hero photograph (m101 revised): 16:9 cinematic frame, center-cropped
   with object-fit:cover so the container edges stay filled - no letterbox
   panels. Aspect-ratio replaces the old fixed 280px strip so the hero
   grows proportionally with the content column (~675px tall at 1200px
   wide) and shows much more of the poster. Admins should upload posters
   at 16:9 (e.g. 1920×1080) to avoid any crop; off-ratio uploads crop
   symmetrically from the center. */
.bs-session-hero.has-hero .bs-session-hero-photo {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    max-height: 70vh;
    object-fit: cover;
    object-position: center;
    background-color: var(--surface-raised);
}
.bs-session-hero-body {
    padding: 1.5rem 1.5rem 1.75rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
@media (min-width: 641px) {
    .bs-session-hero-body { padding: 2rem 2.25rem 2.25rem; }
}
.bs-session-hero-eyebrow {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.bs-session-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.025em;
    font-size: clamp(1.9rem, 6vw, 3rem);
    line-height: 1.02;
    margin: 0;
    color: var(--text);
    word-break: break-word;
}
.bs-session-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.85rem;
    color: var(--text-muted);
    font-size: 0.95rem;
}
.bs-session-levelchip {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.7rem;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill, 999px);
    color: var(--text);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    line-height: 1;
}
.bs-session-when {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.875rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.bs-session-when-sep {
    margin: 0 0.4rem;
    color: var(--surface-raised);
}
.bs-session-where { color: var(--text-muted); }

/* Signature strip - cypher ring next to the primary action. The ring
   is the §5 signature; the action is the single accent-filled control
   on the page. On mobile they stack (ring first, action full-width). */
.bs-session-signature {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1.25rem 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    flex-wrap: wrap;
}
@media (min-width: 641px) {
    .bs-session-signature { padding: 1.5rem 2.25rem; gap: 2rem; }
}
.bs-session-actions {
    flex: 1 1 auto;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.75rem;
    min-width: 12rem;
}
@media (max-width: 640px) {
    .bs-session-actions { justify-content: stretch; }
    .bs-session-actions > .mud-button-root { flex: 1 1 auto; }
}
.bs-session-actionnote {
    color: var(--text-muted);
    font-size: 0.95rem;
    padding: 0.5rem 0;
}
.bs-session-actionnote.is-danger { color: var(--danger); }

/* Content sections - each is a surface card with an eyebrow header and
   body. Renders only when its own data is present; no empty shells. */
.bs-session-section {
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    padding: 1.25rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
@media (min-width: 641px) {
    .bs-session-section { padding: 1.75rem 2.25rem; }
}
.bs-session-body {
    margin: 0;
    color: var(--text);
    font-size: 1.0625rem;
    line-height: 1.6;
    white-space: pre-wrap;
    /* Cap prose at a comfortable line length even inside a 1280 px
       container. Structural content (bracket, instructor grid, next-
       sessions strip) fills the full width via its own rules. */
    max-width: 68ch;
}

/* Practical block - labeled rows in a definition list. Labels use the
   overline treatment so they read as metadata not headers. */
.bs-session-practical {
    margin: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
}
@media (min-width: 641px) {
    .bs-session-practical {
        grid-template-columns: max-content minmax(0, 1fr);
        column-gap: 2rem;
        row-gap: 1.1rem;
        align-items: baseline;
    }
}
.bs-session-practical dt {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    margin: 0;
    line-height: 1.4;
}
.bs-session-practical dd {
    margin: 0;
    color: var(--text);
    font-size: 1rem;
    line-height: 1.55;
    white-space: pre-wrap;
    max-width: 68ch;
}

/* Instructor cards - avatar + name + bio. Substitute chip nudges the
   name so members instantly see it's not the regular teacher. */
.bs-session-instructors {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
.bs-session-instructor {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
}
.bs-session-instructor-avatar {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-raised);
    display: grid;
    place-items: center;
    box-shadow: inset 0 0 0 1px var(--surface-raised);
    flex: 0 0 auto;
}
.bs-session-instructor-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.bs-session-instructor-avatar > span {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: 1.75rem;
    color: var(--accent);
    line-height: 1;
    text-transform: uppercase;
}
.bs-session-instructor-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.bs-session-instructor-name {
    color: var(--text);
    font-weight: 600;
    font-size: 1.0625rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bs-session-instructor-sub {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-pill, 999px);
    background: rgba(245, 36, 40, 0.14);
    color: var(--accent);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1;
}
.bs-session-instructor-bio {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.55;
    white-space: pre-wrap;
    max-width: 68ch;
}

/* Next-sessions strip - compact chips linking to sibling sessions of
   the same class. Muted variant for cancelled rows so members still
   see them but understand they can't book. */
.bs-session-next {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}
.bs-session-next-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.85rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill, 999px);
    color: var(--text);
    text-decoration: none;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
    transition: border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-session-next-chip:hover,
.bs-session-next-chip:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
    outline: none;
}
.bs-session-next-chip.is-muted {
    color: var(--text-muted);
    text-decoration: line-through;
}
.bs-session-next-chip.is-muted:hover { color: var(--text-muted); border-color: var(--surface-raised); }
.bs-session-next-day { font-weight: 500; }
.bs-session-next-time { color: var(--text-muted); }
.bs-session-next-cancelled {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--danger);
}

/* Skeleton + empty states - same footprint as the live layout so the
   hero-to-content transition doesn't jump. */
.bs-session-skel { display: flex; flex-direction: column; }
.bs-session-hero-skel {
    height: 280px;
    background: var(--surface-raised);
    border-radius: var(--radius-card, 16px);
    position: relative;
    overflow: hidden;
}
.bs-session-hero-skel::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
    animation: bs-skel-shimmer 1.4s ease-in-out infinite;
}
.bs-session-empty {
    padding: 2rem 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    align-items: flex-start;
}

/* ==================================================================
   Session detail - Battle variant
   ------------------------------------------------------------------
   Extra blocks the Battle layout can layer on top of the base session
   detail: a Prize pill in the meta row, a Results podium block for
   posted results (winner + optional runner-up), and simple body
   sections for Judges + DJs. The base .bs-session-* classes already
   handle the rest.
   ================================================================== */
.bs-session-prizechip {
    display: inline-flex;
    align-items: center;
    padding: 0.28rem 0.75rem;
    border: 1px solid var(--accent);
    color: var(--accent);
    border-radius: var(--radius-pill, 999px);
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Results panel - sits directly under the hero when a winner is posted.
   Two-slot podium (1st + optional 2nd), mono ranks, big-name display,
   optional body text underneath for judge picks / top 4 notes. */
.bs-session-battle-results {
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    padding: 1.5rem 1.5rem 1.75rem;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
@media (min-width: 641px) {
    .bs-session-battle-results { padding: 2rem 2.25rem 2.25rem; }
}
.bs-session-battle-results-eyebrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.bs-session-battle-results-postedat {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.bs-session-battle-podium {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
}
@media (min-width: 641px) {
    .bs-session-battle-podium { grid-template-columns: 1.4fr 1fr; gap: 1.5rem; }
}
.bs-session-battle-place {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 1rem 1.15rem;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card, 16px);
    background: var(--bg);
    min-width: 0;
}
.bs-session-battle-place.is-first {
    border-color: var(--accent);
    background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 140%);
}
.bs-session-battle-place-rank {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.7rem;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.bs-session-battle-place.is-first .bs-session-battle-place-rank { color: var(--accent); }
.bs-session-battle-place-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.025em;
    font-size: clamp(1.5rem, 4vw, 2.25rem);
    line-height: 1.05;
    color: var(--text);
    word-break: break-word;
}
.bs-session-battle-place-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1;
}
.bs-session-battle-results-body {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.55;
    white-space: pre-wrap;
}

/* ==================================================================
   Session detail - Seminar variant
   ------------------------------------------------------------------
   Guest spotlight (bigger than the regular Instructor card) + agenda.
   Agenda + price chip both reuse existing base classes; only the
   Guest block needs its own styles.
   ================================================================== */
.bs-session-guest {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
}
@media (min-width: 641px) {
    .bs-session-guest { gap: 1.75rem; }
}
.bs-session-guest-avatar {
    /* Larger than the regular instructor card - the guest IS the show. */
    width: 96px;
    height: 96px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-raised);
    display: grid;
    place-items: center;
    box-shadow: inset 0 0 0 1px var(--surface-raised);
    flex: 0 0 auto;
}
@media (min-width: 641px) {
    .bs-session-guest-avatar { width: 128px; height: 128px; }
}
.bs-session-guest-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.bs-session-guest-avatar > span {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: 2.5rem;
    color: var(--accent);
    line-height: 1;
    text-transform: uppercase;
}
.bs-session-guest-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.bs-session-guest-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    font-size: clamp(1.4rem, 3.5vw, 2rem);
    line-height: 1.05;
    color: var(--text);
    word-break: break-word;
}
.bs-session-guest-bio {
    margin: 0;
    color: var(--text);
    font-size: 1rem;
    line-height: 1.6;
    white-space: pre-wrap;
    max-width: 68ch;
}

/* ==================================================================
   Event detail (/events/{id}) - extras on top of the shared session
   detail shell. Events don't have a cypher ring (no capacity); the
   signature strip's ring is replaced by a big mono going-count that
   reads as social proof.
   ================================================================== */
.bs-event-going {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.5rem 0;
    min-width: 6rem;
}
.bs-event-going-count {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: clamp(2rem, 5vw, 2.75rem);
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}
.bs-event-going-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1;
}

/* ==================================================================
   Battle bracket grid (member + admin, shared component)
   ------------------------------------------------------------------
   Horizontal columns per round, matches stacked within a column.
   Compact variant used on /sessions/{id}; full variant on the admin
   editor. Champion match glows in accent when the final is decided.
   Grid scrolls horizontally on narrow screens rather than wrapping -
   the bracket structure only reads correctly left-to-right.
   ================================================================== */
.bs-bracket-empty {
    color: var(--text-muted);
    font-style: italic;
    padding: 1rem 0;
}
.bs-bracket {
    /* Horizontal scroll wrapper - the inner .bs-bracket-scroll is a
       flex row that overflows here rather than cramming rounds. */
    max-width: 100%;
    overflow-x: auto;
    padding-bottom: 0.5rem;
}
.bs-bracket-scroll {
    display: flex;
    gap: 1.5rem;
    align-items: stretch;
    min-width: min-content;
}
.bs-bracket.is-compact .bs-bracket-scroll { gap: 1rem; }

.bs-bracket-round {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    /* Rounds stretch to fill the page horizontally (flex 1) and vertically
       inherit the tallest round's height via align-items:stretch on the
       parent .bs-bracket-scroll. The round label sits at the top; matches
       live inside a grid child below that splits the REMAINING vertical
       space into N equal rows -- one per match. That's what keeps R0's
       four midpoints aligned with R1's two, and R1's two with R2's one. */
    flex: 1 1 0;
    min-width: 12rem;
}
.bs-bracket.is-compact .bs-bracket-round { min-width: 11rem; }

.bs-bracket-round-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    margin-bottom: 0.25rem;
    text-align: center;
    flex-shrink: 0;
}
.bs-bracket-round-matches {
    /* Fills whatever height the round has after the label. Grid rows split
       that space into N equal parts (grid-auto-rows: 1fr); each match
       centers within its row via align-self, so match-cards of slightly
       different heights (winner-set vs not) don't shift alignment. */
    flex: 1 1 auto;
    display: grid;
    grid-auto-rows: 1fr;
    align-items: center;
    min-height: 0;
}
.bs-bracket-round-matches > .bs-bracket-match {
    align-self: center;
}

.bs-bracket-match {
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input, 12px);
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.bs-bracket-match.is-final {
    border-color: var(--surface-raised);
    background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 140%);
}
.bs-bracket-match.is-champion {
    border-color: var(--accent);
}
.bs-bracket-match-body {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.bs-bracket-slot {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.6rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: 8px;
    min-height: 2.25rem;
}
.bs-bracket-slot.is-empty { color: var(--text-muted); font-style: italic; }
.bs-bracket-slot.is-winner {
    border-color: var(--accent);
    color: var(--text);
}
.bs-bracket-slot.is-winner .bs-bracket-slot-name {
    color: var(--accent);
    font-weight: 600;
}
.bs-bracket-slot-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bs-bracket-slot-pick {
    flex: 0 0 auto;
    padding: 0.15rem 0.5rem;
    background: transparent;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill, 999px);
    color: var(--text-muted);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-bracket-slot-pick:hover:not(:disabled),
.bs-bracket-slot-pick:focus-visible:not(:disabled) {
    color: var(--accent);
    border-color: var(--accent);
    outline: none;
}
.bs-bracket-slot-pick:disabled { opacity: 0.45; cursor: default; }

.bs-bracket-match-clear {
    align-self: flex-end;
    padding: 0.15rem 0.5rem;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-bracket-match-clear:hover:not(:disabled),
.bs-bracket-match-clear:focus-visible:not(:disabled) {
    color: var(--danger);
    outline: none;
}
.bs-bracket-match-clear:disabled { opacity: 0.45; cursor: default; }

.bs-bracket-error {
    margin-top: 0.75rem;
    padding: 0.5rem 0.75rem;
    background: rgba(229, 72, 77, 0.12);
    border: 1px solid var(--danger);
    border-radius: var(--radius-input, 12px);
    color: var(--danger);
    font-size: 0.9rem;
}

/* Wraps the bracket inside the .bs-session-battle-results shell on the
   member page - keeps the eyebrow band + horizontal scroll consistent
   with the rest of the detail card. */
.bs-session-battle-bracket .bs-bracket { margin-top: 0.25rem; }

/* Champion celebration band - replaces the plain "BRACKET" eyebrow when
   the final has been decided. Editorial CHAMPION overline, huge name in
   the display face, mono crowned date. Subtle fade+scale on mount (one
   orchestrated moment per §4; motion-safe below). No confetti, no
   emoji, no badge chrome per §1's "explicitly forbidden" list. */
.bs-session-battle-champion {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-bottom: 1rem;
    margin-bottom: 0.75rem;
    border-bottom: 1px solid var(--surface-raised);
    animation: bs-champion-in var(--dur-slow, 400ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)) both;
}
.bs-session-battle-champion-overline {
    color: var(--accent);
    /* Slightly tighter tracking so the accent-colored overline reads as
       a proclamation rather than a metadata label. */
    letter-spacing: 0.22em;
}
.bs-session-battle-champion-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: -0.03em;
    /* Scales with viewport - reads as headline on desktop, still bold
       on mobile. */
    font-size: clamp(2.25rem, 6vw, 3.75rem);
    line-height: 1;
    color: var(--text);
    word-break: break-word;
    /* Subtle accent underline swipe underneath the name to mark the
       crowning without any gamification chrome. */
    text-shadow: none;
}
.bs-session-battle-champion-crowned {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
@keyframes bs-champion-in {
    from { opacity: 0; transform: translateY(6px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .bs-session-battle-champion { animation: none; }
}

/* Admin bracket editor - seed inputs above, editable BracketView below. */
.bs-adminbracket {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    padding: 0 0.5rem;
}
.bs-adminbracket-seeds {
    padding: 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
}
.bs-adminbracket-seeds-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 0.75rem 1rem;
    margin-top: 1rem;
}
.bs-adminbracket-seed {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
}
.bs-adminbracket-seed-num {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: 0.75rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
    min-width: 1.6rem;
    padding-top: 0.6rem;
}
.bs-adminbracket-seed-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
/* Link-member ghost chip below the seed input. Small, unobtrusive; only
   pulls attention when the admin needs it. */
.bs-adminbracket-seed-link,
.bs-adminbracket-seed-linked {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.5rem;
    background: transparent;
    border: 1px dashed var(--surface-raised);
    border-radius: var(--radius-pill, 999px);
    color: var(--text-muted);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    cursor: pointer;
    align-self: flex-start;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-adminbracket-seed-link:hover,
.bs-adminbracket-seed-link:focus-visible {
    color: var(--accent);
    border-color: var(--accent);
    outline: none;
}
.bs-adminbracket-seed-linked {
    color: var(--accent);
    border-style: solid;
    border-color: var(--accent);
    cursor: default;
}
.bs-adminbracket-seed-unlink {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 1rem;
    line-height: 1;
    padding: 0 0.15rem;
    cursor: pointer;
    margin-left: 0.2rem;
}
.bs-adminbracket-seed-unlink:hover,
.bs-adminbracket-seed-unlink:focus-visible {
    color: var(--danger);
    outline: none;
}

/* Linked dancer slot in the bracket becomes a hover-underlined link. */
.bs-bracket-slot-name.is-linked {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted var(--surface-raised);
    padding-bottom: 1px;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-bracket-slot-name.is-linked:hover,
.bs-bracket-slot-name.is-linked:focus-visible {
    color: var(--accent);
    border-color: var(--accent);
    outline: none;
}
.bs-bracket-slot.is-winner .bs-bracket-slot-name.is-linked {
    border-color: rgba(245, 36, 40, 0.35);
}

/* Small note block inside the member picker dialog. */
.bs-memberpicker-note {
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 1rem 0.25rem 0.25rem;
    text-align: center;
}
.bs-memberpicker-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.75rem;
}
.bs-memberpicker-row {
    appearance: none;
    background: transparent;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input, 12px);
    padding: 0.5rem 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--text);
    cursor: pointer;
    text-align: left;
    transition: border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                background var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-memberpicker-row:hover,
.bs-memberpicker-row:focus-visible {
    border-color: var(--accent);
    background: rgba(245, 36, 40, 0.05);
    outline: none;
}
.bs-memberpicker-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--surface-raised);
    overflow: hidden;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    color: var(--accent);
    font-size: 0.9rem;
    text-transform: uppercase;
}
.bs-memberpicker-avatar img {
    width: 100%; height: 100%; object-fit: cover;
}
.bs-memberpicker-name {
    font-weight: 600;
    font-size: 0.95rem;
}
.bs-adminbracket-grid {
    padding: 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
}
.bs-adminbracket-danger {
    display: flex;
    justify-content: flex-end;
    padding: 0 0.5rem 1rem;
}

/* ==================================================================
   Admin analytics page (/admin/subscriptions/analytics)
   ------------------------------------------------------------------
   Preset chip row + optional custom date pickers + KPI grid + per-plan
   table + export buttons. KpiCard tile is intentionally simple - big
   mono digits, uppercase label. Muted variant reads as reference.
   ================================================================== */
.bs-analytics-range {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin: 1.25rem 0 1.5rem;
}
.bs-analytics-presets {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.bs-analytics-custom {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}
.bs-analytics-rangelabel {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.bs-analytics-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}
.bs-kpi-card {
    padding: 1.25rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: var(--radius-card, 16px);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.bs-kpi-card.is-muted {
    /* Reference-only tiles read quieter - no accent border, muted label
       colour, so live "now" numbers don't compete with the headline
       range-scoped numbers above them. */
    border-color: var(--surface-raised);
}
.bs-kpi-value {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500;
    font-size: clamp(1.6rem, 3vw, 2.15rem);
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}
.bs-kpi-card.is-muted .bs-kpi-value { color: var(--text-muted); }
.bs-kpi-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1.3;
}

.bs-analytics-breakdown {
    margin-top: 2rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.bs-analytics-export {
    margin-top: 2.5rem;
    padding: 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.bs-analytics-export-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0.75rem;
    margin-top: 0.75rem;
}
.bs-analytics-export-card {
    padding: 1rem 1rem 0.75rem;
    background: var(--bg);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input, 12px);
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.bs-analytics-export-card-title {
    font-weight: 600;
    color: var(--text);
    font-size: 0.95rem;
}
.bs-analytics-export-card-sub {
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.4;
    margin-bottom: 0.35rem;
}
.bs-analytics-export-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: auto;
    padding-top: 0.5rem;
}
/* Anchor tag "buttons" - simpler than trying to mimic MudButton's markup,
   and cleaner semantics for a download link. */
.bs-analytics-export-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.4rem 0.9rem;
    border-radius: var(--radius-pill, 999px);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    text-decoration: none;
    cursor: pointer;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                background var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-analytics-export-btn.is-secondary {
    background: transparent;
    border: 1px solid var(--surface-raised);
    color: var(--text);
}
.bs-analytics-export-btn.is-secondary:hover,
.bs-analytics-export-btn.is-secondary:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
    outline: none;
}
.bs-analytics-export-btn.is-primary {
    background: var(--accent);
    border: 1px solid var(--accent);
    color: var(--accent-ink);
}
.bs-analytics-export-btn.is-primary:hover,
.bs-analytics-export-btn.is-primary:focus-visible {
    filter: brightness(1.06);
    outline: none;
}

.bs-analytics-groupspace {
    margin-top: 1.5rem;
}

/* Skeleton placeholder while KPIs load. Kept simple; MudBlazor's Skeleton
   handles the shimmer. */
.bs-analytics-skel { margin: 1rem 0; }

/* ==================================================================
   Inline admin toolbar on /sessions/{id} and /events/{id}
   ------------------------------------------------------------------
   Only renders when the caller is an Admin -- gives them one-click
   access to Edit / Roster / Manage bracket / Delete without having
   to navigate back to /admin/schedule. Visually differentiated with
   a subtle accent-tinted background + ADMIN eyebrow so admins know
   the block is theirs and not a member-facing control.
   ================================================================== */
.bs-page-adminbar {
    /* Sits below the back link, above the hero. Faint accent tint on
       the border so it reads as an admin band without being loud. */
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    background: rgba(245, 36, 40, 0.04);
    border: 1px dashed var(--accent);
    border-radius: var(--radius-input, 12px);
}
.bs-page-adminbar-eyebrow {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--accent);
    line-height: 1;
}
.bs-page-adminbar-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}
/* MudSpacer inside the actions row pushes Delete permanently to the
   far right so admins don't mis-tap it while reaching for Edit. */
.bs-page-adminbar-actions > .mud-flex-spacer {
    flex: 1 1 auto;
}

/* ==================================================================
   Admin schedule sections + filter chips
   ------------------------------------------------------------------
   Two clearly-named sections on /admin/schedule (Sessions and Events
   & closures), each with its own header + chip row for filtering by
   kind. Chip row is a horizontal scroller on narrow admin panes so
   long labels don't wrap into two lines.
   ================================================================== */
.bs-admin-section {
    margin-top: 2.5rem;
}
.bs-admin-section:first-of-type { margin-top: 1.5rem; }

.bs-admin-section-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}
.bs-admin-section-count {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    padding-bottom: 0.35rem;
}

.bs-admin-filterchips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0.25rem 0 0.9rem;
    padding-bottom: 0.25rem;
}
.bs-admin-filterchip {
    appearance: none;
    background: transparent;
    border: 1px solid var(--surface-raised);
    color: var(--text-muted);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    padding: 0.35rem 0.8rem;
    border-radius: var(--radius-pill, 999px);
    line-height: 1;
    cursor: pointer;
    transition: color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                border-color var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1)),
                background var(--dur-fast, 140ms) var(--ease, cubic-bezier(0.2,0.8,0.2,1));
}
.bs-admin-filterchip:hover,
.bs-admin-filterchip:focus-visible {
    color: var(--text);
    border-color: var(--text-muted);
    outline: none;
}
.bs-admin-filterchip.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}
.bs-admin-filterchip.is-active:hover,
.bs-admin-filterchip.is-active:focus-visible {
    color: var(--accent-ink);
    border-color: var(--accent);
}

/* Small form-eyebrow used inside the admin dialog to introduce a subsection
   ("Battle detail"). Not tied to the session page - kept here because it's
   used only by these battle blocks so far. */
.bs-form-eyebrow {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}

/* --- Admin Schedule (Πρόγραμμα) mobile layout ---------------------------
   Everything here is gated on max-width:899.98px so desktop stays untouched.
   The default 8-column sessions table + 6-column events table both need to
   scroll horizontally on a phone, which reads as broken. On mobile we hide
   the <thead>, flip <table>/<tbody>/<tr>/<td> to display:block, and style
   each row as a card. Also stacks the page-hero action buttons full-width
   (4 wrapping in a row on a phone was the other half of the mess) and puts
   the two date pickers on their own line so they're touch-sized.

   All selectors are scoped to .bs-admin-table so this affects ONLY the
   admin tables that opt in via Class="bs-admin-table". Member-facing
   MudTables and any other bs-* components are not touched. */
@media (max-width: 899.98px) {
    /* Page hero: stack title above actions, and stack the actions full-width
       so 4 buttons don't wrap into a chaotic 2x2 grid. Primary "New session"
       is the last one in source order, which puts it at the bottom -- easiest
       thumb reach on a phone. */
    .bs-page-hero {
        flex-direction: column;
        align-items: stretch;
        gap: 0.75rem;
    }
    .bs-page-hero-actions { width: 100%; gap: 0.5rem; }
    .bs-page-hero-actions > .mud-button {
        flex: 1 1 100%;
        justify-content: center;
        min-height: 44px;
    }

    /* Filter bar: date pickers full-width, one per row, so tapping them
       doesn't require pinch-zooming; switch sits below on its own line. */
    .bs-filter-bar {
        flex-direction: column;
        align-items: stretch;
        padding: 0.75rem;
    }
    .bs-filter-bar > .mud-input-control,
    .bs-filter-bar > .mud-picker { width: 100%; }
    .bs-filter-bar .ml-2 { margin-left: 0 !important; }

    /* Section head: count sits under the title instead of right-aligned. */
    .bs-admin-section-head { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
    .bs-admin-section-count { padding-bottom: 0; }

    /* --- Table -> card list --------------------------------------------
       MudTable wraps its <table> in .mud-table-container with overflow-x
       set to auto. Turning off that scroll + flipping the table elements
       to block collapses everything into a vertical stack. */
    .bs-admin-table.mud-table {
        background: transparent;
        border: none;
        border-radius: 0;
        overflow: visible;
    }
    .bs-admin-table .mud-table-container { overflow: visible; }
    .bs-admin-table table,
    .bs-admin-table .mud-table-root { display: block; width: 100%; }
    .bs-admin-table .mud-table-head { display: none; } /* column headers -> not shown on mobile */
    .bs-admin-table .mud-table-body { display: block; }

    /* Each row is a card. Gap between cards via margin so the reader can
       parse boundaries at a glance; hover state neutered (no pointer). */
    .bs-admin-table .mud-table-body .mud-table-row {
        display: block;
        background: var(--surface);
        border: 1px solid var(--surface-raised);
        border-radius: var(--radius-card);
        padding: 0.75rem 0.9rem 0.5rem;
        margin-bottom: 0.75rem;
    }
    .bs-admin-table .mud-table-body .mud-table-row:hover { background: var(--surface); }

    /* Cells: block-stack with no borders (the card carries the border) and
       tight vertical rhythm so a row doesn't sprawl for half a viewport. */
    .bs-admin-table .mud-table-body .mud-table-cell {
        display: block;
        padding: 0.2rem 0;
        border-bottom: none;
        font-size: 0.95rem;
        text-align: left !important;
    }
    /* First cell (Date) reads as an eyebrow: small, muted, uppercase. Both
       tables put the date column first, so nth-child(1) is safe here. */
    .bs-admin-table .mud-table-body .mud-table-cell:nth-child(1) {
        font-family: 'JetBrains Mono', ui-monospace, monospace;
        font-size: 0.75rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--text-muted);
        margin-bottom: 0.15rem;
    }
    /* Second cell (Time range) shares eyebrow treatment, on the same line
       visually via negative margin -- reads "MON 26 AUG · 19:00 - 20:30". */
    .bs-admin-table .mud-table-body .mud-table-cell:nth-child(2) {
        display: inline;
        font-family: 'JetBrains Mono', ui-monospace, monospace;
        font-size: 0.75rem;
        letter-spacing: 0.08em;
        color: var(--text-muted);
    }
    .bs-admin-table .mud-table-body .mud-table-cell:nth-child(2)::before {
        content: " · ";
        color: var(--text-muted);
    }
    /* Third cell (Class name / Event title) is the card title -- larger,
       heavier, its own line, with a small top gap from the eyebrow above. */
    .bs-admin-table .mud-table-body .mud-table-cell:nth-child(3) {
        font-size: 1.05rem;
        font-weight: 600;
        color: var(--text);
        margin-top: 0.25rem;
        margin-bottom: 0.35rem;
    }

    /* Fill ring cell in the sessions table (4th column). Left-align on
       mobile -- the desktop centre alignment leaves it stranded mid-card. */
    .bs-admin-table .bs-admin-fill { justify-content: flex-start; }

    /* Actions row (last cell): full-width strip with icons spread out so
       they're 44px+ tappable. Padding-top separates from the info above. */
    .bs-admin-table .mud-table-body .mud-table-cell:last-child {
        padding-top: 0.5rem;
        margin-top: 0.35rem;
        border-top: 1px solid var(--surface-raised);
    }
    .bs-admin-table .bs-inline-actions {
        display: flex;
        justify-content: space-around;
        gap: 0.25rem;
        width: 100%;
    }
    .bs-admin-table .bs-inline-actions .mud-icon-button,
    .bs-admin-table .bs-inline-actions .mud-button {
        min-width: 44px;
        min-height: 44px;
    }
}

/* --- Admin drawer: mobile overlay ---------------------------------------
   Desktop keeps the Mini/expanded rail. On mobile the drawer is hidden by
   default via bs-hide-mobile, and admins reach it via a hamburger in the
   header that flips is-mobile-open on. Without this override there was NO
   mobile path into /admin/* routes -- the reason the mobile menu appeared
   missing after signing in as admin from a phone. */
.bs-app-bar-admin-menu {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    padding: 0;
    color: var(--text-muted);
    border-radius: 50%;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.bs-app-bar-admin-menu:hover,
.bs-app-bar-admin-menu:focus-visible {
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    outline: none;
}
.bs-app-bar.is-transparent .bs-app-bar-admin-menu { color: var(--text); }

.bs-admin-drawer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 1399;
}

/* When the mobile hamburger is on, force the drawer visible over the page
   as a full-height overlay. Beats the .bs-hide-mobile display:none rule
   because we swap that class out in the Razor class-expression itself. */
@media (max-width: 899.98px) {
    .bs-admin-drawer.is-mobile-open,
    .bs-admin-drawer.is-mobile-open .mud-drawer {
        display: block !important;
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        height: 100vh !important;
        width: min(300px, 86vw) !important;
        z-index: 1400 !important;
    }
    /* Force the expanded look on mobile even if the desktop toggle sat at
       "mini" -- there's no rail-collapse gesture on phones, and the mini
       state hides labels which defeats the mobile drawer's whole purpose. */
    .bs-admin-drawer.is-mobile-open .bs-admin-nav-link > span,
    .bs-admin-drawer.is-mobile-open .bs-admin-nav-group-head,
    .bs-admin-drawer.is-mobile-open .bs-admin-drawer-brand-text { display: block; }
    .bs-admin-drawer.is-mobile-open .bs-admin-drawer-toggle { display: none; }
}

/* ============================================================
   /merch — studio merch showcase (no checkout).
   Structure over content: the placeholder grid signals "hoodies /
   tees / caps live here" even before real product cards exist.
   Elevation from borders + surface steps per spec §4.
   ============================================================ */
.bs-merch-head {
    text-align: left;
    margin-bottom: 24px;
}
.bs-merch-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: clamp(28px, 4vw, 40px);
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--text);
    margin: 8px 0 12px;
}
.bs-merch-sub {
    color: var(--text-muted);
    font-size: clamp(15px, 1.1vw, 17px);
    line-height: 1.55;
    max-width: 60ch;
    margin: 0;
}
.bs-merch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
    margin-top: 32px;
}
.bs-merch-card {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
    overflow: hidden;
}
.bs-merch-card-media {
    aspect-ratio: 1 / 1;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
}
.bs-merch-card-mark {
    width: 40%;
    height: auto;
    opacity: 0.35;
    filter: grayscale(1);
}
.bs-merch-card-body {
    padding: 12px 14px 16px;
}
.bs-merch-card-status {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 13px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.bs-merch-cta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    margin-top: 40px;
    padding: 24px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
}
.bs-merch-cta-note {
    margin: 0;
    color: var(--text-muted);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.bs-merch-cta-sub {
    margin: 0;
    color: var(--text);
    font-size: clamp(15px, 1.05vw, 16px);
    line-height: 1.55;
    max-width: 60ch;
}

/* ============================================================
   /links — curated references (music, video, reading, community).
   Four fixed groups; each group renders its own empty-state when
   its DB-backed list is empty. Class prefix stays bs-study-* to
   avoid a wide rename after the page-name pivot from Study.
   ============================================================ */
.bs-study-head {
    text-align: left;
    margin-bottom: 24px;
}
.bs-study-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: clamp(28px, 4vw, 40px);
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--text);
    margin: 8px 0 12px;
}
.bs-study-sub {
    color: var(--text-muted);
    font-size: clamp(15px, 1.1vw, 17px);
    line-height: 1.55;
    max-width: 60ch;
    margin: 0;
}
.bs-study-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    margin-top: 32px;
}
.bs-study-card {
    display: flex;
    gap: 16px;
    padding: 20px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
}
.bs-study-card-icon {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 12px;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.bs-study-card-icon .mud-icon-root {
    font-size: 22px;
}
.bs-study-card-body { flex: 1 1 auto; min-width: 0; }
.bs-study-card-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 20px;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--text);
    margin: 0 0 6px;
}
.bs-study-card-sub {
    color: var(--text-muted);
    font-size: 14px;
    line-height: 1.5;
    margin: 0 0 14px;
}
.bs-study-card-empty {
    display: inline-block;
    padding: 6px 10px;
    background: var(--surface-raised);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.bs-study-cta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    margin-top: 40px;
    padding: 24px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card);
}
.bs-study-cta-note {
    margin: 0;
    color: var(--text);
    font-size: clamp(15px, 1.05vw, 16px);
    line-height: 1.55;
    max-width: 60ch;
}

/* Real product cards on /merch (populated state). Placeholder card styles
   above stay for the empty-state grid. */
.bs-merch-card-photo {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.bs-merch-card-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 16px;
    line-height: 1.25;
    letter-spacing: -0.005em;
    color: var(--text);
    margin-bottom: 4px;
}
.bs-merch-card-price {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-weight: 500;
    font-size: 14px;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    margin-bottom: 6px;
}
.bs-merch-card-desc {
    color: var(--text-muted);
    font-size: 13px;
    line-height: 1.5;
    margin: 0;
}

/* Real link list on /links (populated state per group). */
.bs-study-links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.bs-study-link a {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 10px 12px;
    background: var(--surface-raised);
    border-radius: 12px;
    color: var(--text);
    text-decoration: none;
    transition: background var(--dur-fast) var(--ease);
}
.bs-study-link a:hover,
.bs-study-link a:focus-visible {
    background: color-mix(in srgb, var(--accent) 12%, var(--surface-raised));
}
/* Optional thumbnail on the left of a link row — album covers, book covers,
   video thumbnails. Square, rounded, cover-fit; the group icon renders in
   its place when no ImageUrl is set. Same 48px slot on desktop and mobile
   so the row height doesn't jump between rows with/without an image. */
.bs-study-link-thumb {
    width: 48px;
    height: 48px;
    border-radius: 10px;
    object-fit: cover;
    background: var(--surface);
    flex: 0 0 auto;
}
/* Wrapper for the title + description so the thumbnail + open-in-new icon
   don't disrupt the text stack. Grabs the remaining row width. */
.bs-study-link-body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}
.bs-study-link-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--text);
    min-width: 0;
}
.bs-study-link-desc {
    display: block;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.5;
    margin-top: 2px;
}
.bs-study-link-icon {
    color: var(--text-muted);
    flex: 0 0 auto;
    margin-top: 2px;
}
.bs-study-links-skel {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Admin form utility used inside dialogs above MediaUpload. */
.bs-form-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 13px;
    color: var(--text);
    margin-bottom: 6px;
}

/* Admin /admin/merch list thumbnails and /admin/useful-links group headers +
   inline icons. Reuses the surrounding .bs-list-card layout — these are just
   accessory bits inside the row. */
.bs-admin-merch-thumb {
    width: 56px;
    height: 56px;
    border-radius: 12px;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    flex: 0 0 auto;
    color: var(--text-muted);
}
.bs-admin-merch-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.bs-admin-links-groupHead {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin: 20px 0 8px;
}
.bs-admin-links-groupHead:first-of-type { margin-top: 8px; }
.bs-admin-links-icon {
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background: var(--surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    flex: 0 0 auto;
    /* Overflow clip so an <img> child fills the rounded slot cleanly when
       a useful-link carries an ImageUrl (falls back to the group MudIcon
       otherwise). */
    overflow: hidden;
}
.bs-admin-links-icon img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.bs-admin-links-url {
    color: var(--text-muted);
    text-decoration: none;
    max-width: 30ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-admin-links-url:hover { color: var(--accent); text-decoration: underline; }

/* Profile "More from the studio" — subdued discovery strip for /merch and
   /links. Rendered below the primary actions so it reads as an addendum
   rather than a competing block. Icon + label, hairline border only. */
.bs-profile-more {
    margin-top: 24px;
    padding-top: 16px;
    border-top: var(--border);
}
.bs-profile-more-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}
.bs-profile-more-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-pill);
    color: var(--text);
    text-decoration: none;
    font-size: 14px;
    transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.bs-profile-more-link:hover,
.bs-profile-more-link:focus-visible {
    background: var(--surface-raised);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--surface-raised));
}
.bs-profile-more-link .mud-icon-root {
    font-size: 18px;
    color: var(--text-muted);
}

/* Draggable admin rows — native HTML5 DnD on desktop, up/down icon
   buttons on touch/keyboard. The grip is the affordance; the row itself
   is only draggable because native DnD requires the draggable attribute
   on the source element. */
.bs-reorderable { transition: opacity var(--dur-fast) var(--ease); }
.bs-reorderable.is-dragging {
    opacity: 0.5;
    outline: 2px dashed color-mix(in srgb, var(--accent) 40%, transparent);
    outline-offset: -2px;
}
.bs-reorder-grip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 32px;
    color: var(--text-muted);
    cursor: grab;
    flex: 0 0 auto;
    touch-action: none;
}
.bs-reorder-grip:active { cursor: grabbing; }
.bs-reorder-grip .mud-icon-root { font-size: 20px; }

/* ==================================================================
   InlineTextEditor - per-string admin editor for landing copy (m105).
   Sits inline with the surrounding text (headings, paragraphs, buttons)
   so it doesn't disrupt page layout when edit mode is on. Blend-in style:
   the input inherits font/color so the admin edits text that looks like
   the text; a dashed accent underline marks it as editable. Save/reset
   surface only when dirty to keep the page quiet.
   ================================================================== */
.bs-inline-text-editor {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    max-width: 100%;
    vertical-align: baseline;
}
.bs-inline-text-input {
    /* Match the surrounding text - no MudBlazor chrome. */
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    line-height: inherit;
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    border: 0;
    border-bottom: 1px dashed var(--accent);
    border-radius: 4px 4px 0 0;
    padding: 2px 6px;
    margin: 0;
    min-width: 4ch;
    max-width: 100%;
    box-sizing: border-box;
    /* field-sizing:content grows the input with its content in modern
       browsers, so a one-word label stays compact and a long headline
       still fits. Older browsers fall back to the default 20ch size, which
       is fine for the copy we edit here. */
    field-sizing: content;
}
.bs-inline-text-input:focus {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.bs-inline-text-input:disabled {
    opacity: 0.6;
    cursor: progress;
}
textarea.bs-inline-text-input {
    /* Textarea variant - break out of inline baseline so it wraps cleanly
       inside prose paragraphs without the button row landing on the last
       text line. */
    display: block;
    width: 100%;
    min-height: 3.2em;
    line-height: 1.5;
    font-family: inherit;
    resize: vertical;
}
.bs-inline-text-editor.is-dirty .bs-inline-text-input {
    border-bottom-style: solid;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.bs-inline-text-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--surface-raised);
    border-radius: 6px;
    cursor: pointer;
    flex: 0 0 auto;
    transition: background var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
}
.bs-inline-text-btn:hover:not(:disabled) {
    background: var(--surface-raised);
}
.bs-inline-text-btn.is-save {
    background: var(--accent);
    color: var(--accent-ink);
    border-color: var(--accent);
}
.bs-inline-text-btn.is-save:hover:not(:disabled) {
    background: color-mix(in srgb, var(--accent) 85%, black);
}
.bs-inline-text-btn:disabled {
    opacity: 0.55;
    cursor: progress;
}
.bs-inline-text-btn .mud-icon-root { font-size: 16px; }

/* Attribute-only text (img alt, iframe title) that can't host an inline
   editor. Renders as a small labelled block near the element it describes,
   visible only when admin edit mode is on. Reuses the InlineTextEditor for
   the input; this rule just gives the block a card-like frame that reads
   as "not part of the page's real content". */
.bs-inline-attr-editor {
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    border: 1px dashed var(--surface-raised);
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-width: 100%;
}
.bs-inline-attr-editor .bs-eyebrow {
    color: var(--text-muted);
}

/* ============================================================
   Plans showcase (/plans) + landing "Membership" preview.
   Restrained pricing layout: neutral card surface, one accent
   moment (the "Contact the studio" CTA at the page bottom).
   Grid auto-flows 1 → 2 → 3 columns as viewport grows.
   ============================================================ */
.bs-plans-head {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1.75rem;
}
.bs-plans-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    letter-spacing: -0.02em;
    color: var(--text);
    line-height: 1.15;
    margin: 0;
}
.bs-plans-sub {
    color: var(--text-muted);
    font-size: clamp(0.95rem, 1.1vw, 1.05rem);
    max-width: 60ch;
    margin: 0;
    line-height: 1.5;
}

.bs-plans-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 1rem;
    margin-bottom: 2rem;
}

.bs-plan-card {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    transition: transform var(--dur-fast, 140ms) var(--ease, ease),
                border-color var(--dur-fast, 140ms) var(--ease, ease);
}
.bs-plan-card:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--surface-raised));
}
.bs-plan-card-eyebrow {
    color: var(--text-muted);
}
.bs-plan-card-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.375rem;
    letter-spacing: -0.01em;
    color: var(--text);
    margin: 0.15rem 0 0;
    line-height: 1.2;
}
.bs-plan-card-price {
    display: flex;
    align-items: baseline;
    gap: 0.25rem;
    color: var(--accent);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    padding: 0.25rem 0;
}
.bs-plan-card-currency {
    font-size: 1.5rem;
    font-weight: 500;
}
.bs-plan-card-amount {
    font-size: 2.75rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
}
.bs-plan-card-lines {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    color: var(--text);
    font-size: 0.95rem;
}
.bs-plan-card-lines li {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
.bs-plan-card-lines .mud-icon-root {
    color: var(--text-muted);
    flex-shrink: 0;
}

.bs-plans-how {
    padding: 1.25rem 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    margin-bottom: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.bs-plans-how-body {
    margin: 0;
    color: var(--text);
    line-height: 1.55;
    max-width: 70ch;
}

.bs-plans-cta {
    display: flex;
    justify-content: center;
    padding: 1rem 0 3rem;
}

.bs-plans-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 3rem 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    text-align: center;
}
.bs-plans-empty .mud-icon-root {
    color: var(--text-muted);
}
.bs-plans-empty-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.25rem;
    margin: 0.25rem 0 0;
    color: var(--text);
}
.bs-plans-empty-sub {
    color: var(--text-muted);
    max-width: 40ch;
    margin: 0 0 0.5rem;
}

/* ---- Landing "Membership" preview strip ---------------------- */
.bs-landing-plans-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1rem;
    margin-top: 1.5rem;
    /* auto-fit tracks default to min-content on each side; without a
       zero min-width the grid track expands past the phone viewport and
       the second plan card runs off the right edge (see mobile bug: two
       cards showing where one should have wrapped). */
    min-width: 0;
}
/* Narrow phones cannot fit even a single 240px track once padding is
   included on some devices, so force a single-column stack below the
   auto-fit breakpoint. */
@media (max-width: 600px) {
    .bs-landing-plans-grid { grid-template-columns: 1fr; }
}
.bs-landing-plan-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.25rem 1.5rem;
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-card, 16px);
    color: var(--text);
    text-decoration: none;
    transition: transform var(--dur-fast, 140ms) var(--ease, ease),
                border-color var(--dur-fast, 140ms) var(--ease, ease);
}
.bs-landing-plan-card:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--surface-raised));
}
.bs-landing-plan-card-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.15rem;
    letter-spacing: -0.01em;
}
.bs-landing-plan-card-price {
    display: flex;
    align-items: baseline;
    gap: 0.2rem;
    /* accent-strong (not --accent) so the "€35" reads as vibrant brand red
       on OLED Android too — AA on 600-weight red glyphs at 32px would
       otherwise desaturate to pink. */
    color: var(--accent-strong);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
}
.bs-landing-plan-card-currency {
    font-size: 1.15rem;
}
.bs-landing-plan-card-amount {
    font-size: 2rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
}
.bs-landing-plan-card-meta {
    color: var(--text-muted);
    font-size: 0.9rem;
}
.bs-landing-plan-card-sep {
    padding: 0 0.35rem;
    color: var(--surface-raised);
}
.bs-landing-cta-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    /* Same reason as .bs-landing-plan-card-price / .bs-landing-eyebrow-v2:
       accent-as-text at 600 weight loses saturation on OLED Android. */
    color: var(--accent-strong);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.95rem;
    align-self: end;
}
.bs-landing-cta-link:hover {
    text-decoration: underline;
}

/* -------- Leaderboard (see Pages/Leaderboard.razor) --------
   Public battle-points ranking. Three visual slabs: podium (top 3), long-tail
   row list (rank 4+), and a footnote. Design constraints per CLAUDE.md:
     - One accent-filled moment per screen. The champion's avatar ring is it;
       every other border / rank chip / row uses surface-raised so hierarchy
       carries the ordinal without repainting the whole page red.
     - Numbers are JetBrains Mono with tabular figures so rank labels and
       point totals line up regardless of digit count.
     - No trophies, no confetti, no gradient bling. The physical podium
       metaphor (2 left, 1 centre + elevated, 3 right) does the work of a
       badge or icon. */
.bs-lb-hero { display: grid; gap: 0.4rem; }
.bs-lb-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.75rem, 3vw, 3.25rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.05;
    margin: 0.25rem 0 0;
}
.bs-lb-sub {
    color: var(--text-muted);
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    line-height: 1.5;
    margin: 0.35rem 0 0;
    max-width: 62ch;
}

/* Podium: 3-column grid with the champion cell taller and centred. On
   mobile the grid collapses to a single column and the visual order flips
   back to 1 -> 2 -> 3 (via order) so ranking reads top-to-bottom naturally. */
.bs-lb-podium {
    display: grid;
    grid-template-columns: 1fr 1.2fr 1fr;
    align-items: end;
    gap: clamp(0.5rem, 1.5vw, 1rem);
    margin-top: clamp(1.5rem, 3vw, 2.5rem);
}
.bs-lb-podium-cell {
    all: unset;
    cursor: pointer;
    box-sizing: border-box;
    display: grid;
    justify-items: center;
    gap: 0.4rem;
    padding: clamp(1rem, 2vw, 1.5rem) 0.75rem clamp(1.25rem, 2vw, 1.75rem);
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    text-align: center;
    transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.bs-lb-podium-cell:hover:not(:disabled) { transform: translateY(-2px); border-color: var(--text-muted); }
.bs-lb-podium-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Tier tokens per podium slot — same bronze / silver / gold tokens the
   badge medals use, so the "top three" reads consistently across the app.
   Ranks 1/2/3 pick up an 18% diagonal patina + a solid tier ring;
   champion (rank 1) keeps its extra height + heavier ring so first
   place still dominates the trio visually. */
.bs-lb-podium-1 { --tier-hue: var(--tier-gold); }
.bs-lb-podium-2 { --tier-hue: var(--tier-silver); }
.bs-lb-podium-3 { --tier-hue: var(--tier-bronze); }
.bs-lb-podium-1,
.bs-lb-podium-2,
.bs-lb-podium-3 {
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--tier-hue) 18%, transparent) 0%,
            transparent 55%),
        var(--surface);
    border-color: var(--tier-hue);
}
.bs-lb-podium-1 {
    outline: 2px solid var(--tier-hue);
    outline-offset: 2px;
    padding-top: clamp(1.25rem, 2.5vw, 2rem);
    padding-bottom: clamp(1.5rem, 2.5vw, 2.25rem);
}
.bs-lb-podium-2, .bs-lb-podium-3 { padding-block: clamp(0.75rem, 1.5vw, 1.25rem); }
.bs-lb-podium-cell.is-me { border-left: 3px solid var(--accent); }

.bs-lb-podium-overline {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(0.6875rem, 0.75vw, 0.8125rem);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1;
}
.bs-lb-podium-1 .bs-lb-podium-overline,
.bs-lb-podium-2 .bs-lb-podium-overline,
.bs-lb-podium-3 .bs-lb-podium-overline { color: var(--tier-hue); }

.bs-lb-podium-rank {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-size: clamp(0.9375rem, 1vw, 1.0625rem);
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
.bs-lb-podium-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(0.95rem, 1.25vw, 1.15rem);
    letter-spacing: -0.01em;
    color: var(--text);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bs-lb-podium-1 .bs-lb-podium-name { font-size: clamp(1.1rem, 1.5vw, 1.4rem); }
.bs-lb-podium-points {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    margin-top: 0.15rem;
}
.bs-lb-podium-points-num {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: clamp(1.4rem, 2.4vw, 2.1rem);
    color: var(--text);
    letter-spacing: -0.02em;
    line-height: 1;
}
/* Points number in the tier hue on every podium slot. Rank 1 keeps its
   size bump so gold reads as the tallest number in the trio. */
.bs-lb-podium-1 .bs-lb-podium-points-num,
.bs-lb-podium-2 .bs-lb-podium-points-num,
.bs-lb-podium-3 .bs-lb-podium-points-num { color: var(--tier-hue); }
.bs-lb-podium-1 .bs-lb-podium-points-num {
    font-size: clamp(1.8rem, 3vw, 2.75rem);
}
.bs-lb-podium-points-unit {
    color: var(--text-muted);
    font-size: clamp(0.75rem, 0.85vw, 0.9rem);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}
.bs-lb-podium-wins {
    color: var(--text-muted);
    font-size: clamp(0.75rem, 0.85vw, 0.9rem);
}

/* Skeleton variant - keeps grid slots reserved so the layout doesn't jump. */
.bs-lb-podium.is-skeleton { pointer-events: none; }
.bs-lb-podium.is-skeleton .bs-lb-podium-cell { min-height: 180px; }
.bs-lb-podium.is-skeleton .bs-lb-skel { width: 60%; height: 40%; border-radius: var(--radius-input); }

/* Avatar puck. Solo variant reused inside the row list (56px) and enlarged
   for the podium via .bs-lb-avatar-lg. The champion gets an accent ring
   drawn with box-shadow so it stays outside the border-radius crop. */
.bs-lb-avatar {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-raised);
    display: grid;
    place-items: center;
    color: var(--text);
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    font-size: 1.4rem;
    text-transform: uppercase;
    flex-shrink: 0;
}
.bs-lb-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bs-lb-avatar-lg { width: clamp(72px, 8vw, 96px); height: clamp(72px, 8vw, 96px); font-size: clamp(1.6rem, 2.2vw, 2.1rem); }
/* Tier rings on the podium avatars — 3px outline for gold (loudest),
   2px for silver / bronze. Same tokens as the badge medals. */
.bs-lb-avatar.is-rank-gold { --tier-hue: var(--tier-gold); }
.bs-lb-avatar.is-rank-silver { --tier-hue: var(--tier-silver); }
.bs-lb-avatar.is-rank-bronze { --tier-hue: var(--tier-bronze); }
.bs-lb-avatar.is-rank-gold {
    outline: 3px solid var(--tier-hue);
    outline-offset: 2px;
    color: var(--tier-hue);
}
.bs-lb-avatar.is-rank-silver,
.bs-lb-avatar.is-rank-bronze {
    outline: 2px solid var(--tier-hue);
    outline-offset: 2px;
    color: var(--tier-hue);
}

/* Row list - one clickable button per row. Own row gets a subtle accent
   left stripe (same primitive as ClassCard.is-mine) so a member spots
   themselves quickly without repainting the row. */
.bs-lb-list {
    display: grid;
    gap: 0.5rem;
}
.bs-lb-row {
    all: unset;
    box-sizing: border-box;
    cursor: pointer;
    display: grid;
    grid-template-columns: 3.5rem 56px 1fr auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-left: 3px solid transparent;
    border-radius: var(--radius-card);
    transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.bs-lb-row:hover { border-color: var(--text-muted); }
.bs-lb-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bs-lb-row.is-me { border-left-color: var(--accent); background: rgba(245, 36, 40, 0.05); }
/* Tier tint on rank-1/2/3 rows — matters in the fallback path where there
   are fewer than three ranked members (no podium is rendered, top ranks
   land in the row list). Same tokens as the podium so both layouts look
   like one hierarchy. */
.bs-lb-row.is-rank-gold { --tier-hue: var(--tier-gold); }
.bs-lb-row.is-rank-silver { --tier-hue: var(--tier-silver); }
.bs-lb-row.is-rank-bronze { --tier-hue: var(--tier-bronze); }
.bs-lb-row.is-rank-gold,
.bs-lb-row.is-rank-silver,
.bs-lb-row.is-rank-bronze {
    border-color: var(--tier-hue);
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--tier-hue) 12%, transparent) 0%,
            transparent 45%),
        var(--surface);
}
.bs-lb-row.is-rank-gold .bs-lb-row-rank,
.bs-lb-row.is-rank-silver .bs-lb-row-rank,
.bs-lb-row.is-rank-bronze .bs-lb-row-rank { color: var(--tier-hue); font-weight: 600; }
.bs-lb-row.is-rank-gold .bs-lb-row-points-num,
.bs-lb-row.is-rank-silver .bs-lb-row-points-num,
.bs-lb-row.is-rank-bronze .bs-lb-row-points-num { color: var(--tier-hue); }
.bs-lb-row.is-rank-gold .bs-lb-avatar,
.bs-lb-row.is-rank-silver .bs-lb-avatar,
.bs-lb-row.is-rank-bronze .bs-lb-avatar {
    outline: 2px solid var(--tier-hue);
    outline-offset: 2px;
    color: var(--tier-hue);
}
.bs-lb-row.is-skeleton { min-height: 72px; }
.bs-lb-row.is-skeleton .bs-lb-skel { width: 100%; height: 24px; border-radius: var(--radius-input); }

.bs-lb-row-rank {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 1.05rem;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    text-align: right;
}
.bs-lb-row-body { min-width: 0; display: grid; gap: 0.15rem; }
.bs-lb-row-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.0625rem;
    letter-spacing: -0.005em;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.bs-lb-row-you {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--accent);
    padding: 2px 6px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-pill);
    line-height: 1;
}
.bs-lb-row-meta { color: var(--text-muted); font-size: 0.85rem; }
.bs-lb-row-points {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    justify-self: end;
}
.bs-lb-row-points-num {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 1.35rem;
    color: var(--text);
    letter-spacing: -0.01em;
}
.bs-lb-row.is-me .bs-lb-row-points-num { color: var(--accent); }
.bs-lb-row-points-unit {
    color: var(--text-muted);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.bs-lb-footnote {
    color: var(--text-muted);
    font-size: 0.875rem;
    line-height: 1.5;
    max-width: 62ch;
}

/* Mobile: collapse the podium to a single column, restore ranked order so
   the viewer reads 1 -> 2 -> 3 top-to-bottom (the flanking layout only
   makes sense on a wide row). Rows tighten their column template so a
   long point total doesn't push the rank number off screen. */
@media (max-width: 640px) {
    .bs-lb-podium { grid-template-columns: 1fr; gap: 0.75rem; }
    .bs-lb-podium-1 { order: 1; }
    .bs-lb-podium-2 { order: 2; }
    .bs-lb-podium-3 { order: 3; }
    .bs-lb-podium-cell { padding-block: 1rem; }

    .bs-lb-row {
        grid-template-columns: 2.25rem 44px 1fr auto;
        gap: 0.55rem;
        padding: 0.65rem 0.75rem;
    }
    .bs-lb-avatar { width: 44px; height: 44px; font-size: 1.1rem; }
    .bs-lb-row-rank { font-size: 0.95rem; }
    .bs-lb-row-name { font-size: 1rem; }
    .bs-lb-row-points-num { font-size: 1.2rem; }
}

/* Reduced-motion: honor the CLAUDE.md §4 rule by dropping the hover lift
   on the podium so the page stays visually still. Focus rings and colour
   transitions are still allowed - they carry meaning, not decoration. */
@media (prefers-reduced-motion: reduce) {
    .bs-lb-podium-cell:hover { transform: none; }
}

/* -------- Rank chip (profile display, see Profile.razor / PublicProfile.razor) --------
   Compact "you are Nth" chip sitting beside the identity block. Rendered
   only when the member has non-zero points - no rank means the chip stays
   hidden and the profile shows nothing (spec §9 empty-state rule). */
.bs-rank-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.6rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    text-decoration: none;
    color: var(--text);
    transition: border-color var(--dur-fast) var(--ease);
}
.bs-rank-chip:hover { border-color: var(--text-muted); }
.bs-rank-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Rank-tier chip variants — same tokens as the podium + profile hero.
   Gold gets an outline stroke on top of the border ring (matches the
   podium-1 treatment), silver and bronze use just the tier-tinted
   border so they read as calmer within the same visual vocabulary. */
.bs-rank-chip.is-rank-gold { --tier-hue: var(--tier-gold); }
.bs-rank-chip.is-rank-silver { --tier-hue: var(--tier-silver); }
.bs-rank-chip.is-rank-bronze { --tier-hue: var(--tier-bronze); }
.bs-rank-chip.is-rank-gold,
.bs-rank-chip.is-rank-silver,
.bs-rank-chip.is-rank-bronze {
    border-color: var(--tier-hue);
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--tier-hue) 14%, transparent) 0%,
            transparent 55%),
        var(--surface);
}
.bs-rank-chip.is-rank-gold { outline: 2px solid var(--tier-hue); outline-offset: 1px; }
.bs-rank-chip-slot {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 1.35rem;
    color: var(--text);
    letter-spacing: -0.01em;
    line-height: 1;
}
.bs-rank-chip.is-rank-gold .bs-rank-chip-slot,
.bs-rank-chip.is-rank-silver .bs-rank-chip-slot,
.bs-rank-chip.is-rank-bronze .bs-rank-chip-slot { color: var(--tier-hue); }
.bs-rank-chip-slot-hash { color: var(--text-muted); margin-right: 1px; }
.bs-rank-chip-slot-total { color: var(--text-muted); font-size: 0.9rem; }
.bs-rank-chip-body { display: grid; gap: 0.1rem; }
.bs-rank-chip-label {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
    line-height: 1;
}
.bs-rank-chip-value {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 0.95rem;
    color: var(--text);
    letter-spacing: -0.005em;
}

/* -------- Admin points panel (see Pages/Admin/EditUser.razor) --------
   Award-history list plus quick add row. Rows use the same visual grammar
   as the leaderboard list so admin + member views feel like one system. */
.bs-points-panel { display: grid; gap: 0.75rem; }
.bs-points-summary {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    background: var(--surface-raised);
    border-radius: var(--radius-input);
}
.bs-points-summary-num {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 1.5rem;
    color: var(--text);
    letter-spacing: -0.01em;
}
.bs-points-summary-label { color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em; }
.bs-points-row {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
}
.bs-points-row-points {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 1.05rem;
    letter-spacing: -0.005em;
    min-width: 3.5rem;
    text-align: right;
}
.bs-points-row-points.is-positive { color: var(--success); }
.bs-points-row-points.is-negative { color: var(--danger); }
.bs-points-row-reason { color: var(--text); font-size: 0.9375rem; }
.bs-points-row-date { color: var(--text-muted); font-size: 0.8125rem; }

/* ========================================================================
   Crew profile page (/crew/{id})
   Marketing/reader lane - portrait hero + long-form bio + upcoming strip.
   Lives on the same shell that renders the landing so the transition from
   card-tap to page reads as one motion.
   ======================================================================== */
.bs-crew-page {
    max-width: 1200px;
    margin: 0 auto;
    padding: clamp(16px, 3vw, 24px) clamp(16px, 3vw, 24px) 96px;
    display: flex;
    flex-direction: column;
    gap: clamp(32px, 5vw, 64px);
}
.bs-crew-page-back {
    /* PageBack renders its own chip; scope is just alignment. */
    margin-bottom: -8px;
}

/* Hero: two-column on desktop (portrait left, text right), stacked on
   mobile. Portrait uses the same grade as the landing crew tile so the
   card-to-page transition doesn't shift the photograph's colour. */
.bs-crew-hero {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: clamp(24px, 4vw, 48px);
    align-items: start;
}
.bs-crew-hero-media {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 5;
    max-height: 620px;
    border-radius: var(--radius-card);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    background-color: var(--surface-raised);
    filter: saturate(0.85) contrast(1.05);
    border: 1px solid var(--surface-raised);
}
.bs-crew-hero-body {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-top: 8px;
}
.bs-crew-hero-eyebrow {
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
}
.bs-crew-hero-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-weight: 800;
    /* display: 28→56px scaled to viewport; caps so 2K doesn't oversize. */
    font-size: clamp(2rem, 5vw, 3.5rem);
    letter-spacing: -0.02em;
    line-height: 1.05;
    color: var(--text);
    text-transform: uppercase;
    margin: 0;
}
.bs-crew-hero-bio {
    display: flex;
    flex-direction: column;
    gap: 14px;
    color: color-mix(in srgb, var(--text) 85%, transparent);
    font-size: clamp(0.95rem, 1vw, 1.0625rem);
    line-height: 1.65;
    max-width: 62ch;
}
.bs-crew-hero-bio p {
    margin: 0;
    white-space: pre-wrap;
}
@media (max-width: 780px) {
    .bs-crew-hero {
        grid-template-columns: 1fr;
        gap: 20px;
    }
    .bs-crew-hero-media {
        aspect-ratio: 4 / 5;
        max-height: 480px;
    }
}

/* Sections below the hero share alignment with the hero-body column so
   the eye-line stays consistent as the reader scrolls. */
.bs-crew-section {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Teaching-next: horizontal chip strip. Overflows horizontally on mobile
   (scrolls with touch); wraps on desktop so 6-8 upcoming reads as a
   composed group not a single line. */
.bs-crew-next {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}
.bs-crew-next-chip {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "day     time"
        "name    tags"
        "name    tags";
    align-items: center;
    gap: 4px 12px;
    padding: 14px 16px;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-card);
    background: var(--surface);
    color: var(--text);
    text-decoration: none;
    min-width: 260px;
    flex: 1 1 300px;
    max-width: 420px;
    transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.bs-crew-next-chip:hover {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--surface-raised));
    transform: translateY(-1px);
}
.bs-crew-next-chip:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.bs-crew-next-day {
    grid-area: day;
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: -0.005em;
    color: var(--text);
}
.bs-crew-next-time {
    grid-area: time;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.8125rem;
    color: var(--text-muted);
}
.bs-crew-next-name {
    grid-area: name;
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
    line-height: 1.25;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.bs-crew-next-tags {
    grid-area: tags;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    justify-content: flex-end;
}
.bs-crew-next-level {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: 2px 8px;
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-pill);
}
.bs-crew-next-sub {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--warning);
    padding: 2px 8px;
    border: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--warning) 10%, transparent);
}

/* Single-accent CTA at the bottom, mirroring the "one filled button per
   screen" rule. Everything else on this page is outline / ghost. */
.bs-crew-cta-row {
    margin-top: 8px;
    flex-direction: row;
    justify-content: flex-start;
}
.bs-crew-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 12px 22px;
    background: var(--accent);
    color: var(--accent-ink);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: -0.005em;
    border-radius: var(--radius-input);
    text-decoration: none;
    transition: transform var(--dur-fast) var(--ease), filter var(--dur-fast) var(--ease);
}
.bs-crew-cta:hover { filter: brightness(1.08); transform: translateY(-1px); }
.bs-crew-cta:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* Loading skeleton - shape matches the final hero + text lines so
   there's no reflow jump when the fetch completes. */
.bs-crew-skel {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: clamp(24px, 4vw, 48px);
    align-items: start;
}
.bs-crew-skel-hero {
    aspect-ratio: 4 / 5;
    max-height: 620px;
    border-radius: var(--radius-card);
    background: linear-gradient(90deg,
        var(--surface) 0%,
        var(--surface-raised) 50%,
        var(--surface) 100%);
    background-size: 200% 100%;
    animation: bs-crew-shimmer 1.4s linear infinite;
    border: 1px solid var(--surface-raised);
}
.bs-crew-skel-lines {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 16px;
}
.bs-crew-skel-line {
    height: 14px;
    border-radius: 6px;
    background: linear-gradient(90deg,
        var(--surface) 0%,
        var(--surface-raised) 50%,
        var(--surface) 100%);
    background-size: 200% 100%;
    animation: bs-crew-shimmer 1.4s linear infinite;
}
.bs-crew-skel-line-eyebrow { width: 22%; height: 10px; }
.bs-crew-skel-line-title   { width: 78%; height: 40px; margin: 6px 0 12px; }
.bs-crew-skel-line-body    { width: 96%; }
.bs-crew-skel-line-body-short { width: 62%; }
@keyframes bs-crew-shimmer {
    0%   { background-position:  100% 0; }
    100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .bs-crew-skel-hero,
    .bs-crew-skel-line {
        animation: none;
        background: var(--surface-raised);
    }
}
@media (max-width: 780px) {
    .bs-crew-skel {
        grid-template-columns: 1fr;
        gap: 20px;
    }
    .bs-crew-skel-hero { aspect-ratio: 4 / 5; max-height: 480px; }
}

/* Not-found + error - same silhouette so an unhealthy load doesn't
   dead-end the visitor. CTA is outline (still one accent per screen -
   the accent lives on the eyebrow and focus ring). */
.bs-crew-empty {
    max-width: 60ch;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 48px 0;
}
.bs-crew-empty-title {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: clamp(1.5rem, 3vw, 2rem);
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--text);
    margin: 0;
}
.bs-crew-empty-body {
    color: var(--text-muted);
    font-size: 0.9375rem;
    line-height: 1.55;
    margin: 0;
}
.bs-crew-empty-cta {
    align-self: flex-start;
    margin-top: 12px;
    padding: 10px 18px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    background: transparent;
    color: var(--text);
    border: 1px solid var(--surface-raised);
    border-radius: var(--radius-input);
    font-family: 'Commissioner', system-ui, sans-serif;
    font-size: 0.9375rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease);
}
.bs-crew-empty-cta:hover {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--surface-raised));
}
.bs-crew-empty-cta:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

