/* SubSloth Dashboard — Modern MudBlazor styling */

/* ── Design-Tokens (STYLE.md §3) ──────────────────────────────────────────────
   Alle Tokens leiten sich aus den --mud-palette-*-Variablen ab, die der
   MudThemeProvider (SubSlothTheme) generiert — sie flippen daher automatisch
   mit dem Light/Dark-Toggle. Keine festen Hex-Werte für Flächen/Text/Borders! */
:root {
    /* Flächen */
    --app-surface-card:   var(--mud-palette-surface);
    --app-surface-inner:  var(--mud-palette-background);
    --app-surface-raised: var(--mud-palette-drawer-background);
    --app-border:         var(--mud-palette-lines-default);
    --app-text-muted:     var(--mud-palette-text-secondary);
    /* Dezentes "Anheben" Richtung Textfarbe — hellt im Dark Mode auf, dunkelt im Light Mode ab */
    --app-hover-bg:       color-mix(in srgb, var(--mud-palette-text-primary) 5%, transparent);

    /* Semantische Status-Farben (Case-Workflow) */
    --app-status-open:          var(--mud-palette-error);
    --app-status-investigating: var(--mud-palette-warning);
    --app-status-closed:        var(--mud-palette-success);

    /* Bewusst modus-unabhängige Akzente (funktionieren auf hell & dunkel).
       Werte kommen aus dem Heroes-Lounge-DS, wo es ein Äquivalent gibt (Kommentar nennt
       das DS-Token); der Rest ist als DS-Lücke markiert und wird im Rework geklärt. */
    --app-on-dark:       var(--fg-on-dark);        /* Text auf dauerhaft dunklen Flächen (Map-Banner, Tooltips) */
    --app-on-dark-muted: var(--fg-on-dark-muted);  /* gedämpfter Text auf dauerhaft dunklen Bannern/Overlays */
    --app-team-home:     var(--hl-team-a);         /* Team-Akzent home (Ban-/Pick-Tiles) */
    --app-team-away:     var(--hl-team-b);         /* Team-Akzent away */
    --app-blacklisted:   var(--hl-violet-600);     /* Blacklist-Chip */
    --app-banned:        var(--hl-red-600);        /* Gebannte Spieler */
    /* DS-Lücke: kein Gold-/Critical-Äquivalent im DS. Bewusst beibehalten, bis das DS
       einen Winner- bzw. Critical-Ton definiert (siehe docs/plans/2026-07-28-ui-rework-design-system-plan.md). */
    --app-gold: #ffd700;          /* Winner/MVP-Highlight — DS-Lücke */
    --app-critical: #9b1c31;      /* Critical-Chip — DS-Lücke */

    /* Skeleton pulse — richtet sich nach MudBlazors Skeleton-Default, darf aber nicht
       hardcoded übernommen werden; nutzt Overlay aus Text-Farbe für Theme-Flip. */
    --app-skeleton-bg: color-mix(in srgb, var(--mud-palette-text-primary) 8%, transparent);

    /* Case-Tracking-Status-Marker (Violation-Banner + Legende).
       Waren vier Rohfarben aus der Zeit vor dem DS. Der Rework-Plan §6.2 hielt fest, dass
       das DS für vier Stufen zu wenig Töne hat — „late" und „deadline" wären beide gelblich.
       Die Auflösung liegt im Bestand selbst: jeder der sechs Zustände trägt bereits ein
       eigenes Icon (CheckCircle, AccessTime, Timer, Gavel, Warning, Report). Die Farbe
       unterscheidet also nichts, was das Icon nicht schon unterscheidet, und kann auf die
       semantischen Palette-Rollen fallen — die zudem mit dem Theme flippen.
       „late" und „deadline" teilen sich bewusst das Amber; getrennt bleiben die Namen, weil
       die Zustände getrennt sind. */
    --app-track-resolved: var(--mud-palette-success);   /* rechtzeitig registriert */
    --app-track-late:     var(--mud-palette-warning);   /* Late Registration */
    --app-track-deadline: var(--mud-palette-warning);   /* Deadline läuft */
    --app-track-case:     var(--hl-violet-500);         /* Case existiert — DS-Violett */

    /* Case-Nummer-Akzent in .detail-stats-table (z.B. Cases.razor) */
    --app-case-number: var(--mud-palette-primary);

    /* Alt-Aliase — nur für Bestandscode, in neuem Code die --app-*-Tokens nutzen */
    --card-bg: var(--app-surface-card);
    --card-border: var(--app-border);
    --table-bg: var(--app-surface-inner);
    --inner-card-bg: var(--app-surface-inner);
    --color-open: var(--app-status-open);
    --color-investigating: var(--app-status-investigating);
    --color-closed: var(--app-status-closed);
    --color-case-number: var(--mud-palette-primary-lighten);
    --color-muted: var(--app-text-muted);
    --color-critical: var(--app-critical);
    --color-blacklisted: var(--app-blacklisted);
}

/* ── Layout-Chrome (AppBar/Drawer) — Hintergrund kommt aus der Palette ── */
.app-appbar {
    backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--app-border);
}

.app-drawer {
    border-right: 1px solid var(--app-border);
}

/* Marke in der AppBar: HL-Symbol + SubSloth-Wortmarke (BrandMark.razor).
   Die DS-Lücke aus Rework-Plan §6.6 ist geschlossen — die ursprünglich gelieferten Logos
   waren deckende JPGs ohne Alpha; die jetzigen PNGs sind echt freigestellt.
   Display-Font/Uppercase/Tracking der Wortmarke kommen aus dem Theme (Typo.h6). */
.brand-mark {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

/* Das Symbol ist reines Markenblau und braucht deshalb keine Modus-Variante.
   Höhe an der Wortmarke ausgerichtet, Breite folgt dem Seitenverhältnis (654×752). */
.brand-mark__symbol {
    height: 24px;
    width: auto;
    display: block;
}

.app-wordmark {
    color: var(--mud-palette-appbar-text);
}

/* Volles HL-Lockup auf der Login-Fläche (§5.1). Zwei Tinten, in Markup umgeschaltet —
   MudBlazor schreibt seine Palette in :root und lässt keinen CSS-Hook für einen
   Bildwechsel. Breite gedeckelt, damit es auf der 420px-Karte Luft behält. */
.brand-lockup {
    width: 100%;
    max-width: 240px;
    height: auto;
    display: block;
}

/* ── Sidebar-Navigation (DS: ui_kits/subsloth Sidebar.jsx) ──────────────────────
   Das DS zeichnet die Sidebar mit uppercase Display-Sektionslabels und einem
   aktiven Eintrag, der als abgesetzte Akzentfläche mit Seitenluft sitzt — nicht
   als randlose Vollbreiten-Markierung. */
.nav-section-label {
    display: block;
    font-family: var(--font-display);
    font-size: var(--app-fs-micro);
    font-weight: var(--app-fw-medium);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--app-text-muted);
}

/* Der Play/Moderate-Umschalter sitzt über den Sektionen und trennt die beiden Hälften des
   Menüs. Volle Breite, damit er als Umschalter und nicht als weiterer Menüeintrag liest, und
   mit einer Trennlinie darunter, weil alles Folgende von ihm abhängt. */
.nav-mode-switch {
    border-bottom: 1px solid var(--app-border);
}

.nav-mode-switch__group {
    width: 100%;
}

.nav-mode-switch__group .mud-toggle-item {
    flex: 1 1 0;
    justify-content: center;
}

.app-drawer .mud-nav-link.active:not(.mud-nav-link-disabled) {
    background: var(--surface-accent-active);
    color: var(--fg-on-dark);
    border-radius: var(--app-radius-sm);
    /* Seitenluft, damit die Fläche als Kachel und nicht als Vollbreiten-Balken liest */
    margin-inline: var(--space-2);
}

.app-drawer .mud-nav-link.active:not(.mud-nav-link-disabled) .mud-nav-link-icon {
    color: var(--fg-on-dark);
}

/* ── Font-Weight-Utilities (STYLE.md §3.2) ── */
.fw-medium   { font-weight: 500; }
.fw-semibold { font-weight: 600; }
.fw-bold     { font-weight: 700; }
.fw-black    { font-weight: 800; }

/* Stat-number status colors (Moderation pages) — map to the canonical --app-status-* tokens. */
.text-status-open          { color: var(--app-status-open); }
.text-status-investigating { color: var(--app-status-investigating); }
.text-status-closed        { color: var(--app-status-closed); }
.text-status-blacklisted   { color: var(--app-blacklisted); }

/* Der Zähler, der beim Freischalten der Nutzungsstatistik herunterzählt. Sitzt rechts im Nav-Eintrag
   und nimmt keine eigene Zeile, damit der Eintrag beim Zählen nicht springt. */
.usage-unlock { position: relative; }

.usage-unlock__counter {
    margin-left: auto;
    min-width: 1.5rem;
    padding: 0 .4rem;
    border-radius: 999px;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-size: .75rem;
    font-weight: 600;
    line-height: 1.4rem;
    text-align: center;
}

/* The lounge preview on the tournament editor. A dark panel with the configured accent down its
   left edge — the one visual thing Discord does that matters here, because the accent is one of the
   fields being edited. */
.lobby-preview {
    border: 1px solid var(--app-border);
    border-left: 4px solid var(--lobby-accent, var(--mud-palette-primary));
    border-radius: 8px;
    background: var(--surface-dark);
    padding: 0.75rem 1rem;
}

.lobby-preview__head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}

.lobby-preview__title {
    color: var(--app-on-dark);
    font-weight: 700;
    font-size: 1rem;
}

.lobby-preview__hint {
    color: var(--app-on-dark-muted);
    font-size: var(--text-body-xs);
}

/* Pre, because the post's line breaks are part of it. Wrapping is on so a long map list does not
   push a horizontal scrollbar under the whole editor. */
.lobby-preview__body {
    margin: 0;
    color: var(--app-on-dark);
    font-family: var(--mud-typography-body1-family, monospace);
    font-size: 0.8rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ── Standard-Karten (STYLE.md §4.1) ── */
.app-card {
    background: var(--app-surface-card) !important; /* Mud-Elevation-Hintergrund überschreiben */
    border: 1px solid var(--app-border);
}

.app-card--inner {
    background: var(--app-surface-inner) !important; /* Mud-Elevation-Hintergrund überschreiben */
}

/* ProfileFilterBar popover width — must be GLOBAL, not scoped: MudPopover portals its panel to
   MudPopoverProvider at the app root, outside the component's DOM subtree, so a scoped selector
   (or ::deep) can't reach it. The panel carries this class via MudPopover Class=. Gives the
   three-column season grid room. */
.profile-filter-bar__popover {
    min-width: 320px;
    max-width: 90vw;
}

html, body {
    font-family: var(--font-body); /* DS --font-body (Inter) */
    margin: 0;
    padding: 0;
}

/* Hide number input spinners globally */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

h1:focus {
    outline: none;
}

/* ── Season Selector in AppBar ── */
.mud-appbar .mud-select {
    color: inherit !important;
}

.mud-appbar .mud-input-outlined .mud-input-outlined-border {
    border-color: var(--app-border) !important;
}

.mud-appbar .mud-input-outlined:hover .mud-input-outlined-border {
    border-color: var(--app-text-muted) !important;
}

.mud-appbar .mud-select .mud-input {
    font-size: 13px;
    color: inherit;
}

/* ── Season Selector in Drawer ── */
.season-selector-drawer {
    padding: 12px 12px 8px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--app-border);
}

.season-selector-drawer .mud-button-root {
    border-radius: 8px;
    padding: 6px 12px;
    min-height: 36px;
}

/* ── Status cards with left accent bar ── */
.status-card {
    position: relative;
    overflow: hidden;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    border: 1px solid var(--app-border);
    background: var(--app-surface-card) !important;
}

/* The DS is explicit that hover is "opacity or color shift only, no bounce/spring" and that
   elevation stays flat — the previous translateY(-2px) plus a raw 0 8px 24px shadow was both
   the bounce and the shadow-pop it rules out. A border shift carries the affordance instead. */
.status-card:hover {
    border-color: var(--border-accent);
}

.status-card::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 4px;
    border-radius: 4px 0 0 4px;
}

.status-card.status-active::before { background: var(--mud-palette-success); }
.status-card.status-inactive::before { background: var(--mud-palette-error); }
.status-card.status-info::before { background: var(--mud-palette-primary); }
.status-card.status-warning::before { background: var(--mud-palette-warning); }

/* ── Map Banner ── */
.replay-map-banner {
    position: relative;
    border-radius: 12px;
    overflow: hidden;
    background-size: cover;
    background-position: center;
    background-color: var(--app-surface-card);
    min-height: 160px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.replay-map-banner__overlay {
    position: relative;
    z-index: 1;
    text-align: center;
    padding: 32px 16px;
}

.replay-map-banner::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.6) 100%);
}

.replay-map-banner__build {
    position: absolute;
    bottom: 8px;
    right: 12px;
    z-index: 1;
}

/* ── Match Card ── */

/* ── Draft Order Strip ──
   Used on MatchDetail between GameDetailCard and GameStatsTabs. One horizontal row of compact
   hero tiles grouped by draft phase (ban-burst / pick-burst / ban-burst / pick-burst).
   Border colour encodes the HL team side (already resolved to home/away by GameStatMerge,
   so blue/red here is always = HL home/away, NOT raw in-game Blue/Red). Border style encodes
   ban (dashed, dimmed) vs pick (solid, vivid). Swaps get a small ↻ overlay. */
.draft-strip {
    gap: 0.5rem;
    justify-content: center; /* draft row centred horizontally */
}

.draft-strip__caption { justify-content: center; }

.draft-strip__title { letter-spacing: 0.3px; }

.draft-strip__phase {
    gap: 0.3rem;
}

.draft-strip__phase-sep {
    width: 1px;
    height: 52px;
    background: var(--app-border);
    flex-shrink: 0;
    margin: 0 0.35rem;
}

.draft-strip__tile {
    width: 56px;
    height: 56px;
    border-radius: 50%; /* round, follows the circular hero portrait instead of a box */
    border: 2px solid transparent;
    overflow: hidden;
    position: relative;
    flex-shrink: 0;
    background: var(--app-surface-card);
}

.draft-strip__tile--home { border-color: var(--app-team-home); }
.draft-strip__tile--away { border-color: var(--app-team-away); }

.draft-strip__tile--ban {
    border-style: dashed;
    opacity: 0.85;
}

/* Banned heroes shown de-saturated to read as "removed". */
.draft-strip__tile--ban .draft-strip__tile-img {
    filter: grayscale(1);
}

.draft-strip__tile--pick {
    border-style: solid;
}

.draft-strip__tile-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.draft-strip__tile-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--app-text-muted);
    font-size: 12px;
    font-weight: var(--app-fw-semibold);
}

/* Marks a drafted hero that was traded away or into a slot during the draft. Big enough to read on a
   56px portrait — the 10px corner it used to be was invisible at the size these tiles are drawn. */
.draft-strip__tile-overlay {
    position: absolute;
    bottom: -2px;
    right: -2px;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid var(--app-surface-card);
    background: var(--mud-palette-warning);
    color: var(--mud-palette-background);
    font-size: 11px;
    line-height: 1;
    font-weight: 700;
}

/* The trades, behind the last divider. Deliberately a block of its own: a trade is not a pick, and
   inline it made the second pick phase read as ten drafted heroes. */
.draft-strip__swaps-icon {
    color: var(--mud-palette-warning);
    font-size: 1.1rem;
    line-height: 1;
}

/* The portraits inside a trade are the same size as the drafted ones, so the eye reads the pair as
   the same heroes rather than as a different kind of thing. The pill is therefore only a hairline and
   a tint around them — anything more would make the trade taller than the row it belongs to. */
.draft-strip__swap {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    padding: 2px;
    border: 1px solid var(--app-border);
    border-radius: 999px;
    background: var(--app-surface-inner);
}

.draft-strip__swap-arrow {
    color: var(--app-text-muted);
    font-size: 0.95rem;
    line-height: 1;
    padding: 0 0.1rem;
}

/* The player who started the trade, named first in the tooltip and ringed here. Inset rather than
   outset: the tiles sit shoulder to shoulder in the pill, and an outer ring would overlap its
   partner. */
.draft-strip__tile--swap.is-initiator {
    box-shadow: inset 0 0 0 2px var(--mud-palette-warning);
}

.draft-strip__label { font-size: var(--text-body-xs); font-weight: var(--app-fw-semibold); }
.draft-strip__label--home { color: var(--app-team-home); }
.draft-strip__label--away { color: var(--app-team-away); }

.draft-strip__source-chip {
    height: 20px;
    font-size: var(--text-body-xs);
    text-transform: none;
    letter-spacing: 0;
}
/* DS Card (readme "Borders & shadows" / "Corners"): 1px --border-subtle, small radius, very
   soft low-elevation shadow — separation comes from the border, not from the shadow. The
   previous 0 4px 20px rgba(0,0,0,.4) was a raw colour and far heavier than the DS allows. */
.match-card {
    position: relative;
    isolation: isolate;
    background: var(--app-surface-card);
    border-radius: var(--app-radius-lg);
    overflow: hidden;
    border: 1px solid var(--app-border);
    box-shadow: var(--app-shadow-card);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Full-card click target that navigates to the match details. Sits above passive content but
   below interactive elements (player tags, footer buttons) which are lifted with higher z-index. */
.match-card__click {
    position: absolute;
    inset: 0;
    z-index: 2;
    cursor: pointer;
}

/* DS: hover is "opacity or color shift only, no bounce/spring" and "no shadow-pop". */
.match-card:hover {
    border-color: var(--border-accent);
    box-shadow: var(--app-shadow-hover);
}

.match-card--played {
    border-left: 4px solid var(--mud-palette-success);
}

/* Header Banner.

   Es war bewusst ein dunkles Marken-Band in beiden Themes — aber nur halb: der Text darauf
   (.match-card__meta, .match-card__id) hängt an --mud-palette-text-secondary und kippt mit dem
   Theme mit. Im hellen Modus stand also dunkelgraue Schrift auf fast schwarzem Grund. Eine Regel,
   die zur Hälfte umschaltet, sieht nicht nach Absicht aus, sondern nach Fehler.

   Jetzt kippt das Band mit: eine Tönung der Textfarbe über der Kartenfläche. Dunkel bleibt dunkel,
   hell wird ein ruhiger grauer Streifen — in beiden Fällen als Kopfzeile lesbar, und Vorder- und
   Hintergrund bleiben im selben Modus. */
.match-card__header {
    background: color-mix(in srgb, var(--mud-palette-text-primary) 7%, var(--app-surface-card));
    padding: 10px 16px;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 6px;
    border-bottom: 2px solid var(--mud-palette-success);
}

.match-card__header--played {
    border-bottom-color: var(--mud-palette-success);
    /* Eine Spur kräftiger als der Normalfall; den "played"-Status trägt weiterhin die Success-Border. */
    background: color-mix(in srgb, var(--mud-palette-text-primary) 12%, var(--app-surface-card));
}

.match-card__header--warning {
    border-bottom-color: var(--mud-palette-error);
}

.match-card__header--ingame {
    border-bottom-color: var(--mud-palette-info);
}

/* Season badge on match cards — the DS Tag (components/core/Badge.jsx): a tinted pill in the
   display face. The two rgba() literals were the old Discord blurple and stayed put through the
   palette flip; 0.65rem (10.4px) was below the DS's 11px floor. */
.season-badge {
    padding: 3px 10px;
    border-radius: var(--app-radius-sm);
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    font-weight: var(--app-fw-semibold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-caps);
    background: color-mix(in srgb, var(--mud-palette-primary) 25%, transparent);
    /* Der Vollton, nicht die aufgehellte Stufe: die war für das dunkle Band gewählt und wäre auf dem
       hellen Kopf blassblau auf blassblau. --mud-palette-primary trägt pro Modus die passende. */
    color: var(--mud-palette-primary);
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 50%, transparent);
    white-space: nowrap;
    flex-shrink: 0;
}

/* The playoff badge sits next to the season's and must not read as a second season: the DS's
   secondary accent, same pill. */
.season-badge--playoff {
    background: color-mix(in srgb, var(--mud-palette-secondary) 25%, transparent);
    color: var(--mud-palette-secondary);
    border-color: color-mix(in srgb, var(--mud-palette-secondary) 50%, transparent);
}

.match-card__meta {
    font-size: var(--text-body-xs);
    color: var(--app-text-muted);
    letter-spacing: var(--tracking-caps);
}

/* Match ID — the DS reserves the mono face for IDs alongside scores and MMR. */
.match-card__id {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--app-text-muted);
    font-weight: var(--app-fw-medium);
    font-size: var(--text-body-sm);
}

/* VS Section — the core layout */
.match-card__body {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 0;
    padding: 16px;
    align-items: center;
    /* Home blue, away red — the same two tokens the scoreboard, the map bans and the draft strip
       use, so which side is which is answered before a word has been read. Weaker than the
       scoreboard's tint: this one repeats down a list of cards, where the scoreboard appears once. */
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--app-team-home) 14%, transparent) 0%,
            transparent 40%,
            transparent 60%,
            color-mix(in srgb, var(--app-team-away) 14%, transparent) 100%);
}

/* One side of a match card: crest at the outer edge, name and line-up inwards. Laid out like the
   match-detail scoreboard, so the two views of the same fixture read the same way round. */
.team {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.team__main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.team--home { text-align: left; }
.team--away { text-align: right; }

/* Team names are the DS's display treatment (readme: the wordmark face is used for
   headings/CTAs/labels), so they move from Inter 800 onto Oswald. The weight drops to 700:
   Oswald ships nothing above it, and --app-fw-black (800) is reserved for the body face —
   see the rework plan §6.5. --tracking-display replaces the hardcoded 0.5px. */
.team__name {
    position: relative;
    z-index: 1;
    font-family: var(--font-display);
    font-size: var(--text-body-md);
    font-weight: var(--app-fw-bold);
    color: var(--mud-palette-text-primary);
    text-transform: uppercase;
    letter-spacing: var(--tracking-display);
    line-height: var(--leading-snug);
}

/* The team's crest, in a slot of its own.

   It used to be a watermark: over-scaled to 155% of the block, faded to a quarter, masked towards
   the middle and sitting behind the player chips. At that size and opacity it read as a coloured
   smudge rather than as a crest — the same treatment the match-detail scoreboard dropped, and for
   the same reason. Contained in a square box so a wide wordmark and a round emblem both fit whole.
   Narrow screens drop it: the line-up needs the width more than the crest does. */
.team__crest {
    flex: 0 0 auto;
    width: clamp(32px, 5vw, 48px);
    height: clamp(32px, 5vw, 48px);
    object-fit: contain;
    opacity: 0.92;
    user-select: none;
}

@media (max-width: 599.98px) {
    .team__crest { display: none; }
}

.team__name--winner {
    color: var(--mud-palette-warning);
}

/* A team's name, leading to its page. Colour is inherited, so a winner's highlight survives and only
   the underline on hover says it is clickable. The z-index is for the match cards, where the whole
   card is one link to the match (.match-card__click, z-index 2) and a name inside it has to sit above
   that overlay or the card swallows the click; on the match-detail scoreboard there is no overlay and
   it costs nothing. */
.team__name-link {
    position: relative;
    z-index: 3;
    color: inherit;
    text-decoration: none;
}

.team__name-link:hover,
.team__name-link:focus-visible {
    text-decoration: underline;
}

/* ── Match detail scoreboard ──────────────────────────────────────────────────────────────────
   The header of a match page: both line-ups, the score, and each side's logo at the outer edge.

   The logos used to be watermarks — over-scaled to 155%, faded to a quarter and clipped by the
   grid column they sat in. Cropped at the card edge and sitting under the player chips, they read
   as a smudge rather than as a crest, which is the opposite of what a logo is for. They are one of
   the two things somebody identifies a match by, so they get a slot of their own and are shown
   whole.

   The tint says which side is which before you have read a word: home blue, away red. The same two
   tokens the map bans, the draft strip and the per-player stat rows already use, so "left is blue"
   holds across the whole match page. It fades out well before the middle, so the score still sits
   on the ordinary card surface and nothing has to fight a colour for contrast — the DS is a flat
   system, and this is a tint on one card, not a mood. */
.match-scoreboard {
    position: relative;
    display: flex;
    align-items: center;
    gap: clamp(8px, 2vw, 24px);
    padding: 16px clamp(12px, 2vw, 24px);
    overflow: hidden;
    border: 1px solid var(--app-border);
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--app-team-home) 22%, transparent) 0%,
            transparent 42%,
            transparent 58%,
            color-mix(in srgb, var(--app-team-away) 22%, transparent) 100%),
        var(--app-surface-card);
}

/* One side of the fixture. The team block takes the slack, which centres it between the score and
   its own logo and keeps both sides symmetrical however long the names are. */
.match-scoreboard__side {
    display: flex;
    align-items: center;
    gap: clamp(8px, 1.5vw, 16px);
    flex: 1 1 0;
    min-width: 0;
}

.match-scoreboard__team {
    flex: 1 1 auto;
    min-width: 0;
    text-align: center;
}

/* Whole, not cropped: contain inside a square box, so a wide wordmark and a round crest both fit
   without either being cut. Just under full opacity — furniture beside the names, not a rival. */
.match-scoreboard__logo {
    flex: 0 0 auto;
    width: clamp(44px, 9vw, 88px);
    height: clamp(44px, 9vw, 88px);
    object-fit: contain;
    opacity: 0.92;
    user-select: none;
}

/* The DS reserves the mono face for scores, MMR and IDs — tabular figures so 10–7 and 0–0 occupy
   the same width and the two team blocks do not shift as a match is reported. */
.match-scoreboard__score {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: clamp(1.5rem, 4vw, 2.25rem);
    font-weight: var(--app-fw-black);
    line-height: 1;
    white-space: nowrap;
    color: var(--mud-palette-text-primary);
}

.match-scoreboard__players {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 8px;
}

/* Narrow screens: the two line-ups need the width more than the crests do, and a 44px logo next to
   seven wrapped chips is neither legible nor useful. */
@media (max-width: 599.98px) {
    .match-scoreboard__logo { display: none; }
}

/* VS Divider */
.vs-divider {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 0 16px;
    gap: 2px;
}

/* Display face; weight 900 was unreachable (Oswald tops out at 700, Inter at 800) and so
   already rendered as the nearest available weight. The glow is gone with it — the DS has no
   text-shadow anywhere and the raw amber it used was left over from the old palette. */
.vs-divider__text {
    font-family: var(--font-display);
    font-size: var(--text-display-sm);
    font-weight: var(--app-fw-bold);
    color: var(--mud-palette-warning);
}

/* A score is a number: DS mono, tabular, so 1:0 and 3:2 occupy the same width. */
.vs-divider__score {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--text-body-sm);
    font-weight: var(--app-fw-semibold);
    color: var(--mud-palette-text-primary);
}

.vs-divider__time {
    font-size: 11px;
    color: var(--app-text-muted);
    white-space: nowrap;
}

.vs-divider__takedowns {
    font-size: 11px;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
    white-space: nowrap;
    letter-spacing: 0.5px;
}

.vs-divider__takedowns-icon {
    color: var(--mud-palette-error-lighten);
    margin-right: 1px;
}

.team-logo {
    object-fit: contain;
    border-radius: 4px;
    vertical-align: middle;
    flex-shrink: 0;
}

/* Player Roster */
.team__roster {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.team--away .team__roster {
    justify-content: flex-end;
}

.player-tag {
    position: relative;
    z-index: 3;
    background: var(--app-hover-bg);
    border: 1px solid var(--app-border);
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 11px;
    color: var(--mud-palette-text-primary);
    transition: all 0.2s;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    min-height: 32px;
}

/* Every tint below used to be a literal rgba() of the *pre-DS* palette (neon green #00ff88,
   Discord blurple #5865F2, #ED4245, #be1e2d) paired with a var() text colour. After the palette
   flip the tint and the text it sits behind were no longer the same hue. Mixing from the same
   variable the text uses keeps them in step, in both modes and through any future palette edit. */
.player-tag:hover {
    background: color-mix(in srgb, var(--mud-palette-success) 10%, transparent);
    border-color: var(--mud-palette-success);
    color: var(--mud-palette-success);
}

.player-tag--warning {
    border-color: color-mix(in srgb, var(--mud-palette-error) 50%, transparent);
    color: var(--mud-palette-error);
}

.player-tag--warning:hover {
    background: color-mix(in srgb, var(--mud-palette-error) 10%, transparent);
    border-color: var(--mud-palette-error);
    color: var(--mud-palette-error);
}

.player-tag--sub {
    border-color: color-mix(in srgb, var(--mud-palette-primary) 50%, transparent);
    color: var(--mud-palette-primary);
}

.player-tag--sub:hover {
    background: color-mix(in srgb, var(--mud-palette-primary) 10%, transparent);
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary);
}

.player-tag--banned {
    border-color: color-mix(in srgb, var(--app-banned) 70%, transparent);
    color: var(--app-banned);
    background: color-mix(in srgb, var(--app-banned) 8%, transparent);
}

.player-tag--banned:hover {
    background: color-mix(in srgb, var(--app-banned) 15%, transparent);
    border-color: var(--app-banned);
    color: var(--app-banned);
}

/* Status Badge — the DS StatusPill (components/core/Badge.jsx): a pill in the display face with
   a tint mixed from its own accent. The comment here used to claim the rgba() tints were alpha
   variants of the palette accents; that stopped being true at the palette flip — they were the
   old accents (#ffaa00, #00ff88, #7289DA, #ED4245) sitting behind the new ones. */
.status-badge {
    padding: 3px 10px;
    border-radius: var(--app-radius-pill);
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    font-weight: var(--app-fw-semibold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-caps);
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.status-badge--pending {
    background: color-mix(in srgb, var(--mud-palette-warning) 15%, transparent);
    color: var(--mud-palette-warning);
    border: 1px solid color-mix(in srgb, var(--mud-palette-warning) 40%, transparent);
}

/* The one badge that reads as the card's headline result, so it stays a step larger. Weight
   stays at the display face's ceiling — 800 is body-face only (plan §6.5). */
.status-badge--played {
    background: color-mix(in srgb, var(--mud-palette-success) 20%, transparent);
    color: var(--mud-palette-success);
    border: 1px solid color-mix(in srgb, var(--mud-palette-success) 60%, transparent);
    font-size: var(--text-body-sm);
    padding: 4px 12px;
    font-weight: var(--app-fw-bold);
}

.status-badge--ingame {
    background: color-mix(in srgb, var(--mud-palette-info) 15%, transparent);
    color: var(--mud-palette-info);
    border: 1px solid color-mix(in srgb, var(--mud-palette-info) 40%, transparent);
    animation: pulse-badge 1.5s infinite;
}

.status-badge--warning {
    background: color-mix(in srgb, var(--mud-palette-error) 15%, transparent);
    color: var(--mud-palette-error);
    border: 1px solid color-mix(in srgb, var(--mud-palette-error) 40%, transparent);
    animation: pulse-badge 1.5s infinite;
}

/* The DS allows "opacity or color shift only" — a pulse qualifies, but it is the one animation
   in the app that never stops, so it yields to the reduced-motion preference. */
@keyframes pulse-badge {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.7; }
}

@media (prefers-reduced-motion: reduce) {
    .status-badge--ingame,
    .status-badge--warning {
        animation: none;
    }
}

/* Match Card Footer */
.match-card__footer {
    position: relative;
    z-index: 3;
    background: var(--app-hover-bg);
    border-top: 1px solid var(--app-border);
    padding: 10px 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* Footer action row: Details on the left, "View on HL" pushed to the right (via ms-auto). */
.match-card--skeleton {
    /* Amarant-leaner Skeleton-Hintergrund statt Echt-Daten; kein Hover-Lift */
    pointer-events: none;
}

/* A skeleton is not clickable, so it keeps the resting card chrome on hover. */
.match-card--skeleton:hover {
    border-color: var(--app-border);
    box-shadow: var(--app-shadow-card);
}

.match-card--skeleton .mud-skeleton {
    background-color: var(--app-skeleton-bg);
}

.match-card__footer-actions {
    width: 100%;
}

.match-card__maps {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.match-card__games {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
}

.game-card {
    display: flex;
    align-items: stretch;
    border-radius: 6px;
    overflow: hidden;
    border: 1px solid var(--app-border);
    background: var(--app-hover-bg);
}

/* Which side won a map. These carried the old blurple/neon-green literals; the two roles have
   DS team tokens, and using them also makes the pair read as "home vs away" rather than as
   "brand vs success". */
.game-card--home-win {
    border-color: color-mix(in srgb, var(--app-team-home) 40%, transparent);
}

.game-card--away-win {
    border-color: color-mix(in srgb, var(--app-team-away) 40%, transparent);
}

.game-card__img {
    width: 80px;
    min-height: 48px;
    background-size: cover;
    background-position: center;
    flex-shrink: 0;
    /* The new full-res map art is vivid — tone the small thumbnail down. */
    filter: brightness(0.7);
}

.game-card__info {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    min-height: 40px;
}

.game-card__map {
    font-size: 11px;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

.game-card__duration {
    font-size: 10px;
    color: var(--app-text-muted);
}

.game-card__winner {
    font-size: 10px;
    font-weight: 600;
    color: var(--app-gold);
}

/* ── Game Stats (expandable per-game player stats table) ── */
.game-stats-container {
    padding: 8px 12px 10px;
    border-top: 1px solid var(--app-border);
}

.game-stats-bans {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 8px;
    font-size: 10px;
    color: var(--app-text-muted);
}

.game-stats-ban-label {
    font-weight: 500;
}

.game-stats-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
}

.game-stats-table th {
    text-align: left;
    padding: 3px 6px;
    color: var(--app-text-muted);
    font-weight: 600;
    font-size: 9px;
    text-transform: uppercase;
    border-bottom: 1px solid var(--app-border);
}

.game-stats-table td {
    padding: 2px 6px;
    color: var(--mud-palette-text-primary);
    white-space: nowrap;
}

.game-stats-row--home td {
    color: var(--mud-palette-primary-lighten);
}

.game-stats-row--away td {
    color: var(--mud-palette-success-lighten);
}

.map-tag {
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 600;
    border: 1px solid var(--app-border);
    background: var(--app-hover-bg);
    color: var(--app-text-muted);
}

.map-tag--home-win {
    border-color: rgba(88, 101, 242, 0.5);
    color: var(--mud-palette-primary);
}

.map-tag--away-win {
    border-color: rgba(0, 255, 136, 0.5);
    color: var(--mud-palette-success);
}

.view-btn {
    background: transparent;
    border: 1px solid var(--mud-palette-primary);
    color: var(--mud-palette-primary);
    padding: 4px 12px;
    border-radius: 6px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.15s;
    text-decoration: none;
    display: inline-block;
}

.view-btn:hover {
    background: rgba(85, 136, 255, 0.15);
    color: var(--mud-palette-primary-lighten);
}

/* ── Unregistered Sub Banner ── */
.unreg-sub-banner {
    background: rgba(237, 66, 69, 0.08);
    border-top: 1px solid rgba(237, 66, 69, 0.3);
    padding: 8px 16px;
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 11px;
    color: var(--mud-palette-error);
}

.unreg-sub-entry {
    margin-bottom: 2px;
}

/* ── Page headers (DS Topbar; PageHeader.razor, plan §2.5) ──
   The class existed but nothing used it — all 22 headers were hand-rolled copies of the
   same flex markup. It now backs the shared component. Alignment follows the DS Topbar:
   flex-start, so a two-line subtitle does not drag the action buttons down with it. */
.page-header {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 24px;
}

.page-header--with-actions {
    justify-content: space-between;
}

.page-header__lead {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.page-header__actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* Section divider */
.section-title {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    margin-bottom: 16px;
}

.section-title::after {
    content: '';
    flex: 1;
    height: 1px;
    background: currentColor;
    opacity: 0.12;
}

/* Drag handle */
.drag-handle {
    cursor: grab;
    opacity: 0.4;
    transition: opacity 0.15s;
}

.drag-handle:hover {
    opacity: 1;
}

/* Card hover effects */
.hover-card {
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    border: 1px solid var(--app-border);
}

.hover-card:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Quick action buttons */
.quick-action {
    transition: transform 0.15s ease;
}

.quick-action:hover {
    transform: translateY(-2px);
}

/* Smooth page transitions */
.mud-main-content {
    animation: fadeIn 0.2s ease-in;
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Expansion panel refinements */
.mud-expand-panel {
    border-radius: var(--app-radius-lg) !important; /* DS --radius-lg; Mud-Default überschreiben */
    margin-bottom: 8px !important;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA9NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--hl-red-600);
    padding: 1rem 1rem 1rem 3.7rem;
    color: var(--fg-on-dark);
}

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

/* ── Pulse dot animation (Bot Status) ── */
.pulse-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    animation: pulse 2s ease-in-out infinite;
}

.pulse-green {
    background: var(--mud-palette-success);
    box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.6);
}

.pulse-red {
    background: var(--mud-palette-error);
    box-shadow: 0 0 0 0 rgba(237, 66, 69, 0.6);
    animation-name: pulseRed;
}

@keyframes pulse {
    0% { box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.6); }
    70% { box-shadow: 0 0 0 8px rgba(0, 255, 136, 0); }
    100% { box-shadow: 0 0 0 0 rgba(0, 255, 136, 0); }
}

@keyframes pulseRed {
    0% { box-shadow: 0 0 0 0 rgba(237, 66, 69, 0.6); }
    70% { box-shadow: 0 0 0 8px rgba(237, 66, 69, 0); }
    100% { box-shadow: 0 0 0 0 rgba(237, 66, 69, 0); }
}

/* Next match countdown card */
.next-match-card {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--app-border);
    background: var(--app-surface-card) !important;
}

.next-match-card::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 4px;
    border-radius: 4px 0 0 4px;
    background: var(--mud-palette-warning);
}

/* ── Division Toggle ── */
.division-toggle {
    cursor: pointer;
    user-select: none;
    padding: 4px 8px;
    border-radius: 8px;
    transition: background 0.15s ease;
}

.division-toggle:hover {
    background: var(--app-hover-bg);
}

/* Fallback-Banner für Games ohne Map-Bild — bewusst dunkles Marken-Band in beiden Themes. */
.detail-map-fallback {
    background: var(--surface-dark);
}

/* ── Replay Analyzer ── */
/* Bewusst dunkles Marken-Banner in beiden Themes (weißer Text darauf ok). */
.replay-match-banner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    background: var(--surface-dark);
    border-bottom: 1px solid var(--app-border);
}

.replay-team {
    padding: 20px 24px;
    text-align: center;
}

.replay-team--winner {
    background: rgba(0, 255, 136, 0.06);
}

.replay-team--loser {
    background: rgba(237, 66, 69, 0.04);
    opacity: 0.7;
}

.replay-vs {
    font-size: 22px;
    font-weight: 900;
    color: var(--mud-palette-warning);
    text-shadow: 0 0 20px rgba(255, 170, 0, 0.5);
    padding: 0 16px;
}

.replay-teams-grid {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
}

.replay-player-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    border-radius: 8px;
    margin-bottom: 4px;
    transition: background 0.15s;
}

.replay-player-row:hover {
    background: var(--app-hover-bg);
}

.replay-player-hero {
    display: flex;
    align-items: center;
    gap: 10px;
}

@media (max-width: 960px) {
    .replay-match-banner,
    .replay-teams-grid {
        grid-template-columns: 1fr;
    }

    .replay-vs {
        text-align: center;
        padding: 8px 0;
    }
}

/* ── Replay Stats Table ── */
.replay-tabs .mud-tabs-toolbar {
    background: rgba(0, 0, 0, 0.2) !important;
    border-bottom: 1px solid var(--app-border);
}

/* ── Substitutes page tabs ── */
.sub-tabs .mud-tabs-toolbar {
    background: rgba(0, 0, 0, 0.2) !important;
    border-bottom: 1px solid var(--app-border);
}

/* MudTabs scrolls its strip by translating the wrapper, and it keeps that offset across renders. On
   a handful of tabs that comfortably fit, the offset is never right and never needed: coming back
   from a drill-down redrew the bar already scrolled, with the first tabs pushed out of sight.

   So the offset is refused outright, and the strip is given ordinary overflow instead — a screen
   narrow enough to need scrolling still scrolls, natively, and the arrows go because with no offset
   to set they would do nothing. */
.sub-tabs .mud-tabs-tabbar-wrapper {
    transform: none !important;
}

.sub-tabs .mud-tabs-tabbar-content {
    overflow-x: auto;
    scrollbar-width: thin;
}

.sub-tabs .mud-tabs-scroll-button {
    display: none;
}

.replay-stats-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    table-layout: auto;
}

.replay-stats-table th {
    padding: 6px 8px;
    text-align: left;
    font-weight: 600;
    color: var(--app-text-muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 1px solid var(--app-border);
    white-space: nowrap;
}

.replay-stats-th-hero {
    width: 40px;
}

.replay-stats-th-num {
    text-align: right !important;
}

/* A tier column is as wide as the pick that names itself, and no wider — the filler beside them
   holds whatever the row has left over. */
.replay-stats-th-talent {
    text-align: left !important;
    white-space: nowrap;
}

/* The column that exists only to be empty. Every table has spare width somewhere; this one decides
   where it goes, instead of leaving it to the player column and stranding the build at the far edge. */
.replay-talent-filler {
    width: 100%;
    border-bottom: 1px solid var(--app-border);
}

.replay-stats-row td {
    padding: 4px 8px;
    border-bottom: 1px solid var(--mud-palette-table-lines);
    vertical-align: middle;
}

.replay-stats-row:hover td {
    background: var(--app-hover-bg);
}

.replay-stats-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    color: var(--mud-palette-text-primary);
    white-space: nowrap;
}

.replay-stats-team-header td {
    padding: 6px 8px;
    font-size: 11px;
    border-bottom: 2px solid var(--app-border);
    background: rgba(0, 0, 0, 0.15);
}

.replay-stats-totals td {
    padding: 8px 10px;
    border-top: 2px solid var(--app-border);
    background: rgba(0, 0, 0, 0.1);
    color: var(--mud-palette-text-primary);
    font-weight: 700;
}

.replay-stats-spacer td {
    padding: 3px 0;
    border: none;
}

.replay-stats-hero-cell {
    width: 40px;
    padding: 4px 6px !important;
}

.replay-hero-icon {
    width: 32px;
    height: 32px;
    border-radius: 6px;
    object-fit: cover;
    background: var(--app-hover-bg);
    border: 1px solid var(--app-border);
}

/* MatchDetail: small role badge overlaid on the hero icon in the per-game stats table. */
.mdx-hero-role { position: relative; display: inline-block; line-height: 0; }
.mdx-role-icon {
    position: absolute; right: -4px; bottom: -4px;
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--app-surface-card); padding: 1px;
    box-shadow: 0 0 0 1px var(--app-border);
}
/* MatchDetail: GameStatsTabs cell styling — replaces inline color/font-weight/font-size (STYLE.md §4.4/§3.2/§5.4). */
.mdx-team-home { color: var(--mud-palette-primary); }
.mdx-team-away { color: var(--mud-palette-error); }
.mdx-stat-best { font-weight: 700; color: var(--app-gold); }
.mdx-kda-k { color: var(--mud-palette-success); }
.mdx-kda-d { color: var(--mud-palette-error); }
.mdx-kda-a { color: var(--mud-palette-primary-lighten); }
.mdx-subtext { font-size: var(--text-body-xs); color: var(--app-text-muted); }
/* A MudTooltip with Color.Dark is a dark surface in both themes, so its text has to come from the
   DS tokens that mean "on a dark surface" and do not flip. The description used --app-text-muted,
   which does flip: in light mode it turned dark grey on near-black and could not be read. The title
   was already right, which is why only half the tooltip was broken. */
.mdx-tooltip-title { font-weight: 700; color: var(--app-on-dark); }
.mdx-tooltip-desc { font-size: var(--text-body-xs); color: var(--app-on-dark-muted); line-height: 1.4; }

/* ── Replay stats tables: merged Hero+Player lead cell ──
   The hero's partyframe portrait in a fixed slot at the left, names beside it. Falls back to the
   small hero icon in the same slot when no partyframe art exists for the hero. Row height ~44px. */
.replay-pf-cell {
    position: relative;
    min-width: 150px;
    height: 44px;
    /* !important (STYLE.md §8): overrides the generic `.replay-stats-row td { padding: 4px 8px }`
       rule, which otherwise wins on specificity and would inset the banner from the cell edge. */
    padding: 0 8px 0 0 !important;
}
/* Portrait first, then the name — a row, not a stack.

   The name used to sit *on* the hero's party-frame banner, which was faded out to the right and given
   a text shadow to keep it readable. It was not readable: the banners are busy artwork in every hue,
   and a name over "Kel'Thuzad" and a name over "Chen" are two different reading problems. Beside the
   portrait there is no artwork to fight, so the shadow and the fade both go.

   Kept separate from .replay-pf-cell so callers that also render an overflowing child (e.g.
   ReplayAnalyzer's hover popover) can place it as a sibling outside this wrapper — it would
   otherwise be clipped by overflow:hidden. */
.replay-pf-inner {
    position: relative;
    height: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    overflow: hidden;
}
/* A fixed slot rather than the banner's own width: the frames differ in aspect ratio, and a column
   whose text starts at a different x for every hero is a column nobody can scan down. */
.replay-pf-img {
    flex: 0 0 auto;
    width: 56px;
    height: 100%;
    object-fit: cover;
    /* The hero's face sits at the left of a party frame; crop toward it rather than to the middle. */
    object-position: left center;
    border-radius: 4px;
}
.replay-pf-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 100%;
}
/* No party frame for this hero: a square icon takes the same slot, so the name still starts at the
   same x whichever of the two the row happens to have. */
.replay-pf-fallback {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    margin-left: 6px;
    border-radius: 4px;
}
/* The icon variant needed to clear an absolutely positioned image. It is a flex item now, so the
   modifier has nothing left to do — kept as a no-op hook rather than removed, because three
   components render it. */
.replay-pf-text--icon {
    padding-left: 0;
}

/* ── Replay Talent Cells ── */
.replay-talent-cell {
    text-align: left;
    vertical-align: middle;
    padding: 2px !important;
}

/* Icon plus the talent's name. The name used to live only in the tooltip, which meant reading a
   build was seven hovers; with the width the tiers were being denied anyway, it fits on the row.
   The tint is mixed from the palette — it was a literal rgba() of the pre-DS Discord blurple, left
   behind by the palette flip (STYLE.md §1.1). */
.replay-talent-pick {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    justify-content: center;
    max-width: 100%;
    min-height: 34px;
    /* Symmetric: three other places render this pill with nothing but the icon in it (the replay
       analyzer, the hero detail's builds). The name pays for its own breathing room instead. */
    padding: 2px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--mud-palette-primary) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 30%, transparent);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

/* Colour shift only — the DS rules out the bounce the old scale(1.1) did, and a pill that wide
   growing under the cursor shoves its neighbours around. */
.replay-talent-pick:hover {
    border-color: var(--mud-palette-primary);
    background: color-mix(in srgb, var(--mud-palette-primary) 20%, transparent);
}

/* Long names are cut rather than allowed to set the column's width; the tooltip still has it whole. */
.replay-talent-name {
    margin-right: 6px;
    font-size: 11px;
    line-height: var(--leading-snug);
    color: var(--mud-palette-text-primary);
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Narrow viewports: the icons alone, as before. Seven names beside seven icons needs room that a
   phone does not have, and an ellipsis after three letters says less than the icon does. */
@media (max-width: 1279.98px) {
    .replay-talent-name { display: none; }
}

.replay-talent-icon {
    width: 28px;
    height: 28px;
    border-radius: 4px;
    object-fit: cover;
}

.replay-talent-fallback {
    font-size: 9px;
    font-weight: 700;
    color: var(--app-text-muted);
    text-overflow: ellipsis;
    overflow: hidden;
    max-width: 36px;
    display: block;
}

.replay-talent-empty {
    color: var(--mud-palette-text-disabled);
    font-size: 14px;
}

/* ── Team Search Filter ── */
.match-filter-bar {
    background: var(--app-surface-card);
    border: 1px solid var(--app-border);
    border-radius: 12px;
    padding: 12px 16px;
    margin-bottom: 16px;
}

/* ── Legend Card ── */
.legend-card {
    background: var(--app-surface-card) !important;
    border: 1px solid var(--app-border);
    border-radius: 12px;
    padding: 12px 16px;
}

/* ── Severity/Risk chip overrides for dark theme visibility ── */
.chip-critical {
    background: var(--color-critical) !important;
    color: white !important; /* fester Akzent-Hintergrund — bewusst weiß in beiden Themes */
}

.chip-blacklisted {
    background: var(--color-blacklisted) !important;
    color: white !important; /* fester Akzent-Hintergrund — bewusst weiß in beiden Themes */
}

/* ── Responsive ── */

/* Responsive tables: horizontal scroll with fade indicator */
.mud-table {
    overflow: visible;
}

.mud-table .mud-table-container {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: var(--mud-palette-text-disabled) transparent;
}

.mud-table .mud-table-container::-webkit-scrollbar {
    height: 6px;
}

.mud-table .mud-table-container::-webkit-scrollbar-thumb {
    background: var(--mud-palette-text-disabled);
    border-radius: 3px;
}

.mud-table .mud-table-container::-webkit-scrollbar-track {
    background: transparent;
}

/* Ensure table doesn't shrink columns too aggressively */
.mud-table .mud-table-root {
    min-width: 700px;
}

/* Truncated text in table cells */
.mud-td {
    max-width: 250px;
}

.cell-truncate {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Page headers: stack on mobile */
@media (max-width: 600px) {
    .d-flex.align-center.justify-space-between {
        flex-direction: column;
        align-items: flex-start !important;
        gap: 12px;
    }
}

/* ── Player Name Popover ── */
.replay-player-name {
    font-weight: 600;
    color: var(--mud-palette-text-primary);
    cursor: pointer;
    display: inline-block;
}

.replay-player-name:hover {
    color: var(--mud-palette-primary);
    text-decoration: underline;
}

.replay-player-popover {
    position: absolute;
    left: 0;
    top: 100%;
    z-index: 100;
    background: var(--app-surface-card);
    border: 1px solid var(--app-border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.5);
    white-space: nowrap;
}

/* Stats cards: 2-col on very small screens */
@media (max-width: 400px) {
    .mud-grid > .mud-grid-item {
        flex-basis: 50% !important;
        max-width: 50% !important;
    }
}

@media (max-width: 960px) {
    .match-card__body {
        grid-template-columns: 1fr;
        gap: 12px;
        text-align: center;
    }

    .team--home, .team--away {
        text-align: center;
    }

    .team__roster, .team--away .team__roster {
        justify-content: center;
    }

    .vs-divider {
        padding: 4px 0;
    }

    .vs-divider__text {
        font-size: 18px;
    }

    /* Tighter filter bars on tablet */
    .d-flex.align-center.gap-4.flex-wrap {
        gap: 8px !important;
    }
}

@media (max-width: 600px) {
    .mud-main-content {
        padding: 8px !important;
    }

    .mud-container {
        padding-left: 8px !important;
        padding-right: 8px !important;
    }


    /* Compact stat cards. Targets the KPI value itself now that the tiles render through
       StatCard's .stat-card__value rather than a heading typo. */
    .status-card,
    .stat-card {
        padding: 8px 12px !important;
    }

    .stat-card__value {
        font-size: var(--text-display-sm);
    }

    /* Dialog full-width on mobile */
    .mud-dialog {
        margin: 8px !important;
        max-height: calc(100vh - 16px) !important;
    }

    .hero-card {
        width: 56px;
    }

    .hero-portrait {
        width: 48px;
        height: 48px;
    }

    .hero-name {
        font-size: var(--text-body-xs);
    }

    .map-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    }

    /* Table: hide less important columns on mobile */
    .hide-on-mobile {
        display: none !important;
    }

    /* Full width filter inputs */
    .d-flex.align-center.gap-4.flex-wrap > * {
        flex: 1 1 100% !important;
        max-width: 100% !important;
    }
}

/* ── Hero Grid (Nexus Rumble) ── */
.hero-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.hero-card {
    position: relative;
    width: 72px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    cursor: pointer;
    transition: transform 0.15s ease;
}

.hero-card:hover {
    transform: translateY(-3px);
}

.hero-portrait {
    position: relative;
    width: 64px;
    height: 64px;
    border-radius: 10px;
    overflow: hidden;
    border: 2px solid var(--app-border);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    flex-shrink: 0;
}

.hero-card:hover .hero-portrait {
    border-color: var(--app-text-muted);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.hero-portrait img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: filter 0.2s ease;
}

.hero-card.banned .hero-portrait {
    border-color: var(--mud-palette-error);
}

.hero-card.banned .hero-portrait img {
    filter: grayscale(100%) brightness(0.4);
}

.hero-ban-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(237, 66, 69, 0.25);
}

.hero-name {
    display: block;
    font-size: var(--text-body-xs);
    margin-top: 4px;
    color: var(--app-text-muted);
    line-height: 1.1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hero-card.banned .hero-name {
    color: var(--mud-palette-error);
    font-weight: 600;
}

/* ── Map Cards (Map Pool) ── */
.map-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
}

.map-card {
    position: relative;
    border-radius: 12px;
    overflow: hidden;
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.2s ease, filter 0.2s ease;
    border: 2px solid transparent;
}

.map-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}

.map-card.active {
    border-color: var(--mud-palette-success);
}

.map-card:not(.active) {
    opacity: 0.45;
    filter: grayscale(50%);
}

.map-card:not(.active):hover {
    opacity: 0.85;
    filter: none;
}

.map-card-img {
    width: 100%;
    height: 110px;
    object-fit: cover;
    display: block;
}

.map-card-label {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 8px 10px;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.85));
    font-size: var(--text-body-sm);
    font-weight: 600;
    color: white; /* liegt auf abgedunkeltem Map-Bild — bewusst weiß */
}

.map-card-badge {
    position: absolute;
    top: 6px;
    right: 6px;
    /* Liegt auf dem Map-Bild — bewusst feste Akzentfarben, unabhängig vom Theme. */
    background: var(--hl-green-500);
    color: var(--hl-ink-950);
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-body-xs);
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* Sortable stats table headers */
.replay-stats-sortable {
    cursor: pointer;
    user-select: none;
    transition: color 0.15s;
}

.replay-stats-sortable:hover {
    color: var(--mud-palette-primary);
}

.replay-sort-icon {
    font-size: 10px;
    margin-left: 2px;
    opacity: 0.6;
}

/* ── Match Detail Page ── */

.match-detail-game {
    transition: box-shadow 0.2s ease;
}

.match-detail-game:hover {
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.match-detail-map {
    position: relative;
    width: 100%;
    height: 300px;
    border-radius: 12px 12px 0 0;
    overflow: hidden;
}

/* Blurred + darkened map art on its own layer so the sharp content above is unaffected. */
.match-detail-map::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--map-bg);
    background-size: cover;
    background-position: center;
    filter: blur(3px) brightness(0.55);
    transform: scale(1.06); /* absorb blur edge bleed */
    z-index: 0;
}

.match-detail-map__overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 16px 20px;
    /* Lighter than before — the background layer is already darkened. */
    background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.25) 55%, rgba(0, 0, 0, 0.05) 100%);
    border-radius: 12px 12px 0 0;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
}

.match-detail-map__overlay .w-100 {
    width: 100%;
}

.match-detail-level-badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.5);
    color: var(--mud-palette-text-primary);
    font-size: 12px;
    font-weight: 500;
    border: 1px solid var(--app-border);
}

.match-detail-winner-badge {
    display: inline-block;
    padding: 3px 12px;
    border-radius: 6px;
    background: rgba(0, 255, 136, 0.15);
    color: var(--mud-palette-success);
    font-size: 13px;
    font-weight: 700;
    border: 1px solid rgba(0, 255, 136, 0.3);
}

.detail-ban-label {
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 6px;
    background: var(--app-hover-bg);
    color: var(--app-text-muted);
    font-size: 12px;
    font-weight: 500;
    border: 1px solid var(--app-border);
}

/* ── Ban Tiles (Map & Hero) ──
   Die rgba(85,136,255,…)/rgba(255,85,85,…)-Töne sind die festen Team-Akzente
   (--app-team-home/--app-team-away) als Alpha-Varianten — bewusst theme-unabhängig. */

.ban-map-tile {
    display: flex;
    flex: 1;
    min-width: 80px;
    flex-direction: column;
    align-items: center;
    border-radius: 8px;
    overflow: hidden;
    border: 2px solid var(--app-border);
    background: var(--app-surface-inner);
    transition: transform 0.15s ease;
}
.ban-map-tile:hover {
    transform: translateY(-2px);
}

.ban-map-tile--home {
    border-color: rgba(85, 136, 255, 0.5);
    box-shadow: 0 0 8px rgba(85, 136, 255, 0.15);
}
.ban-map-tile--away {
    border-color: rgba(255, 85, 85, 0.5);
    box-shadow: 0 0 8px rgba(255, 85, 85, 0.15);
}

.ban-map-tile__img {
    width: 100%;
    height: 56px;
    background-size: cover;
    background-position: center;
    filter: grayscale(60%) brightness(0.8);
    transition: filter 0.2s ease;
}
.ban-map-tile:hover .ban-map-tile__img {
    filter: grayscale(20%) brightness(1);
}
.ban-map-tile__img--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--app-surface-raised);
}
.ban-map-tile__name {
    padding: 3px 6px;
    font-size: 9px;
    font-weight: 600;
    color: var(--app-text-muted);
    text-align: center;
    max-width: 96px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ban-hero-tile {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    border-radius: 8px;
    overflow: hidden;
    border: 2px solid var(--app-border);
    background: var(--app-surface-inner);
    transition: transform 0.15s ease;
    flex-shrink: 0;
}
.ban-hero-tile:hover {
    transform: translateY(-2px);
}

.ban-hero-tile--home {
    border-color: rgba(85, 136, 255, 0.5);
    box-shadow: 0 0 8px rgba(85, 136, 255, 0.15);
}
.ban-hero-tile--away {
    border-color: rgba(255, 85, 85, 0.5);
    box-shadow: 0 0 8px rgba(255, 85, 85, 0.15);
}

.ban-hero-tile__img {
    width: 48px;
    height: 48px;
    object-fit: cover;
    filter: grayscale(40%);
    transition: filter 0.2s ease;
}
.ban-hero-tile:hover .ban-hero-tile__img {
    filter: grayscale(0%);
}
.ban-hero-tile__img--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    background: var(--app-surface-raised);
    color: var(--mud-palette-text-disabled);
    font-size: 10px;
}
.ban-hero-tile__name {
    padding: 2px 6px;
    font-size: 9px;
    font-weight: 600;
    color: var(--app-text-muted);
    text-align: center;
    max-width: 72px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Compact variant for MatchCard games section */
.ban-hero-tile--compact .ban-hero-tile__img {
    width: 32px;
    height: 32px;
}
.ban-hero-tile--compact .ban-hero-tile__img--fallback {
    width: 32px;
    height: 32px;
}
.ban-hero-tile--compact .ban-hero-tile__name {
    font-size: 8px;
    max-width: 56px;
}

.ban-map-tile--compact .ban-map-tile__img {
    width: 64px;
    height: 36px;
}
.ban-map-tile--compact .ban-map-tile__name {
    font-size: 8px;
    max-width: 60px;
}

/* ── Ban Layout Helpers ── */

.ban-team-separator {
    width: 2px;
    background: var(--app-border);
    align-self: stretch;
    margin: 0 12px;
    border-radius: 1px;
}

/* Team label under the map ban / pick tiles (home left, away right). */
.draft-team-label {
    font-weight: var(--app-fw-semibold, 600);
}

.ban-vs-divider {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    font-weight: 900;
    color: var(--mud-palette-warning);
    /* Dark halo: the glow lit the letters from behind and did nothing for their edges against a
       photograph. Every other piece of text on this banner is separated the same way. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
    padding: 0 12px;
    letter-spacing: 3px;
    gap: 4px;
}

/* ── Overlay hero tile (compact, for map overlay) ── */

.ban-hero-tile--overlay {
    border-width: 1px;
    border-radius: 6px;
}
.ban-hero-tile--overlay .ban-hero-tile__img {
    width: 64px;
    height: 64px;
    filter: grayscale(45%);
}
.ban-hero-tile--overlay .ban-hero-tile__img--fallback {
    width: 64px;
    height: 64px;
    font-size: 12px;
}
.ban-hero-tile--overlay .ban-hero-tile__name {
    font-size: 10px;
    max-width: 58px;
    padding: 1px 3px;
}

/* ── Pick vs Ban states (placed after --overlay so they win on source order) ── */

/* Picks: full-colour, vivid, stronger team-coloured glow. */
.ban-hero-tile--pick .ban-hero-tile__img {
    filter: none;
}
.ban-hero-tile--pick.ban-hero-tile--home {
    border-color: rgba(85, 136, 255, 0.85);
    box-shadow: 0 0 12px rgba(85, 136, 255, 0.35);
}
.ban-hero-tile--pick.ban-hero-tile--away {
    border-color: rgba(255, 85, 85, 0.85);
    box-shadow: 0 0 12px rgba(255, 85, 85, 0.35);
}

/* Bans: greyscale + dimmed, with a subtle "banned" marker. */
.ban-hero-tile--banned {
    position: relative;
    border-color: var(--app-border);
    box-shadow: none;
    opacity: 0.85;
}
.ban-hero-tile--banned .ban-hero-tile__img {
    filter: grayscale(100%) brightness(0.5);
}
.ban-hero-tile--banned:hover .ban-hero-tile__img {
    filter: grayscale(100%) brightness(0.65);
}
.ban-hero-tile--banned::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    /* only over the image area, not the name label */
    height: 64px;
    background: repeating-linear-gradient(135deg, rgba(237, 66, 69, 0.0) 0 6px, rgba(237, 66, 69, 0.18) 6px 8px);
    pointer-events: none;
}
.ban-hero-tile--banned .ban-hero-tile__name {
    color: var(--app-text-muted);
}

.hero-col-teamname {
    font-size: 12px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    text-align: center;
}
.hero-col-teamname--winner {
    color: var(--app-gold);
    /* Dark halo rather than a glow: gold on a bright patch of map art has too little contrast on its
       own, and every one of these banners is a photograph underneath. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

/* The winner said a second way. Colour alone is a poor answer to "who won" — it is one glance at a
   12px line for readers who see the hue, and no answer at all for those who do not. */
.hero-col-crown {
    margin-right: 0.15rem;
    font-size: 13px;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
}

.hero-col-level {
    font-size: 13px;
    font-weight: 700;
    /* On-dark, not the theme's muted grey: this banner is dark in both themes, and the theme token
       turns near-black on it in light mode. */
    color: var(--app-on-dark-muted);
    text-align: center;
}
.game-duration-badge {
    font-size: 11px;
    font-weight: 600;
    /* Same banner, same reason as .hero-col-level. */
    color: var(--app-on-dark-muted);
    text-align: center;
}

.detail-stats-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

/* Column heads carry the DS's recurring micro-label treatment — the same rule as .hl-label
   (hl-theme.css) and as the DS's own DataTable/StandingsTable column heads. Previously this
   was a hand-rolled near-miss: body face instead of the display face, weight 600 instead of
   500, and a hardcoded 0.5px letter-spacing instead of --tracking-caps. */
.detail-stats-table th {
    padding: 8px 10px;
    text-align: left;
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    font-weight: var(--weight-medium);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--fg-muted);
    border-bottom: 1px solid var(--app-border);
    user-select: none;
    white-space: nowrap;
}
/* A numeric column's head belongs over its figures. `.replay-stats-num` sets text-align:right, but
   on a <th> it was losing to the `.detail-stats-table th` rule above purely on specificity (0-1-1
   beats 0-1-0) — so every numeric column drew its label hard left while its numbers sat hard right.
   The <td> side never had the problem, which is exactly why the two ends stopped lining up. */
.detail-stats-table th.replay-stats-num {
    text-align: right;
}

/* Scoped to the element and the class rather than to `.detail-stats-table`, because ThSortable is
   also used inside MudSimpleTable now. Only ThSortable emits `.sortable`, so nothing else can pick
   these up — and a header that sorts but shows neither a pointer nor its arrow is a header nobody
   discovers is clickable. */
th.sortable {
    cursor: pointer;
    transition: color 0.15s;
}
th.sortable:hover {
    color: var(--app-text-muted);
}
th .sort-arrow {
    display: inline-block;
    margin-left: 2px;
    font-size: 9px;
    opacity: 0.5;
}
th.sort-active {
    color: var(--mud-palette-warning);
}
th.sort-active .sort-arrow {
    opacity: 1;
}

.hero-role-chip-icon { width: 16px; height: 16px; vertical-align: middle; margin-right: 4px; }

.detail-stats-table td {
    padding: 8px 10px;
    color: var(--mud-palette-text-primary);
    border-bottom: 1px solid var(--app-border);
    vertical-align: middle;
}

/* Bulk-action toolbar above a table, shown once rows are selected (TeamChanges,
   MatchChanges, PlayerChanges). Was three copies of the same inline
   Style="background: rgba(88,101,242,.1); border: 1px solid rgba(88,101,242,.3)" — the old
   Discord blurple, left behind by the palette flip. Now one class, mixed from the palette. */
.bulk-toolbar {
    background: color-mix(in srgb, var(--mud-palette-primary) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 30%, transparent);
}

/* Shared pager for .detail-stats-table (STYLE.md §4.4). */
.table-pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 8px 4px 0;
    font-size: var(--text-body-sm);
    color: var(--app-text-muted);
}
.table-pager__label { font-size: var(--text-body-xs); }
.table-pager__range { font-variant-numeric: tabular-nums; }
.table-pager__size { max-width: 96px; }

.detail-stat--max {
    color: var(--app-gold);
    font-weight: 800;
    text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}

/* Home/away row tints. These were raw rgba() of the old Discord blurple and red — the same
   two roles already have DS tokens (--app-team-home/away → --hl-team-a/b), so they now tint
   from those and follow the theme. */
.detail-stats-row--home td {
    background: color-mix(in srgb, var(--app-team-home) 8%, transparent);
}

.detail-stats-row--away td {
    background: color-mix(in srgb, var(--app-team-away) 8%, transparent);
}

/* Horizontal-scroll wrapper for wide .detail-stats-table on narrow viewports. */
.detail-stats-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Match/series result colouring — the winning side green, the losing side red. Reused wherever a
   result/score is shown (standings, recent results, match tables) so results always read the same. */
.result-win { color: var(--mud-palette-success); }
.result-loss { color: var(--mud-palette-error); }

/* Selection checkbox column in .detail-stats-table (e.g. TeamChanges.razor). */
.dst-check { width: 36px; }

/* Clickable-row affordance (STYLE.md §4.4): hover tint + primary left accent bar so it
   reads as clickable. Tint uses the theme-safe --app-hover-bg (works light+dark). */
.detail-stats-table tbody tr.row-clickable { cursor: pointer; transition: background 0.12s ease; }
.detail-stats-table tbody tr.row-clickable:hover td { background: var(--app-hover-bg); }
.detail-stats-table tbody tr.row-clickable:hover td:first-child {
    box-shadow: inset 3px 0 0 var(--mud-palette-primary);
}
@media (prefers-reduced-motion: reduce) {
    .detail-stats-table tbody tr.row-clickable { transition: none; }
}

/* Case-# cell accent (Cases.razor) — the DS mono face (was a literal 'Courier New' stack),
   which is what the DS reserves for IDs, scores and MMR. */
.case-number {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--app-fw-semibold);
    color: var(--app-case-number);
}

/* Game-history rows are shaded per MATCH (all games of a match share a tone), so it's easy to
   see which rows belong together. A left accent reinforces the grouping. */
.game-history-row--a td {
    background: var(--app-hover-bg);
}

/* Alternate match group. The tone only has to differ from --a's --app-hover-bg, so it is
   mixed from the palette primary instead of the raw violet-grey it used to hardcode. */
.game-history-row--b td {
    background: color-mix(in srgb, var(--mud-palette-primary) 7%, transparent);
}

.game-history-row--a td:first-child {
    box-shadow: inset 3px 0 0 color-mix(in srgb, var(--mud-palette-primary) 35%, transparent);
}

.game-history-row--b td:first-child {
    box-shadow: inset 3px 0 0 rgba(120, 130, 200, 0.6);
}

.stat-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 12px;
}

.stat-card {
    background: var(--app-surface-raised);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-md);
    padding: 12px;
    display: flex;
    flex-direction: column;
    height: 100%;
}

/* KPI value (DS StatCard, components/data/DataTable.jsx): the mono face with tabular
   figures, so the numbers in a tile grid line up column-wise instead of drifting. The
   display face is deliberately not used here — it is for titles, and it is uppercase. */
.stat-card__value {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--text-display-md);
    font-weight: var(--app-fw-semibold);
    line-height: var(--leading-tight);
    color: var(--mud-palette-text-primary);
}

/* Denser secondary grids (role distribution, per-map tiles) — the DS's smaller step. */
.stat-card__value--sm {
    font-size: var(--text-display-sm);
}

.stat-card__sub {
    font-size: var(--text-body-xs);
    line-height: var(--leading-snug);
    color: var(--fg-muted);
    margin-top: 2px;
}

/* Tonal values — the DS StatCard's `tone` prop. Pass the class via StatCard's ValueClass so
   call sites never carry a colour of their own. */
.stat-card__value--accent  { color: var(--mud-palette-primary); }
.stat-card__value--success { color: var(--mud-palette-success); }
.stat-card__value--warning { color: var(--mud-palette-warning); }
.stat-card__value--error   { color: var(--mud-palette-error); }

.stat-card--center {
    text-align: center;
    align-items: center;
    justify-content: center;
}

.stat-card--clickable {
    cursor: pointer;
}

/* Was a no-op — it re-set the border to the value the base rule already has. */
.stat-card--clickable:hover {
    border-color: var(--border-accent);
}

.detail-hero-cell {
    padding: 4px !important;
    width: 40px;
}

.detail-hero-icon {
    width: 36px;
    height: 36px;
    border-radius: 6px;
    object-fit: cover;
    background: var(--app-hover-bg);
    border: 1px solid var(--app-border);
}

/* MatchDetail XP-momentum chart (signature element). */
.mdx-xp-chart { padding: 4px 2px; }
.mdx-xp-title { font-weight: 600; }
.mdx-xp-plot {
    position: relative; height: 240px; border-radius: 8px;
    border: 1px solid var(--app-border); background: var(--app-surface-inner); padding: 12px;
}
.mdx-xp-svg { width: 100%; height: 100%; overflow: visible; }
.mdx-xp-ymax {
    position: absolute; top: 8px; left: 8px; font-size: 10px;
    font-variant-numeric: tabular-nums; color: var(--app-text-muted);
}
.mdx-xp-legend { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
.mdx-xp-swatch { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }
.mdx-xp-swatch-home { background: var(--mud-palette-primary); }
.mdx-xp-swatch-away { background: var(--mud-palette-error); }
.mdx-xp-milestone { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.mdx-xp-axis-time { font-size: var(--text-body-xs); color: var(--app-text-muted); font-variant-numeric: tabular-nums; }
.mdx-xp-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: mdx-xp-draw 900ms ease-out forwards; }
@keyframes mdx-xp-draw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
    .mdx-xp-line { animation: none; stroke-dashoffset: 0; }
}

/* Discord avatar in the top bar. The failed class is set by the image's own error handler so a CDN
   hiccup shows the fallback ring rather than a broken-image box. */
.app-user-avatar { cursor: pointer; width: 32px; height: 32px; }
.app-user-avatar img { width: 100%; height: 100%; object-fit: cover; }
.app-user-avatar--failed img { display: none; }
.app-user-avatar--failed { border: 1px solid var(--app-border); }

/* ── The navigation's search box (NavSearch.razor) ──────────────────────────────
   Quieter than a form field in the page body: it sits above the links and should read as part of the
   navigation, not as something to fill in. */
.nav-search__field .mud-input-outlined-border {
    border-color: var(--app-border);
}

.nav-search__field .mud-input-root {
    font-size: 0.85rem;
    background: var(--app-surface-raised);
}

/* The BattleTag or the fixture beside a suggestion — enough to tell two people with the same
   nickname apart, quiet enough not to compete with the name. */
.nav-search__subtitle {
    margin-left: auto;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

/* Who-is-online panel on the moderator badge.

   Where it sits is ActiveUsersBadge's problem and it solves it by being a menu anchored to the
   badge's right edge — no arithmetic here. What is left for CSS is the surface: a menu popover is
   a light paper sheet meant to hold menu items, and this is a dark panel of text.

   The dark stays dark in both themes, like every other --*-on-dark surface: the panel reads as an
   overlay on the toolbar, which is dark either way. */
.mud-popover.presence-pop {
    background: var(--surface-dark);
    color: var(--fg-on-dark);
    padding: 10px 14px;
}

/* The menu's list padding on top of the panel's own would be two sets of spacing; there are no menu
   items in here for it to space. */
.presence-pop .mud-menu-list {
    padding: 0;
    min-width: 0;
}

/* The width lives on the panel, not on the popover around it: MudBlazor's positioning JS writes
   max-width straight onto the popover node as an inline style, where no stylesheet can reach it.
   min() is the last line of defence — anchoring keeps the panel inside a window wider than it is,
   and this is what happens in a window that is not. */
.presence-tip {
    min-width: min(240px, calc(100vw - 32px));
    max-width: min(340px, calc(100vw - 32px));
    font-size: 13px;
    line-height: 1.6;
}
.presence-tip-head { font-weight: 600; font-size: 13.5px; margin-bottom: 7px; }
.presence-tip-empty { opacity: 0.7; }
.presence-tip-row { display: flex; align-items: center; gap: 10px; padding: 1px 0; }
.presence-tip-row--idle { opacity: 0.55; }
.presence-tip-dot {
    width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
    background: var(--mud-palette-success);
}
/* Same surface, same rule: a flipping colour would put a dark dot on the dark panel in light mode. */
.presence-tip-row--idle .presence-tip-dot { background: var(--app-on-dark-muted); }
.presence-tip-name { font-weight: 500; }
/* Pushed right and allowed to shrink, so a long name never squeezes the page out of view. */
.presence-tip-page {
    margin-left: auto; opacity: 0.75; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; max-width: 55%;
}
/* Since their last sign of life. Fixed width and right-aligned so the column reads as a column
   rather than as a ragged edge that moves whenever somebody clicks something. */
.presence-tip-seen {
    flex: 0 0 auto; min-width: 50px; text-align: right;
    opacity: 0.6; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.presence-tip-foot { margin-top: 7px; opacity: 0.6; font-size: 12px; }

/* Talent name with its icon in the tier table. Smaller than the build-row icons: there is one per
   row here rather than seven, and it sits beside text rather than standing in for it. */
.hero-talent-label { display: flex; align-items: center; gap: 8px; }
.hero-talent-label-icon {
    width: 22px; height: 22px; border-radius: 4px; object-fit: cover;
    flex: 0 0 auto; /* keeps the reserved box so rows do not jump as images land */
}

/* Collapsible roster header on the Heroes tab. */
.hero-roster-head {
    display: flex; align-items: center; cursor: pointer;
    margin-bottom: 12px; user-select: none;
}
.hero-roster-head:hover { color: var(--mud-palette-primary); }
.hero-roster-head:focus-visible { outline: 2px solid var(--mud-palette-primary); outline-offset: 2px; border-radius: 4px; }
.hero-roster-count {
    font-family: var(--font-mono); font-size: var(--text-body-sm);
    color: var(--app-text-muted); margin-left: 6px;
}

/* Hero page: talent tiers on the left, build tables on the right. The tier list is tall and narrow
   while the build tables are short and wide, so stacking them made the page far longer than it had
   to be. One column again once there is no room for two. */
.hero-talent-grid {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1rem; align-items: start;
}
.hero-talent-grid-col { min-width: 0; }
@media (max-width: 1200px) {
    .hero-talent-grid { grid-template-columns: minmax(0, 1fr); }
}

/* The level heading inside the single talent table. It is a row rather than a caption above its own
   table, which is what used to put Games and Win% at a different place in every tier. */
.detail-stats-table tr.hero-tier-row td {
    padding-top: 14px; border-bottom: 1px solid var(--app-border);
    font-family: var(--font-display); font-size: var(--text-body-xs);
    letter-spacing: var(--tracking-caps); text-transform: uppercase;
    color: var(--fg-muted); font-weight: var(--weight-medium);
}
.detail-stats-table tbody:first-of-type tr.hero-tier-row td { padding-top: 4px; }

/* Scrubbing playhead. Moved by the browser on every mouse move, so it never touches the circuit. */
.mdx-xp-playhead {
    position: absolute; top: 12px; bottom: 12px; width: 1px;
    background: var(--app-text-muted); opacity: 0.75;
    pointer-events: none; display: none; z-index: 1;
}

/* Crosshair tooltip on the XP chart. Positioned inside .mdx-xp-plot, which is already relative, and
   flipped to the left half once the cursor passes the middle so it never leaves the card. */
.mdx-xp-tip {
    position: absolute; top: 10px; z-index: 2;
    transform: translateX(8px);
    min-width: 150px; max-width: 260px;
    padding: 8px 10px; border-radius: 6px;
    background: var(--app-surface-card); border: 1px solid var(--app-border);
    box-shadow: 0 4px 14px rgb(0 0 0 / 0.28);
    font-size: 11px; line-height: 1.45;
    pointer-events: none; /* the strips underneath keep the hover */
}
.mdx-xp-tip--left { transform: translateX(calc(-100% - 8px)); }
.mdx-xp-tip-time {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    color: var(--app-text-muted); margin-bottom: 4px;
}
.mdx-xp-tip-team + .mdx-xp-tip-team { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--app-border); }
.mdx-xp-tip-name { font-weight: 600; }
.mdx-xp-tip-total { font-variant-numeric: tabular-nums; margin-bottom: 2px; }
/* Label, change, figure. Three tracks so the numbers line up down the tooltip whether or not a
   measurement is running; the middle one keeps its width when empty. */
.mdx-xp-tip-row {
    display: grid; grid-template-columns: 1fr auto auto;
    gap: 4px 10px; align-items: baseline;
    color: var(--app-text-muted); font-variant-numeric: tabular-nums;
}
.mdx-xp-tip-row--total { color: inherit; margin-bottom: 2px; }
.mdx-xp-tip-delta { min-width: 3.5em; text-align: right; }
.mdx-xp-tip-delta--up { color: var(--mud-palette-success); }
.mdx-xp-tip-delta--down { color: var(--mud-palette-error); }
.mdx-xp-tip-value { min-width: 4em; text-align: right; }
/* The level-mark tooltip is a three-line note, not a table, so it does not need the tip's width. */
.mdx-xp-tip--tick { min-width: 0; }
.mdx-xp-tip-hint {
    margin-top: 6px; padding-top: 5px; border-top: 1px solid var(--app-border);
    font-size: 10px; opacity: 0.65;
}

/* MatchDetail XP-source donut charts (final cumulative totals, per team). */
.mdx-xpd-charts { display: flex; flex-wrap: wrap; gap: 24px; justify-content: center; }
.mdx-xpd-team { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.mdx-xpd-team-title { color: var(--app-text-muted); }
.mdx-xpd-plot { width: 160px; max-width: 100%; }
.mdx-xpd-svg { width: 100%; height: auto; }
.mdx-xpd-center { fill: var(--mud-palette-text-primary); font-size: 6px; font-weight: 700; }

/* MatchDetail advanced section. */
.mdx-advanced-divider { display: flex; align-items: center; gap: 12px; margin: 8px 0 12px; }
.mdx-advanced-divider::before, .mdx-advanced-divider::after {
    content: ""; flex: 1; height: 1px; background: var(--app-border);
}
.mdx-advanced-divider span { font-size: 11px; letter-spacing: 2px; color: var(--app-text-muted); font-weight: 700; }
.mdx-awards { display: flex; flex-wrap: wrap; gap: 12px; }
.mdx-award-badge {
    display: flex; align-items: center; gap: 8px; padding: 8px 12px;
    border-radius: 8px; border: 1px solid var(--app-border); background: var(--app-surface-inner);
}
.mdx-award-badge--home { border-color: var(--mud-palette-primary); }
.mdx-award-badge--away { border-color: var(--mud-palette-error); }
/* MVP always wins the border treatment, even for home/away award-holders. */
.mdx-award-badge--mvp { border-color: var(--app-gold); box-shadow: 0 0 0 1px var(--app-gold) inset; }
.mdx-award-icon { width: 48px; height: 48px; }
.mdx-award-hero { width: 28px; height: 28px; border-radius: 4px; }
.mdx-award-label { font-size: 12px; font-weight: 700; color: var(--mud-palette-text-primary); }
.mdx-award-badge--mvp .mdx-award-label { color: var(--app-gold); }
.mdx-award-player { font-size: 11px; color: var(--app-text-muted); }

/* MatchDetail teamfight timeline (signature element). */
.mdx-timeline-title { font-weight: 600; }
.mdx-timeline-fb-time { font-weight: 700; font-variant-numeric: tabular-nums; }
.mdx-timeline-plot {
    position: relative; height: 56px; border-radius: 8px;
    border: 1px solid var(--app-border); background: var(--app-surface-inner); padding: 8px 12px;
}
.mdx-timeline-svg { width: 100%; height: 100%; overflow: visible; }
.mdx-timeline-axis-time { font-size: var(--text-body-xs); color: var(--app-text-muted); font-variant-numeric: tabular-nums; }
.mdx-timeline-time { font-variant-numeric: tabular-nums; }
.mdx-timeline-table-wrap { overflow-x: auto; }

.mdx-fight-rect { cursor: pointer; transition: opacity .12s ease; }
.mdx-fight-rect:hover { opacity: .8; }
.mdx-fight-rect--sel { stroke: var(--mud-palette-text-primary); stroke-width: 0.6; }
.mdx-fight-card {
    background: var(--app-surface-card); border: 1px solid var(--app-border);
    border-radius: var(--app-radius-md, 8px); padding: 12px;
}
.mdx-fight-card-head { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.mdx-fight-card-title { font-weight: var(--app-fw-semibold, 600); }
.mdx-fight-score { font-variant-numeric: tabular-nums; font-weight: var(--app-fw-semibold, 600); }
.mdx-kill-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.mdx-kill-time { font-variant-numeric: tabular-nums; color: var(--app-text-muted); min-width: 44px; font-size: var(--text-body-sm); }
.mdx-kill-side { display: inline-flex; align-items: center; gap: 6px; }
.mdx-kill-hero { width: 22px; height: 22px; border-radius: var(--app-radius-sm, 6px); }
.mdx-kill-arrow { color: var(--app-text-muted); }

/* MatchDetail objective timeline (mirrors the teamfight timeline). */
.mdx-obj-details { display: flex; flex-wrap: wrap; gap: 8px; }
.mdx-obj-detail {
    display: flex; flex-direction: column; background: var(--app-surface-inner);
    border: 1px solid var(--app-border); border-radius: var(--app-radius-sm, 6px); padding: 6px 10px;
}
.mdx-obj-detail-label { font-size: var(--text-body-xs); color: var(--app-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
.mdx-obj-detail-val { font-variant-numeric: tabular-nums; font-weight: var(--app-fw-semibold, 600); }

/* MatchDetail unified event timeline (replaces the separate teamfight/objective timelines) —
   Stats-of-the-Storm-style category lanes: one row per event category, markers positioned by
   time inside each lane's track. The lane labels double as the legend (no emoji glyph key). */
/* Kept as a fallback for viewports below the min-width, where the lanes genuinely cannot fit.
   Everything above that fits without scrolling — see .mdx-lane-plot. */
.mdx-lanes-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.mdx-lanes { min-width: 640px; --lane-label: 110px; --lane-gutter: 40px; }
.mdx-lane { display: flex; align-items: stretch; border-bottom: 1px solid var(--app-border); }
.mdx-lane-label {
    flex: 0 0 var(--lane-label); font-size: var(--text-body-xs); text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--app-text-muted); padding: 6px 8px; display: flex; align-items: center;
}
/* Track height is set inline per lane from the number of stacked sub-rows the layout needed. */
.mdx-lane-track { position: relative; flex: 1; min-height: 34px; }
/* The plot the markers are positioned in, inset from the track by a gutter on each side.
   A chip is centred on its own timestamp, so half of the first one reaches back past 0:00 and half
   of the last past the final second. Without the gutter those two halves overflowed the card — the
   whole reason this timeline had a scrollbar that moved barely a chip's width, taking the lane
   labels out of view with it. The gutter is a little wider than half the widest chip. */
.mdx-lane-plot { position: absolute; inset: 0 var(--lane-gutter); }
/* Dashed vertical gridline every 5 minutes of game time, spanning the full lane track. */
.mdx-lane-gridline { position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(--app-border); }
/* The ends of the scale, aligned to the plot rather than to the card: past the lane labels, inside
   the gutters — otherwise they name times the markers underneath them never reach. */
.mdx-lanes-axis {
    display: flex; justify-content: space-between;
    margin-left: var(--lane-label); padding: 4px var(--lane-gutter) 0;
}
.mdx-ev-chip {
    position: absolute; transform: translateX(-50%); display: inline-flex; align-items: center; gap: 3px;
    padding: 2px 6px; border-radius: var(--app-radius-sm, 6px); background: var(--app-surface-inner);
    border: 1px solid var(--app-border); font-size: var(--text-body-xs); cursor: pointer; white-space: nowrap;
}
/* Teamfight chips span a time range (anchored at the fight's start), so they must not be re-centered. */
.mdx-ev-chip--span { transform: none; }
.mdx-ev-chip--home { border-color: var(--mud-palette-primary); }
.mdx-ev-chip--away { border-color: var(--mud-palette-error); }
.mdx-ev-chip--sel { outline: 2px solid var(--mud-palette-text-primary); }
.mdx-ev-chip:hover { background: var(--app-hover-bg); }
.mdx-ev-hero { width: 20px; height: 20px; border-radius: var(--app-radius-sm, 6px); }
.mdx-ev-obj { width: 20px; height: 24px; object-fit: contain; }
.mdx-et-line { padding: 4px 0; }
/* Members of a clustered ("N×") chip stack in the pinned card, separated by a hairline. */
.mdx-et-cluster-item + .mdx-et-cluster-item { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--app-border); }

.detail-hero-name {
    font-size: 11px;
    color: var(--app-text-muted);
    display: block;
    margin-top: 1px;
}

/* ── SeasonMultiSelect: searchable season picker (search + selected chips + scrollable list) ── */
.season-ms {
    width: 100%;
}
/* Three-column season grid: Regular / Events / Offseason. Collapses to fewer columns when narrow. */
.season-ms__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 12px;
}
.season-ms__col {
    border: 1px solid var(--app-border);
    border-radius: 8px;
    background: var(--app-surface-card);
    padding: 4px;
    max-height: 340px;
    overflow-y: auto;
}
.season-ms__col-head {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--app-surface-card);
    padding: 6px 8px 4px;
    font-size: var(--text-body-xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
    color: var(--app-text-muted);
    /* The head carries its own All/None button now, so it has two children to lay out. z-index goes
       with the sticky position: without it the rows scrolling under it draw on top. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
/* Small enough to read as part of the heading rather than as an action bar of its own. */
.season-ms__col-all {
    min-width: 0;
    padding: 0 6px;
    font-size: var(--text-body-xs);
    letter-spacing: 0.5px;
}
.season-ms__col-empty {
    padding: 8px;
    font-size: var(--text-body-sm);
    color: var(--app-text-muted);
}
/* Selected-chips bar: always visible when ≥1 selected; wraps and caps its height for many picks. */
.season-ms__selected {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    max-height: 96px;
    overflow-y: auto;
    padding: 6px 8px;
    border: 1px solid var(--app-border);
    border-radius: 8px;
    background: var(--app-surface-inner);
}
.season-ms__count {
    font-size: var(--text-body-xs);
    font-weight: 600;
    color: var(--app-text-muted);
    margin-right: 2px;
}
.season-ms__clear {
    margin-left: auto;
}
/* Scrollable list with a fixed max height — replaces the ~800px badge grid. */
.season-ms__list {
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid var(--app-border);
    border-radius: 8px;
    background: var(--app-surface-card);
    padding: 4px;
}
.season-ms__row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 6px;
    cursor: pointer;
    user-select: none;
}
.season-ms__row:hover {
    background: var(--mud-palette-action-default-hover);
}
.season-ms__row:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}
.season-ms__row--selected {
    background: var(--mud-palette-primary-hover);
}
.season-ms__label {
    font-size: var(--text-body-sm);
    color: var(--mud-palette-text-primary);
}
.season-ms__empty {
    padding: 16px 8px;
    text-align: center;
    font-size: var(--text-body-sm);
    color: var(--app-text-muted);
}

/* PlayerProfile substitutes mini-cards. */
.sub-mini-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 8px;
}
.sub-mini-card {
    border: 1px solid var(--app-border);
    background: var(--app-surface-inner);
    border-radius: 8px;
    padding: 8px 10px;
}
.sub-mini-card__meta {
    font-size: var(--text-body-xs);
    color: var(--app-text-muted);
}

/* PlayerProfile season-limit rows. */
.limit-row { margin-bottom: 10px; font-size: var(--text-body-sm); color: var(--mud-palette-text-primary); }
.limit-row__count { font-variant-numeric: tabular-nums; color: var(--app-text-muted); }

/* HeroicTickets — channel names read as Discord identifiers, so give them the monospace treatment.
   Shared by the ticket list and the detail page (scoped CSS cannot span both). */
.ticket-channel {
    font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono", Menlo, Consolas, monospace;
    font-size: 0.85em;
    color: var(--app-text-muted);
}

/* A ticket category's emoji, wherever it is shown. Lives here rather than in a scoped file because
   DiscordEmojiView is used from more than one page. Sized to sit on a text line without changing it. */
.discord-emoji {
    display: inline-block;
    width: 1.25em;
    height: 1.25em;
    vertical-align: -0.25em;
    object-fit: contain;
}

/* A unicode emoji is a character, not a picture — it only needs the same footprint. */
.discord-emoji--char {
    width: auto;
    height: auto;
    line-height: 1;
    vertical-align: baseline;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════
   Settings page (DS SettingsControls — components/forms/SettingsControls.jsx)

   These live here and not in Settings.razor.css on purpose. Blazor's CSS isolation stamps
   its b-<hash> attribute only onto markup authored in the .razor file; MudBlazor components
   render their own DOM and never receive it. Every rule below targets a MudBlazor element
   (MudExpansionPanel, MudText, MudChip, MudTextField, MudPaper), so as scoped CSS they
   matched nothing at all — the panel borders, the inline-input width and the save bar had
   been silently dead since they were moved out of inline styles. ::deep is no way out
   either: the page renders MudExpansionPanels at its root, so there is no own element for
   the scope attribute to sit on.
   ═══════════════════════════════════════════════════════════════════════════════════════ */

/* ── DS SettingsSection (components/forms/SettingsControls.jsx) ────────────────────────
   The DS built this group from screenshots of this very page, so the structure already
   matched; what follows is the treatment it prescribes. */

.settings-panel {
    border: 1px solid var(--app-border);
}

/* Section title in the display face. Deliberately NOT uppercased: the DS's own
   SettingsSection sets font-family only, and these titles run long
   ("Draft / Match-Format / Map-Pool") — uppercase Oswald would dominate the panel. */
.settings-panel__title {
    font-family: var(--font-display);
    font-weight: var(--app-fw-semibold);
    letter-spacing: var(--tracking-display);
}

/* Status badges next to the title. The DS draws these as flat blue pills; the colour here
   still carries state (green = active, blue = tournament), which the DS's monochrome
   version would throw away — so only its shape and type scale are adopted. */
/* .mud-chip is repeated in the selector so the DS's 11px wins over MudChip's own 12px —
   without it the font-size is the one rule of these that silently loses. */
.settings-panel__badge.mud-chip {
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
}

/* ── DS FormField hint ────────────────────────────────────────────────────────────────
   The slash command that changes a setting from Discord. It used to hide inside a tooltip
   on an info icon, which meant it was invisible unless you knew to hover. The DS FormField
   puts the hint on the page, under the control. Mono, because it is a command to type. */
.settings-hint {
    display: block;
    margin-top: 4px;
    font-family: var(--font-mono);
    font-size: var(--text-body-xs);
    color: var(--fg-muted);
}

/* ── DS Chip ──────────────────────────────────────────────────────────────────────────
   Removable tag for list values (case categories, map pool). The DS gives the chip itself
   a neutral surface and reserves the loss colour for the remove control alone — the old
   fully-red/blue outlined chips read as a status, which a category name is not. */
.settings-chip.mud-chip {
    background: var(--app-surface-inner);
    border: 1px solid var(--app-border);
    color: var(--mud-palette-text-primary);
    border-radius: var(--app-radius-pill);
    font-size: var(--text-body-sm);
}

.settings-chip.mud-chip .mud-chip-close-button {
    color: var(--mud-palette-error);
}

/* Shared width for the small inline "add" text fields (new category / new map). */
.settings-inline-input {
    max-width: 280px;
}

/* Floating "unsaved changes" bar. Translucent raised surface + primary-tinted border, both
   built from color-mix() over theme variables so they flip automatically with Light/Dark
   (STYLE.md §2.4 — overlay effect, not a flat hardcoded color). */
.settings-savebar {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1300;
    border-radius: var(--app-radius-md, 8px);
    min-width: 480px;
    max-width: 700px;
    /* Opaque, not translucent-with-blur: the DS records "Transparency/blur: none observed"
       and asks for flat surfaces. The bar floats over content, so it has to be readable —
       a solid surface does that without the blur the DS rules out (STYLE.md §4.5). */
    background: var(--app-surface-raised);
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 30%, transparent);
    box-shadow: var(--app-shadow-hover);
}


/* ── Season-stats: filter scope ──────────────────────────────────────────────────
   The Heroes tab's Division/Role chips sit once at the top of the tab and no longer cut
   only the table directly under them. Since the control and the numbers it narrows are now
   far apart, each affected container says so itself: a primary edge down its left side plus
   a pill in the heading naming the active filter (STYLE.md §1.4 — recurring optic as a class). */
.filtered-panel {
    border-left: 3px solid var(--mud-palette-primary) !important;
}

.filter-scope-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    vertical-align: middle;
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: var(--app-radius-pill);
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 40%, transparent);
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    font-weight: var(--app-fw-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--mud-palette-primary);
}

/* A name that keeps the body text colour until you reach for it. Used where a whole leaderboard
   column is clickable and painting every row primary would read as noise (season-stats "Season
   Totals"); the affordance arrives on hover/focus instead of being permanently on. */
.subtle-link {
    color: var(--mud-palette-text-primary);
    cursor: pointer;
    text-decoration: none;
    border-bottom: 1px dotted transparent;
    transition: color 0.15s, border-color 0.15s;
}

.subtle-link:hover,
.subtle-link:focus-visible {
    color: var(--mud-palette-primary);
    border-bottom-color: var(--mud-palette-primary);
    outline: none;
}

/* ── Matchup matrix ───────────────────────────────────────────────────────────────
   A hero-vs-hero grid can run to 90 columns, so the grid scrolls inside its own box while
   both heads stay put. Column heads are icons (a name per column is unreadable at that width),
   row heads carry icon + name. Cell tints are mixed from the palette in the component so they
   follow the theme; everything structural lives here. */
.matchup-matrix {
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-md, 8px);
    background: var(--app-surface-inner);
}

.matchup-matrix__table {
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-body-xs);
    font-variant-numeric: tabular-nums;
}

.matchup-matrix__table th,
.matchup-matrix__table td {
    border-right: 1px solid var(--app-border);
    border-bottom: 1px solid var(--app-border);
}

/* The two heads and the corner stack in that order: the corner has to stay above both. */
.matchup-matrix__colhead {
    position: sticky;
    top: 0;
    z-index: 2;
    width: 34px;
    min-width: 34px;
    padding: 4px;
    text-align: center;
    background: var(--app-surface-card);
}

.matchup-matrix__colhead img {
    width: 26px;
    height: 26px;
    border-radius: 3px;
    display: block;
    margin: 0 auto;
}

.matchup-matrix__abbr {
    font-family: var(--font-display);
    font-weight: var(--app-fw-semibold);
    color: var(--fg-muted);
}

.matchup-matrix__rowhead {
    position: sticky;
    left: 0;
    /* Same layer as the column heads: both have to paint over the tinted cells sliding under them,
       and the corner (3) stays over both. */
    z-index: 2;
    padding: 4px 10px 4px 6px;
    text-align: left;
    white-space: nowrap;
    font-weight: var(--app-fw-semibold);
    background: var(--app-surface-card);
    display: table-cell;
}

.matchup-matrix__rowhead img {
    width: 22px;
    height: 22px;
    border-radius: 3px;
    vertical-align: middle;
    margin-right: 6px;
}

.matchup-matrix__corner {
    position: sticky;
    top: 0;
    left: 0;
    z-index: 3;
    padding: 4px 8px;
    text-align: left;
    white-space: nowrap;
    font-family: var(--font-display);
    font-size: var(--text-body-xs);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--fg-muted);
    background: var(--app-surface-card);
}

.matchup-matrix__cell {
    width: 34px;
    min-width: 34px;
    padding: 4px 2px;
    text-align: center;
    cursor: pointer;
    color: var(--mud-palette-text-primary);
}

.matchup-matrix__cell:hover {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

/* Too few games to mean anything — kept visible, but visibly not evidence. */
.matchup-matrix__cell--thin {
    opacity: 0.4;
    font-style: italic;
}

.matchup-matrix__diagonal {
    width: 34px;
    min-width: 34px;
    background: var(--app-hover-bg);
}

.matchup-matrix__empty {
    width: 34px;
    min-width: 34px;
}

.matchup-matrix__total {
    padding: 4px 8px;
    text-align: center;
    font-weight: var(--app-fw-semibold);
    background: var(--app-surface-card);
}

/* Full-page variant: tighter columns and a smaller portrait, because every pixel saved is one more
   hero on screen before anybody has to scroll. Row heads keep their name — a grid of eighty icons
   with no labels down the side is not readable. */
.matchup-matrix--compact .matchup-matrix__table { font-size: 11px; }

.matchup-matrix--compact .matchup-matrix__colhead,
.matchup-matrix--compact .matchup-matrix__cell,
.matchup-matrix--compact .matchup-matrix__diagonal,
.matchup-matrix--compact .matchup-matrix__empty {
    width: 28px;
    min-width: 28px;
}

.matchup-matrix--compact .matchup-matrix__colhead { padding: 3px 1px; }
.matchup-matrix--compact .matchup-matrix__colhead img { width: 22px; height: 22px; }
.matchup-matrix--compact .matchup-matrix__cell { padding: 3px 1px; }
.matchup-matrix--compact .matchup-matrix__rowhead { padding: 3px 8px 3px 4px; }
.matchup-matrix--compact .matchup-matrix__rowhead img { width: 18px; height: 18px; margin-right: 4px; }

/* The pinned cell, above the grid: one line, so it never pushes the matrix out of view. */
.matchup-detail {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding: 6px 12px;
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 40%, transparent);
    background: color-mix(in srgb, var(--mud-palette-primary) 8%, transparent);
    border-radius: var(--app-radius-md, 8px);
    font-size: var(--text-body-sm);
}

.matchup-detail__pair { font-weight: var(--app-fw-semibold); }

.matchup-detail__warn { color: var(--mud-palette-warning); }

/* Hero icons on the profile pages. Global, not scoped: the player profile and the team profile draw
   the same tables, and a scoped stylesheet only reaches the component it is named after — which is
   how the team's match history ended up with hero portraits at their natural size. */
.profile-hero-icon {
    width: 24px;
    height: 24px;
    border-radius: var(--app-radius-sm, 6px);
}

/* Match-history hero icon (20 px per spec §5.4). */
.profile-hist-hero-icon {
    width: 20px;
    height: 20px;
    border-radius: var(--app-radius-sm, 6px);
}

/* ── Match history, player profile and team profile ─────────────────────────────
   A match is a row and its games are rows under it, indented. The indent is the whole point: three
   lines that belong together used to read as three separate results.

   Global rather than scoped to one page: two pages draw this table, and a scoped sheet only reaches
   the component it is named after. */
.profile-history .profile-match-row > td {
    border-top: 1px solid var(--app-border);
}

.profile-history .profile-game-row > td {
    padding-top: 2px;
    padding-bottom: 2px;
    font-size: 0.8125rem;
}

/* The step in from the match above. On the first cell only, so the row still lines up column by
   column with the match it belongs to. */
.profile-history .profile-game-row > td:first-child {
    padding-left: 28px;
}

.profile-game-result {
    font-size: 0.6875rem;
    font-weight: 600;
    opacity: 0.75;
}

/* The games won and lost, beside the letter that says how it ended. */
.profile-match-score {
    margin-left: 6px;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}

/* ── Roster avatars (RosterAvatars.razor) ───────────────────────────────────────
   A team's people as faces. The ring carries the role as colour, so the row can be read at a glance
   without decoding icons; the icon itself sits on the lower edge of the circle, overlapping it, which
   is what keeps the name below on its own line instead of a third row of symbols. */
.roster-avatars {
    display: flex;
    flex-wrap: wrap;
    gap: 18px 20px;
}

/* Fixed width, and allowed to be it. A flex item will not shrink below its content by default, so
   without min-width:0 a long name makes its whole column wider than the rest — which is what turned
   an even row into ragged gaps. */
.roster-avatar {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    flex: 0 0 72px;
    min-width: 0;
    text-decoration: none;
    color: inherit;
}

/* MudTooltip's own wrapper sits between the link and the ring; it holds only the face now, and has
   to be as wide as the column so the ring lands over the middle of the name below it.

   Three classes deep on purpose. MudBlazor centres nothing here and sets `.mud-tooltip-root
   .mud-tooltip-inline { display: inline-block }` from two classes, which outranks a single
   `.roster-avatar__tip` however late this file loads — so the wrapper stayed a block, the ring sat
   against its left edge, and the face ended up 8px left of the name it belongs to. */
.roster-avatars .roster-avatar .roster-avatar__tip {
    display: flex;
    justify-content: center;
    width: 100%;
    min-width: 0;
}

/* The ring is the role. Its colour is set per role below; this is the shape and the default. */
.roster-avatar__ring {
    position: relative;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    border: 2px solid var(--app-border);
    background: var(--app-surface-raised);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.roster-avatar:hover .roster-avatar__ring {
    transform: translateY(-2px);
    box-shadow: var(--app-shadow-hover);
}

.roster-avatar__image {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

.roster-avatar__initials {
    font-family: var(--font-display);
    font-weight: var(--app-fw-semibold);
    font-size: 1.1rem;
    color: var(--mud-palette-text-secondary);
}

/* Overlapping the lower edge on purpose: inside the circle it would fight the face, below it it
   would be a second line. Half in and half out belongs to both and crowds neither. */
.roster-avatar__role {
    position: absolute;
    bottom: -8px;
    left: 50%;
    transform: translateX(-50%);
    width: 22px;
    height: 22px;
    padding: 2px;
    border-radius: 50%;
    background: var(--app-surface-card);
    border: 1px solid var(--app-border);
}

.roster-avatar__captain {
    position: absolute;
    top: -4px;
    right: -4px;
    font-size: 0.75rem;
    line-height: 1;
    color: var(--mud-palette-warning);
    text-shadow: 0 0 3px var(--app-surface-card);
}

/* display:block is what makes the rest of this work: max-width, overflow and text-overflow do
   nothing at all on a non-replaced inline element, so the name was never being clipped. */
.roster-avatar__name {
    display: block;
    width: 100%;
    font-size: 0.8125rem;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--mud-palette-text-primary);
}

.roster-avatar:hover .roster-avatar__name {
    color: var(--mud-palette-primary);
}

/* One colour per role. The two assassins share a family and differ in weight, because they are two
   kinds of the same job and a row of six unrelated colours reads as noise. */
.roster-avatar--tank .roster-avatar__ring { border-color: var(--mud-palette-info); }
.roster-avatar--bruiser .roster-avatar__ring { border-color: var(--mud-palette-warning); }
.roster-avatar--melee-assassin .roster-avatar__ring { border-color: var(--mud-palette-error); }
.roster-avatar--ranged-assassin .roster-avatar__ring {
    border-color: color-mix(in srgb, var(--mud-palette-error) 60%, var(--mud-palette-warning));
}
.roster-avatar--healer .roster-avatar__ring { border-color: var(--mud-palette-success); }
.roster-avatar--support .roster-avatar__ring { border-color: var(--mud-palette-tertiary); }

/* ── The page's loading indicator (LoadingOverlay.razor) ────────────────────────
   Fixed to the bottom right, above everything, and deliberately not modal: the page underneath stays
   readable and usable while the next answer is worked out. A panel that takes the content's place
   reads as a reload, which is the thing this exists to stop. */
.page-load {
    position: fixed;
    right: 24px;
    bottom: 24px;
    z-index: var(--mud-zindex-snackbar, 1400);
    width: min(340px, calc(100vw - 48px));
    padding: 14px 16px;
    border-radius: var(--app-radius-lg, 12px);
    background: var(--app-surface-card);
    border: 1px solid var(--app-border);
    box-shadow: var(--app-shadow-hover);
    animation: page-load-in 0.18s ease-out;
}

.page-load__title {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 0.875rem;
    font-weight: var(--app-fw-semibold, 600);
}

.page-load__detail {
    margin-top: 6px;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}

@keyframes page-load-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

/* ── Eingeklappte Navigationsschiene ──────────────────────────────────────────
   MudBlazor blendet in der Mini-Schublade die Beschriftung eines einfachen Links selbst
   aus. Was es nicht kann, sind Gruppen, das Suchfeld und der Hälften-Umschalter — die
   ersetzt NavMenu durch Icons, und was hier steht, richtet sie in der 56px-Spalte aus. */
.app-drawer.mud-drawer--closed .nav-rail__button {
    margin: 2px auto;
    display: flex;
}

/* Ein Navigationslink in der Schiene ist ein Icon und sonst nichts.

   MudBlazor blendet in der Mini-Schublade die Beschriftung aus, lässt dem Link aber seine
   seitliche Polsterung von 16px. Die Rechnung geht dann nicht auf: 56px Schiene, minus 2×8px
   Polsterung des Menüs (pa-2), minus 2×16px des Links — bleiben 8px Inhaltsbreite für ein 24px
   breites Icon.

   Ein Icon, das nicht passt, ragt nicht heraus: MudBlazor macht den Link in der Mini-Schublade zu
   einem Flex-Container, und ein Flex-Element schrumpft standardmäßig. Das Icon wurde also auf ein
   Drittel seiner Breite gequetscht, bei voller Höhe — genau das sah aus, als wären die Icons halb
   verschwunden. Die Icon-Buttons daneben (Suche, Hälften-Umschalter, Gruppen) waren nie betroffen,
   weil sie keine .mud-nav-link sind; deshalb wirkte nur ein Teil der Schiene kaputt.

   Also: keine Seitenpolsterung, Icon mittig, und es schrumpft nie wieder. */
.app-drawer.mud-drawer--closed.mud-drawer-mini .mud-nav-link {
    padding-left: 0;
    padding-right: 0;
    justify-content: center;
}

.app-drawer.mud-drawer--closed.mud-drawer-mini .mud-nav-link .mud-icon-root {
    flex: 0 0 auto;
}

/* Die aktive Kachel trägt aufgeklappt 8px Seitenluft, damit sie als Kachel liest und nicht als
   Vollbreiten-Balken. In der Schiene sind 2×8px ein Sechstel der Spalte, und sie gingen von genau
   der Breite ab, die dem Icon fehlte. Auf 4px reduziert: die Kachel bleibt eine Kachel, und aktiv
   wie inaktiv sitzt das Icon auf derselben Mittelachse, sodass nichts springt. */
.app-drawer.mud-drawer--closed.mud-drawer-mini .mud-nav-link.active:not(.mud-nav-link-disabled) {
    margin-inline: 4px;
}

/* Ein aufgeklapptes Untermenü der Schiene. Erst der Gruppenname — ohne ihn wäre es eine
   Liste ohne Überschrift, und die Schiene zeigt keinen. */
.nav-rail__flyout {
    min-width: 220px;
}

.nav-rail__flyout-title {
    padding: 8px 16px 4px;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

/* The result column was sized inline until its header became sortable; ThSortable takes a class,
   not a style, so the width moved here rather than being dropped. */
.profile-history th.hist-result-col {
    width: 70px;
}

/* Map thumbnail inside the Draft by Map table — same 16:9 crop the other map rows use, one size down
   because this table has six columns and the picture is the label, not the subject. */
.draft-map-thumb {
    width: 40px;
    height: 23px;
    border-radius: 4px;
    object-fit: cover;
    flex: 0 0 auto;
}
