﻿html, body {
    font-family: 'Roboto', sans-serif;
}

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

/* ============================================================
   Dashboard / panel layer (Path A visual pass).
   Built on MudBlazor's own --mud-palette-* variables so every
   surface adapts automatically to light/dark themes.
   ============================================================ */

.ff-page-head { margin-bottom: 20px; }
.ff-page-head h1 { font-size: 1.7rem; font-weight: 700; letter-spacing: -.01em; margin: 0; line-height: 1.15; }
.ff-page-sub { color: var(--mud-palette-text-secondary); font-size: .9rem; margin-top: 4px; }

/* Phase 21D: section divider heading on the rebuilt /stats league-history grid. Named distinctly from
   .ff-records-section-head so the two pages' section counts never collide in tests. */
.ff-stats-section-head {
    display: flex; align-items: center; gap: 8px;
    margin: 20px 0 2px; font-size: 1.02rem; font-weight: 700;
}

/* KPI tiles */
.ff-tile {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 14px;
    padding: 16px 18px;
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-height: 104px;
}
.ff-tile-label {
    font-size: .7rem; letter-spacing: .09em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary); font-weight: 600;
    display: flex; align-items: center; gap: 6px;
}
.ff-tile-value {
    font-size: 1.85rem; font-weight: 700; line-height: 1.05;
    font-variant-numeric: tabular-nums; color: var(--mud-palette-text-primary);
}
.ff-tile-value .u { font-size: .95rem; font-weight: 500; color: var(--mud-palette-text-secondary); }
/* A date, not a number: it needs to wrap inside a half-width tile at 360px rather than run past the
   tile edge, so it drops the big display size and is allowed to break between the date and the time. */
.ff-tile-value-date { font-size: 1.15rem; line-height: 1.2; overflow-wrap: break-word; }
@media (min-width: 600px) { .ff-tile-value-date { font-size: 1.25rem; } }
.ff-tile-sub { font-size: .76rem; color: var(--mud-palette-text-secondary); margin-top: auto; }

/* Section panels */
.ff-panel {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 14px;
    overflow: hidden;
}
.ff-panel-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 13px 16px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-panel-head .ttl { font-size: .98rem; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.ff-panel-body { padding: 6px; }
.ff-panel-body.pad { padding: 16px; }

/* Standings rows */
.ff-standing {
    display: grid; grid-template-columns: 30px 1fr auto; align-items: center; gap: 12px;
    padding: 9px 10px; border-radius: 9px;
}
.ff-standing.me { background: rgba(var(--mud-palette-primary-rgb), .13); }
.ff-standing .rank {
    width: 26px; height: 26px; border-radius: 7px; display: flex; align-items: center; justify-content: center;
    font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--mud-palette-background); color: var(--mud-palette-text-secondary);
}
.ff-standing.lead .rank { background: var(--mud-palette-primary); color: #fff; }
.ff-standing .who { font-weight: 600; font-size: .89rem; display: flex; align-items: center; gap: 8px; min-width: 0; }
.ff-standing .who .who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ff-standing .who .metag { font-size: .68rem; font-weight: 700; color: var(--mud-palette-primary); margin-left: 6px; letter-spacing: .04em; }
/* Phase 15B: keep the scoreboard avatar compact so the row height doesn't grow. */
.ff-standing .ff-standing-av { width: 26px; height: 26px; min-width: 26px; font-size: .68rem; }

/* Phase 15B: the shared member chip (avatar + resolved team name), used across surfaces. */
.ff-member-chip { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.ff-member-chip .ff-member-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Batch B (B2): a linkified member chip stays visually quiet (inherits color/weight) and only
   reveals its linkiness on hover, so dense member lists don't turn loud. */
.ff-member-chip.ff-link { color: inherit; font-weight: inherit; }
.ff-member-chip.ff-link:hover { text-decoration: none; }
.ff-member-chip.ff-link:hover .ff-member-chip-name { text-decoration: underline; color: var(--mud-palette-primary); }

/* Phase 15C: public profile + the Account-Settings public-profile editor. */
.ff-profile-hero { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ff-profile-hero-text { min-width: 0; }
.ff-profile-name { margin: 0; font-size: 1.4rem; line-height: 1.2; }
.ff-profile-teams { display: flex; flex-direction: column; gap: 10px; }
.ff-profile-team { display: flex; flex-direction: column; }
.ff-profile-team-name { font-weight: 600; }
.ff-profile-avatar-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ff-profile-avatar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ff-file-btn { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; padding: 6px 12px; border-radius: 8px;
    border: 1px solid var(--mud-palette-lines-default); font-size: .85rem; font-weight: 500; }
.ff-file-btn:hover { background: rgba(var(--mud-palette-primary-rgb), .08); }
.ff-nick-list { display: flex; flex-direction: column; gap: 6px; }
.ff-nick-row { display: flex; align-items: center; gap: 8px; }
.ff-nick-row .ff-nick-player { font-weight: 600; }
.ff-nick-row .ff-nick-arrow { opacity: .6; }
.ff-nick-row .ff-nick-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-nick-add { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; }
.ff-nick-add > * { flex: 1 1 160px; }
.ff-nick-add > button, .ff-nick-add .mud-button-root { flex: 0 0 auto; }

/* Phase 15D: Records Book + career pages. */
.ff-records-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 720px) { .ff-records-grid { grid-template-columns: 1fr 1fr; } }
.ff-record-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--mud-palette-lines-default); }
.ff-record-row:last-child { border-bottom: none; }
.ff-record-row .ff-member-chip { flex: 1 1 auto; min-width: 0; }
.ff-record-meta { font-size: .78rem; color: var(--mud-palette-text-secondary); white-space: nowrap; }
.ff-record-val { font-variant-numeric: tabular-nums; font-weight: 700; min-width: 44px; text-align: right; }
/* Phase 21D: name cell for the merged record-performance rows (player/defense game lists). */
.ff-record-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: .89rem; }
.ff-record-name .ff-record-sub { font-weight: 400; font-size: .78rem; color: var(--mud-palette-text-secondary); }
.ff-records-perf-head { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 1.05rem; font-weight: 800; }
.ff-stat-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.ff-stat-tile { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-radius: 10px;
    background: var(--mud-palette-background-grey); }
.ff-stat-tile .v { font-size: 1.4rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.ff-stat-tile .k { font-size: .74rem; color: var(--mud-palette-text-secondary); }
.ff-career-table { width: 100%; border-collapse: collapse; }
.ff-career-table th, .ff-career-table td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--mud-palette-lines-default); white-space: nowrap; }
.ff-career-table th { font-size: .78rem; color: var(--mud-palette-text-secondary); font-weight: 600; }

/* Phase 15E: rivalry page + picker, and the activity feed. */
.ff-rivalry-head { display: flex; align-items: center; justify-content: center; gap: 20px; margin: 8px 0 20px; flex-wrap: wrap; }
.ff-rivalry-side { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; }
.ff-rivalry-name { font-weight: 600; text-align: center; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-rivalry-wins { font-size: 2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; }
.ff-rivalry-vs { font-size: 1.1rem; color: var(--mud-palette-text-secondary); font-weight: 600; }
.ff-rivalry-picker { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ff-rivalry-picker > .mud-input-control { flex: 1 1 180px; min-width: 140px; }
.ff-feed { display: flex; flex-direction: column; gap: 12px; }
.ff-feed-item { display: flex; align-items: flex-start; gap: 10px; }
.ff-feed-item .ff-feed-icon { margin-top: 2px; flex: 0 0 auto; }
.ff-feed-item .ff-md { min-width: 0; }
/* Batch B (B2): a feed row that links to its week's scoreboard. */
a.ff-feed-item { color: inherit; text-decoration: none; border-radius: 6px; margin: -4px -6px; padding: 4px 6px; }
a.ff-feed-item:hover { background: rgba(var(--mud-palette-primary-rgb), .08); }

/* Batch B (B2): the shared linkified-entity affordance (names, week references). Theme-aware. */
.ff-link { color: var(--mud-palette-primary); text-decoration: none; font-weight: 600; cursor: pointer; }
.ff-link:hover { text-decoration: underline; }

/* ---- 18G: activity feed ---- */
/* Name-level links now render inline inside the entry (member â†’ career, player â†’ hub, week â†’
   scoreboard) rather than the whole row being one link, so entries wrap and each link stays tappable
   at 360px. break-word keeps a long player/team name from overflowing the narrow column. */
.ff-feed-body { overflow-wrap: break-word; }
.ff-feed-body .ff-feed-name { font-weight: 700; }

/* Phase 15F: co-manager surfaces. */
.ff-comanaged { display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: .8rem; color: var(--mud-palette-text-secondary); }
.ff-comanager-add { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; }
.ff-comanager-add > .mud-input-control { flex: 1 1 180px; min-width: 140px; }
.ff-comanager-list { display: flex; flex-direction: column; gap: 6px; }
.ff-comanager-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--mud-palette-lines-default); }
.ff-comanager-row:last-child { border-bottom: none; }

/* T-23: league-settings time-zone picker rows. Friendly label on top, raw IANA id underneath, so the
   commissioner recognizes the zone and can still see exactly what is stored. Stacked (not side by
   side) so a long id wraps instead of pushing the row wide at 360px. */
.ff-tz-option { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ff-tz-label { font-weight: 600; font-size: .9rem; }
.ff-tz-id { font-size: .75rem; color: var(--mud-palette-text-secondary); overflow-wrap: anywhere; }
.ff-tz-none { padding: 8px 12px; font-size: .85rem; color: var(--mud-palette-text-secondary); }
.ff-standing .pts { font-variant-numeric: tabular-nums; font-weight: 600; font-size: .9rem; }

/* Roster rows */
.ff-roster-row {
    display: grid; grid-template-columns: 52px 1fr auto; align-items: center; gap: 12px;
    padding: 11px 14px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-roster-row:last-child { border-bottom: 0; }
.ff-roster-row.total { font-weight: 700; }
.ff-pos {
    font-size: .66rem; font-weight: 700; letter-spacing: .04em; text-align: center;
    padding: 3px 0; border-radius: 6px; color: #fff; text-transform: uppercase;
}
.ff-pos.qb { background: #3b82f6; }
.ff-pos.rb { background: #10b981; }
.ff-pos.wr { background: #f59e0b; }
.ff-pos.flex { background: #8b5cf6; }
.ff-pos.def { background: #ef4444; }
.ff-roster-row .name { font-size: .89rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-roster-row .name.empty { color: var(--mud-palette-text-secondary); font-style: italic; }
.ff-roster-row .pt { font-variant-numeric: tabular-nums; font-weight: 600; }

/* Shared empty-state */
.ff-empty-state {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center; gap: 8px; padding: 34px 20px; color: var(--mud-palette-text-secondary);
}
.ff-empty-state .mud-icon-root { opacity: .5; }

/* Rank medals for the top three */
.ff-standing.r1 .rank { background: #d4af37; color: #231d00; }
.ff-standing.r2 .rank { background: #aab2bd; color: #1b1f24; }
.ff-standing.r3 .rank { background: #cd7f4a; color: #fff; }

/* Page toolbar (year / week selectors etc.) */
.ff-toolbar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 20px; }

/* Segmented control â€” the Draft hub's section switcher. Sticky under the page head, horizontally
   scrollable/swipeable on phones with generous tap targets. */
.ff-segtabs { display: flex; gap: 4px; padding: 4px; margin-bottom: 4px; border-radius: 12px;
    background: var(--mud-palette-background-grey); position: sticky; top: 0; z-index: 3;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.ff-segtabs::-webkit-scrollbar { display: none; }
.ff-segtab { display: inline-flex; align-items: center; gap: 6px; flex: 1 0 auto; justify-content: center;
    min-height: 44px; padding: 6px 16px; border: none; border-radius: 9px; cursor: pointer;
    background: transparent; color: var(--mud-palette-text-secondary); font-size: .9rem; font-weight: 600;
    white-space: nowrap; transition: background .15s, color .15s; }
.ff-segtab:hover { color: var(--mud-palette-text-primary); }
.ff-segtab.active { background: var(--mud-palette-surface); color: var(--mud-palette-primary);
    box-shadow: 0 1px 3px rgba(0,0,0,.18); }
.ff-segtab-ico { flex: 0 0 auto; }
.ff-segtab-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--mud-palette-success);
    animation: ff-seg-pulse 1.6s ease-in-out infinite; }
@keyframes ff-seg-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.8); } }
.ff-seg-body { margin-top: 16px; }

/* Phone: full-width equal segments, tighter padding, label under nothing (icon + short label). */
@media (max-width: 640px) {
    .ff-segtab { flex: 1 1 0; padding: 6px 8px; font-size: .82rem; gap: 4px; }
    .ff-segtab-lbl { overflow: hidden; text-overflow: ellipsis; }
}

/* Two-column panel layout that stacks on narrow screens */
.ff-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 960px) { .ff-cols { grid-template-columns: 1fr; } }

/* Draft pick rows */
.ff-pick-row {
    display: grid; grid-template-columns: 40px 44px 1fr auto; align-items: center; gap: 12px;
    padding: 10px 14px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-pick-row:last-child { border-bottom: 0; }
.ff-pick-row .pick {
    font-variant-numeric: tabular-nums; font-weight: 700; font-size: .8rem;
    color: var(--mud-palette-text-secondary); text-align: center;
}
.ff-pick-row .pick-main { min-width: 0; }
.ff-pick-row .pick-main .nm { font-weight: 600; font-size: .9rem; }
.ff-pick-row .pick-sub {
    font-size: .74rem; color: var(--mud-palette-text-secondary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ff-pick-row .pick-pts { font-variant-numeric: tabular-nums; font-weight: 600; font-size: .9rem; }
.ff-pos.inrow { display: inline-block; width: 100%; }

/* My Team reads as a capped column instead of a half-width grid that left the desktop right half empty. */
.ff-team-wrap { max-width: 720px; }

/* Board "by member" grid â€” one roster card per member, auto-fitting columns (1-up on phones). */
.ff-board-view { display: inline-flex; gap: 6px; }
.ff-board-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 16px; align-items: start; }

/* Phone tightening for the Board pick rows, the roster, and the Room's team strip. */
@media (max-width: 640px) {
    .ff-pick-row { grid-template-columns: 28px 38px 1fr auto; gap: 8px; padding: 8px 4px; }
    .ff-pick-row .pick-main .nm { font-size: .85rem; }
    /* T-33: the chips wrap onto as many lines as the roster needs instead of squeezing five truncated
       names onto one. Tighter gaps and padding so two or three still fit per line at 360px. */
    .ff-teamstrip { gap: 5px 6px; padding: 6px 8px; }
    .ff-teamstrip-slot { padding: 2px 7px 2px 3px; gap: 5px; }
    .ff-teamstrip-slot .ff-pos { min-width: 28px; padding: 2px 5px; font-size: .6rem; }
    .ff-teamstrip-name { font-size: .78rem; }
}

/* Detail-page title (position chip + name) */
.ff-detail-title { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ff-detail-title h1 { margin: 0; }
.ff-detail-title .ff-pos { width: auto; min-width: 46px; padding: 5px 12px; font-size: .78rem; }

/* Key/value rows (profile account info etc.) */
.ff-kv { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: 1px solid var(--mud-palette-lines-default); }
.ff-kv:last-child { border-bottom: 0; }
.ff-kv .k { color: var(--mud-palette-text-secondary); font-size: .85rem; }
.ff-kv .v { font-weight: 600; font-size: .9rem; }

/* ============================================================
   Live Game Day layer (CFBD /scoreboard overlay).
   ============================================================ */
@keyframes ff-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* "LIVE" badge (page head / panel heads) */
.ff-live-badge {
    display: inline-flex; align-items: center; gap: 6px;
    background: #ef4444; color: #fff; font-size: .68rem; font-weight: 700;
    letter-spacing: .09em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px;
}
.ff-live-badge .dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: ff-pulse 1.4s ease-in-out infinite; }

/* Game-day roster slot: pos chip | name + game chip | points */
.ff-gd-slot {
    display: grid; grid-template-columns: 52px 1fr auto; align-items: center; gap: 12px;
    padding: 11px 14px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-gd-slot:last-child { border-bottom: 0; }
.ff-gd-slot.total { font-weight: 700; }
.ff-gd-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ff-gd-name { font-size: .89rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-gd-name.empty { color: var(--mud-palette-text-secondary); font-style: italic; font-weight: 400; }
.ff-gd-pts { font-variant-numeric: tabular-nums; font-weight: 700; font-size: .95rem; }

/* Per-slot game chip */
.ff-gd-chip { display: inline-flex; align-items: center; gap: 6px; font-size: .72rem; font-weight: 600; color: var(--mud-palette-text-secondary); min-width: 0; }
.ff-gd-chip .score { font-variant-numeric: tabular-nums; color: var(--mud-palette-text-primary); font-weight: 700; }
.ff-gd-chip .opp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-gd-chip.live { color: #ef4444; }
.ff-gd-chip .live-dot { width: 7px; height: 7px; border-radius: 50%; background: #ef4444; animation: ff-pulse 1.4s ease-in-out infinite; flex: none; }
.ff-gd-chip .poss { color: #10b981; font-weight: 700; } /* has the ball */

/* League board rows (expandable) */
.ff-gd-board-row {
    display: grid; grid-template-columns: 34px 1fr auto; align-items: center; gap: 12px;
    padding: 10px 12px; border-radius: 9px; cursor: pointer;
}
.ff-gd-board-row:hover { background: rgba(var(--mud-palette-primary-rgb), .06); }
.ff-gd-board-row.me { background: rgba(var(--mud-palette-primary-rgb), .13); }
.ff-gd-board-row .rk {
    width: 28px; height: 28px; border-radius: 7px; display: flex; align-items: center; justify-content: center;
    font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--mud-palette-background); color: var(--mud-palette-text-secondary);
}
.ff-gd-board-row.r1 .rk { background: #d4af37; color: #231d00; }
.ff-gd-board-row.r2 .rk { background: #aab2bd; color: #1b1f24; }
.ff-gd-board-row.r3 .rk { background: #cd7f4a; color: #fff; }
.ff-gd-board-main { min-width: 0; }
.ff-gd-board-main .who { font-weight: 600; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-gd-board-main .who .metag { font-size: .68rem; font-weight: 700; color: var(--mud-palette-primary); margin-left: 6px; letter-spacing: .04em; }
.ff-gd-board-main .sub { font-size: .72rem; color: var(--mud-palette-text-secondary); margin-top: 2px; display: flex; flex-wrap: wrap; gap: 4px 10px; }
.ff-gd-board-main .sub .live { color: #ef4444; font-weight: 700; }
.ff-gd-board-row .pts { font-variant-numeric: tabular-nums; font-weight: 700; font-size: .95rem; }
.ff-gd-board-slots { padding: 2px 6px 10px 40px; }

/* Player-hub form badge (hot / cold) + panel note */
.ff-form-badge {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    padding: 3px 10px; border-radius: 999px; color: #fff;
}
.ff-form-badge.hot { background: #f97316; }
.ff-form-badge.cold { background: #3b82f6; }
.ff-form-badge .mud-icon-root { font-size: 1rem; }
.ff-panel-sub-note { font-size: .75rem; color: var(--mud-palette-text-secondary); }
.ff-splits-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--mud-palette-text-secondary); margin: 12px 0 4px; font-weight: 600; }
.ff-splits-label:first-child { margin-top: 0; }

/* Draft Board (Phase L2) */
.ff-rank-player { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.ff-rank-player:hover .nm { text-decoration: underline; }
.ff-rank-player .nm { font-weight: 600; }
.ff-rank-player .muted { font-size: .8rem; }
.ff-tier { display: inline-block; font-size: .68rem; font-weight: 700; padding: 2px 7px; border-radius: 6px; letter-spacing: .03em; }
.ff-tier.t1 { background: #d4af37; color: #231d00; }
.ff-tier.t2 { background: rgba(var(--mud-palette-primary-rgb), .18); color: var(--mud-palette-primary); }
.ff-tier.t3 { background: var(--mud-palette-background); color: var(--mud-palette-text-secondary); }
.ff-tier.t4, .ff-tier.t5 { background: var(--mud-palette-background); color: var(--mud-palette-text-secondary); opacity: .7; }

/* AI forecast (Phase L3) */
.ff-forecast { font-size: .92rem; line-height: 1.6; color: var(--mud-palette-text-primary); }
.ff-forecast strong { color: var(--mud-palette-primary); }
.ff-forecast-foot { margin-top: 12px; font-size: .72rem; color: var(--mud-palette-text-secondary); font-style: italic; }

/* Hub tiles (Phase 13C) â€” the Admin/Commissioner dashboards */
.ff-hub-tile { padding: 14px 16px; height: 100%; cursor: pointer; transition: transform .12s ease, box-shadow .12s ease; }
.ff-hub-tile:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(0, 0, 0, .25); }

/* Weekly draft-conference chips (13F) â€” wrap cleanly at 360px */
.ff-conf-chips { display: flex; flex-wrap: wrap; gap: 6px; }

/* Platform settings console rows (Phase 13D) */
.ff-setting-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; flex-wrap: wrap; }
.ff-setting-main { flex: 1 1 320px; min-width: 0; }
.ff-setting-editor { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }

/* AI league content markdown (Phase 11D) â€” the MarkdownLite output on League Buzz */
.ff-md { font-size: .95rem; line-height: 1.65; color: var(--mud-palette-text-primary); }
.ff-md p { margin: 0 0 10px; }
.ff-md p:last-child { margin-bottom: 0; }
.ff-md h5 { margin: 12px 0 6px; font-size: 1rem; font-weight: 700; color: var(--mud-palette-primary); }
.ff-md h5:first-child { margin-top: 0; }
.ff-md ol, .ff-md ul { margin: 0 0 10px; padding-left: 22px; display: flex; flex-direction: column; gap: 6px; }
.ff-md li { line-height: 1.55; }
.ff-md strong { color: var(--mud-palette-primary); }

/* Structured AI forecast (Phase L3 Part 2) */
.ff-fc-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
.ff-fc-proj { display: flex; flex-direction: column; line-height: 1.1; }
.ff-fc-proj-val { font-size: 1.7rem; font-weight: 700; color: var(--mud-palette-primary); }
.ff-fc-proj-val.sm { font-size: 1.05rem; font-weight: 600; }
.ff-fc-proj-lbl { font-size: .7rem; color: var(--mud-palette-text-secondary); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }
.ff-fc-reason { font-size: .9rem; line-height: 1.55; color: var(--mud-palette-text-primary); margin-bottom: 10px; }
.ff-fc-drivers { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.ff-fc-drivers li { font-size: .88rem; line-height: 1.5; color: var(--mud-palette-text-primary); }
.ff-fc-drivers li::marker { color: var(--mud-palette-primary); }

/* In-draft intelligence (Phase 8) */
.ff-draft-intel { border-left: 3px solid var(--mud-palette-primary); }
.ff-draft-rec { display: flex; align-items: center; gap: 14px; padding: 10px 12px; border-radius: 8px;
    background: var(--mud-palette-background-grey); flex-wrap: wrap; }
.ff-draft-rec-body { flex: 1 1 200px; min-width: 0; }
.ff-draft-rec-lbl { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--mud-palette-text-secondary); }
.ff-draft-rec-name { font-size: 1.15rem; font-weight: 700; color: var(--mud-palette-text-primary); line-height: 1.2; }
.ff-draft-rec-meta { font-size: .8rem; color: var(--mud-palette-text-secondary); margin-top: 2px; }
.ff-draft-slot { font-weight: 700; color: var(--mud-palette-primary); margin-right: 4px; }
.ff-draft-src { font-style: italic; }
.ff-draft-rec-reason { font-size: .82rem; color: var(--mud-palette-text-primary); margin-top: 4px; }
.ff-draft-board { display: flex; flex-direction: column; margin-top: 4px; }
/* Fixed grid tracks so tier / name / position / school / trend / value / action line up down
   every row, regardless of how long a school name is. name + school flex; the rest are fixed. */
.ff-draft-row { display: grid; align-items: center; gap: 8px; padding: 4px 2px;
    grid-template-columns: 30px minmax(0, 1.4fr) 46px minmax(0, 1fr) 46px 50px 84px;
    border-bottom: 1px solid var(--mud-palette-lines-default); font-size: .85rem;
    border-left: 2px solid transparent; }
.ff-draft-row:last-child { border-bottom: none; }
.ff-draft-row.sel { background: rgba(118, 120, 237, .12); border-left-color: var(--mud-palette-success); }
.ff-draft-pname { font-weight: 600; color: var(--mud-palette-text-primary); text-decoration: none;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-draft-pname:hover { color: var(--mud-palette-primary); text-decoration: underline; }
/* T-34: a row the viewer can no longer roster stays VISIBLE (the landscape is worth scanning) but its
   controls are off and the reason is spelled out under the name â€” a title tooltip alone is invisible
   on the phones members actually draft from. The name cell becomes a two-line stack for that. */
.ff-draft-nmcell { display: flex; flex-direction: column; justify-content: center; min-width: 0; line-height: 1.15; }
.ff-draft-why { font-size: .62rem; color: var(--mud-palette-text-secondary);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-draft-row.blocked .ff-draft-pname,
.ff-draft-row.blocked .ff-draft-pos,
.ff-draft-row.blocked .ff-draft-team,
.ff-draft-row.blocked .ff-draft-val { opacity: .6; }
.ff-draft-pos { font-weight: 700; color: var(--mud-palette-text-secondary); }
.ff-draft-team { color: var(--mud-palette-text-secondary);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-draft-trend { font-size: .66rem; font-weight: 700; text-transform: uppercase; text-align: center;
    padding: 1px 0; border-radius: 4px; }
.ff-draft-trend.hot { color: var(--mud-palette-error); background: rgba(244, 67, 54, .14); }
.ff-draft-trend.cold { color: var(--mud-palette-info); background: rgba(33, 150, 243, .14); }
.ff-draft-trend:empty { background: none; }
.ff-draft-val { font-weight: 700; color: var(--mud-palette-primary); text-align: right; }
.ff-draft-action { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
/* 41: the action cell can hold the quick-add (+) and, on your turn, the select control. Tighten the
   icon buttons so both fit the fixed track without widening the row. */
.ff-draft-action .mud-icon-button { padding: 4px; }
.ff-draft-recdot { color: #f2b807; font-size: .9rem; line-height: 1; }
.ff-draft-recbadge { display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 10px;
    font-size: .62rem; font-weight: 700; letter-spacing: .03em; color: #f2b807;
    background: rgba(242, 184, 7, .14); vertical-align: middle; }
.ff-draft-reset { margin-top: 4px; text-transform: none; }
.ff-trend-short { display: none; }
.ff-back-btn { text-transform: none; }

/* Draft-pool typeahead options: fixed position chip, name, then muted school/conf Â· value. */
.ff-pool-opt { display: flex; align-items: baseline; gap: 8px; width: 100%; min-width: 0; }
.ff-pool-pos { flex: 0 0 34px; font-weight: 700; font-size: .72rem; color: var(--mud-palette-text-secondary); }
.ff-pool-name { font-weight: 600; color: var(--mud-palette-text-primary);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-pool-meta { margin-left: auto; padding-left: 8px; font-size: .78rem; white-space: nowrap;
    color: var(--mud-palette-text-secondary); }

/* Autodraft opt-in row in the draft-intelligence card. */
.ff-autodraft-row { display: flex; align-items: center; flex-wrap: wrap; gap: 2px 12px;
    padding: 4px 10px; border-radius: 8px; background: rgba(118, 120, 237, .06); }

/* "Your team so far" strip in the draft Room â€” the 5 slots filling as you draft.
   T-33: each slot is a CHIP that sizes to its own content and wraps onto another line rather than
   truncating a drafted name to "Devon...". No fixed name width any more; the only cap is the strip
   itself, so a pathological surname ellipsizes instead of overflowing. */
.ff-teamstrip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding: 8px 12px;
    margin-bottom: 16px; border-radius: 10px; background: var(--mud-palette-background-grey); }
.ff-teamstrip-lbl { font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--mud-palette-text-secondary); }
.ff-teamstrip-slot { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%;
    padding: 2px 9px 2px 3px; border-radius: 999px; background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default); }
/* The position badge keeps its natural size inside the chip (the shared .ff-pos.inrow rule stretches
   it to 100% for the fixed-grid rows it was written for). */
.ff-teamstrip-slot .ff-pos { flex: 0 0 auto; width: auto; min-width: 32px; padding: 2px 6px; }
.ff-teamstrip-name { font-size: .82rem; font-weight: 600; color: var(--mud-palette-text-primary);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-teamstrip-slot.empty { background: none; border-style: dashed; }
.ff-teamstrip-slot.empty .ff-teamstrip-name { color: var(--mud-palette-text-disabled); font-weight: 400; }

/* Mobile: keep every player on ONE row â€” smaller text, tighter columns, and H/C instead of HOT/COLD.
   Name + position stay full-size priority; school truncates. */
@media (max-width: 640px) {
    .ff-draft-row {
        grid-template-columns: 24px minmax(0, 2fr) 30px minmax(0, 0.8fr) 20px 40px 64px;
        gap: 5px; font-size: .78rem; padding: 5px 2px;
    }
    /* 41: keep the two action controls on one row at phone widths. */
    .ff-draft-action { gap: 1px; }
    .ff-draft-action .mud-icon-button { padding: 2px; }
    .ff-draft-action .mud-icon-button .mud-icon-root { font-size: 1.05rem; }
    .ff-draft-trend { padding: 0; }
    .ff-trend-full { display: none; }
    .ff-trend-short { display: inline; }
    .ff-draft-row .ff-tier { padding: 1px 4px; font-size: .62rem; }
    .ff-draft-why { font-size: .58rem; }
    .ff-draft-rec { padding: 8px 10px; }
    .ff-draft-rec-name { font-size: 1.05rem; }
}
.ff-kv .muted { color: var(--mud-palette-text-secondary); font-weight: 400; font-size: .74rem; }

/* Compact data tables (player hub game log / season summary) */
.ff-table-scroll { overflow-x: auto; }
.ff-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.ff-table th, .ff-table td { padding: 8px 12px; text-align: left; white-space: nowrap; border-bottom: 1px solid var(--mud-palette-lines-default); }
.ff-table th { color: var(--mud-palette-text-secondary); font-weight: 600; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.ff-table td.num, .ff-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.ff-table td.strong { font-weight: 700; }
.ff-table tbody tr:last-child td { border-bottom: 0; }
.ff-table tbody tr:hover { background: rgba(var(--mud-palette-primary-rgb), .05); }

/* ============================================================
   Data-Viz Foundation â€” chart host.
   ============================================================ */
.ff-chart { position: relative; width: 100%; }
.ff-chart-empty {
    display: flex; align-items: center; justify-content: center; height: 100%;
    color: var(--mud-palette-text-secondary); font-size: .85rem; text-align: center; padding: 12px;
}
.ff-spark-empty { color: var(--mud-palette-text-secondary); }
.ff-table td.spark { width: 120px; padding-top: 2px; padding-bottom: 2px; }

/* League onboarding + switcher (Phase 9E1) */
.ff-league-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ff-league-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ff-league-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 16px; }
.ff-league-card { min-width: 0; }
.ff-league-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
/* .ff-league-empty removed 2026-07-31: the My Leagues empty card joined the shared NoLeagueState
   funnel (scoped styling in NoLeagueState.razor.css). */
.ff-league-switcher { margin-left: 12px; min-width: 0; max-width: min(34vw, 280px); }
.ff-league-switcher .mud-button-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-onboarding { margin-top: 24px; }
.ff-wizard-steps { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.ff-wizard-steps span { color: var(--mud-palette-text-secondary); border: 1px solid var(--mud-palette-lines-default); border-radius: 999px; padding: 5px 11px; font-size: .78rem; }
.ff-wizard-steps span.active { color: var(--mud-palette-primary); border-color: var(--mud-palette-primary); font-weight: 700; }
.ff-wizard-steps span.done { color: var(--mud-palette-success); border-color: var(--mud-palette-success); }
.ff-slot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 12px; }
.ff-review-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; }
.ff-review-grid div { display: flex; flex-direction: column; padding: 12px; border-radius: 9px; background: var(--mud-palette-background-grey); }
.ff-review-grid span { color: var(--mud-palette-text-secondary); font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.ff-wizard-actions { padding: 0 16px 16px; }
.ff-unpaid-gate { max-width: 620px; }
.ff-invite-code { letter-spacing: .14em; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.ff-commissioner-dashboard { margin-bottom: 28px; }
.ff-league-stat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.ff-league-stat-grid div { display: flex; flex-direction: column; padding: 10px; border-radius: 8px; background: var(--mud-palette-background-grey); }
.ff-league-stat-grid span { color: var(--mud-palette-text-secondary); font-size: .72rem; text-transform: uppercase; }
.ff-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.ff-league-admin-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.ff-member-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 12px; }
.ff-member-card { min-width: 0; }
.ff-member-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ff-member-ident { display: flex; align-items: center; gap: 12px; min-width: 0; } /* Phase 15B: avatar + name */
.ff-member-ident > div { min-width: 0; }
.ff-member-badges { display: flex; justify-content: flex-end; gap: 6px; flex-wrap: wrap; }
.ff-member-actions { flex-wrap: wrap; }
.ff-invite-list { display: grid; gap: 10px; }
.ff-invite-row .mud-card-content { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

@media (max-width: 600px) {
    .ff-app-title { display: none; }
    .ff-league-switcher { margin-left: 2px; max-width: 42vw; }
    .ff-onboarding { margin-top: 8px; padding-left: 0; padding-right: 0; }
    .ff-league-actions { width: 100%; }
    .ff-league-actions .mud-button-root { flex: 1; }
    .ff-form-grid, .ff-league-admin-grid { grid-template-columns: 1fr; }
    .ff-invite-row .mud-card-content { align-items: flex-start; flex-direction: column; }
}

/* ============================================================
   Phase 18A â€” shared refresh kit: attribution rows, week-vs-average
   strip, Player Detail hero total, and the MatchupChip label toggle.
   ============================================================ */

/* Attribution rows â€” the compact "stat Â· value Â· +pts" successor to the zero-tile wall.
   Single column at every width; points column is fixed-width so the +/- signs line up. */
.ff-attr-list { margin: 4px 0 8px; }
.ff-attr-row {
    display: grid; grid-template-columns: 1fr auto auto; align-items: baseline; gap: 14px;
    padding: 9px 2px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-attr-row:last-child { border-bottom: 0; }
.ff-attr-stat { color: var(--mud-palette-text-secondary); font-size: .85rem; min-width: 0; }
.ff-attr-val { font-weight: 600; font-size: .95rem; font-variant-numeric: tabular-nums; text-align: right; }
.ff-attr-pts {
    font-variant-numeric: tabular-nums; font-weight: 700; font-size: .85rem;
    min-width: 62px; text-align: right; cursor: default;
}
.ff-attr-pts.pos { color: var(--mud-palette-success); }
.ff-attr-pts.neg { color: var(--mud-palette-error); }

/* Week-vs-season-average strip â€” three cells (this week / season avg / Î”), wrapping on phones. */
.ff-wva { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0; }
.ff-wva-cell {
    flex: 1 1 92px; text-align: center; padding: 10px 12px; border-radius: 10px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default);
}
.ff-wva-cell .k {
    display: block; color: var(--mud-palette-text-secondary); font-size: .68rem;
    text-transform: uppercase; letter-spacing: .05em;
}
.ff-wva-cell .v { display: block; font-weight: 700; font-size: 1.2rem; font-variant-numeric: tabular-nums; margin-top: 4px; }
.ff-wva-cell .v.pos { color: var(--mud-palette-success); }
.ff-wva-cell .v.neg { color: var(--mud-palette-error); }
.ff-wva-note { color: var(--mud-palette-text-secondary); font-size: .82rem; margin: 12px 2px; }

/* Player Detail hero: the season total sits at the trailing edge of the title row. */
.ff-detail-total { margin-left: auto; text-align: right; line-height: 1.05; }
.ff-detail-total .n { display: block; font-size: 1.9rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.ff-detail-total .l { display: block; color: var(--mud-palette-text-secondary); font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; }

.ff-attr-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--mud-palette-text-secondary); font-weight: 600; margin: 18px 2px 2px; }

/* MatchupChip responsive label (the Â§2.3 fix): the full band word shows â‰¥600px, glyph+score below.
   Without these rules both spans rendered together â€” the documented toggle was never wired. */
.ff-matchup-short { display: none; }
@media (max-width: 599.98px) {
    .ff-matchup-full { display: none; }
    .ff-matchup-short { display: inline; }
}

/* ============================================================
   ---- 18D: scoreboard ----
   The league-race board (ScoreboardBoard.razor) across all three tabs: medal ranks + avatars +
   linkified (career) names, a gap-to-leader + signed delta-vs-last-week caption, an expandable
   lineup (Q7), a Season-tab sparkline, and a Total-Wins streak flame. Mobile-first at 360px:
   one-line rows, tap-to-expand; the sparkline appears only â‰¥600px where the row has room. Built on
   the MudBlazor palette vars so it auto-themes; medal + roster idioms mirror the ff-standing/
   ff-roster-row layers this page used to render flat.
   ============================================================ */

.ff-sb-list { display: flex; flex-direction: column; gap: 3px; }
.ff-sb-row { border-radius: 10px; }

/* Row header: a flex line so the optional sparkline slots in without breaking alignment. */
.ff-sb-main {
    width: 100%; display: flex; align-items: center; gap: 10px;
    padding: 9px 10px; border-radius: 10px;
    background: none; border: 0; text-align: left; color: inherit; font: inherit;
}
.ff-sb-main.clickable { cursor: pointer; }
.ff-sb-main.clickable:hover { background: rgba(var(--mud-palette-primary-rgb), .06); }
.ff-sb-main:focus-visible { outline: 2px solid var(--mud-palette-primary); outline-offset: -2px; }
.ff-sb-row.me .ff-sb-main { background: rgba(var(--mud-palette-primary-rgb), .13); }

.ff-sb-rank {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 7px;
    display: flex; align-items: center; justify-content: center;
    font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--mud-palette-background); color: var(--mud-palette-text-secondary);
}
/* Medals for the top three (same palette as the ff-standing medals). */
.ff-sb-rank.r1 { background: #d4af37; color: #231d00; }
.ff-sb-rank.r2 { background: #aab2bd; color: #1b1f24; }
.ff-sb-rank.r3 { background: #cd7f4a; color: #fff; }

.ff-sb-who { flex: 1 1 auto; display: flex; align-items: center; gap: 9px; min-width: 0; }
.ff-sb-av { width: 30px; height: 30px; min-width: 30px; font-size: .7rem; }
.ff-sb-whotext { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ff-sb-nameline { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ff-sb-namewrap { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-sb-name { font-weight: 600; font-size: .9rem; }
/* The name links to a career page but stays visually quiet in a dense list (Batch B convention). */
.ff-sb-name.ff-link { color: inherit; font-weight: 600; }
.ff-sb-name.ff-link:hover { color: var(--mud-palette-primary); }
.ff-sb-you { font-size: .62rem; font-weight: 700; color: var(--mud-palette-primary); letter-spacing: .04em; flex: 0 0 auto; }
.ff-sb-flame { font-size: .72rem; font-weight: 700; flex: 0 0 auto; white-space: nowrap; }

.ff-sb-meta { display: flex; flex-wrap: wrap; gap: 2px 10px; font-size: .72rem; color: var(--mud-palette-text-secondary); }
.ff-sb-delta { font-variant-numeric: tabular-nums; }
.ff-sb-delta.pos { color: var(--mud-palette-success); }
.ff-sb-delta.neg { color: var(--mud-palette-error); }

.ff-sb-spark { display: none; flex: 0 0 66px; width: 66px; }
.ff-sb-val { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 700; font-size: .95rem; text-align: right; }
.ff-sb-chev { flex: 0 0 auto; display: flex; justify-content: center; width: 22px; color: var(--mud-palette-text-secondary); transition: transform .15s ease; }
.ff-sb-chev.open { transform: rotate(180deg); }
.ff-sb-chev-spacer { flex: 0 0 auto; width: 22px; }

/* Expanded lineup â€” indented under the row, reusing the ff-roster-row idiom. */
.ff-sb-panel { padding: 2px 8px 10px 44px; }
.ff-sb-lineup-loading { display: flex; justify-content: center; padding: 12px; }
.ff-sb-lineup-empty { padding: 10px 2px; color: var(--mud-palette-text-secondary); font-size: .85rem; }

/* The sparkline only earns its place where the row has width (matrix: rows + sparkline at â‰¥600px). */
@media (min-width: 600px) { .ff-sb-spark { display: block; } }
/* ---- 18E: leagues pages ---- */
/* Richer My Leagues cards + a hero on the League overview. Everything here is composed from data
   already on the read DTOs (member fill, current-league identities, the current-week lock) â€” no new
   server aggregates (Â§11 Q2). One self-contained block per CSS discipline. */

/* Membership card: stacked, evenly-spaced content so the meter/avatars/draft line breathe. */
.ff-league-card-rich .mud-card-content { display: flex; flex-direction: column; gap: 12px; }
.ff-league-card-current { border-color: var(--mud-palette-primary); }
.ff-lc-title { min-width: 0; }
.ff-lc-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: .8rem; margin-top: 2px; }
.ff-lc-sep { opacity: .55; }
.ff-lc-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Members-fill meter â€” MemberCount / MaxMembers straight off the DTO. */
.ff-lc-fill { display: flex; flex-direction: column; gap: 6px; }
.ff-lc-fill-head { display: flex; align-items: baseline; justify-content: space-between; }
.ff-lc-fill-head span { text-transform: uppercase; letter-spacing: .05em; font-size: .68rem; }
.ff-lc-fill-head strong { font-size: .82rem; font-variant-numeric: tabular-nums; }
.ff-lc-bar { height: 6px; border-radius: 999px; background: var(--mud-palette-lines-default); overflow: hidden; }
.ff-lc-bar > span { display: block; height: 100%; border-radius: inherit; background: var(--mud-palette-primary); transition: width .3s ease; }
.ff-lc-bar-lg { height: 8px; }

/* Overlapping avatar preview (current league only â€” identities are current-league-scoped). */
.ff-lc-avatars { display: flex; align-items: center; }
.ff-lc-av { margin-left: -6px; border: 2px solid var(--mud-palette-surface); }
.ff-lc-av:first-child { margin-left: 0; }
.ff-lc-av-more { margin-left: 8px; font-size: .75rem; color: var(--mud-palette-text-secondary); font-weight: 600; }

.ff-lc-draft { display: flex; align-items: center; gap: 6px; margin-top: auto; }
.ff-lc-draft-icon { font-size: 1rem !important; }

/* Overview hero â€” a primary gradient banner replacing the plain page head. */
.ff-league-hero {
    border-radius: 16px; padding: 22px 24px; margin-bottom: 22px; color: #fff;
    background: linear-gradient(135deg, var(--mud-palette-primary), var(--mud-palette-primary-darken, #C74A00));
}
.ff-league-hero-body { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ff-league-hero-id { min-width: 0; }
.ff-league-hero-name { color: #fff; font-weight: 700; letter-spacing: -.01em; }
.ff-league-hero-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 6px; color: rgba(255, 255, 255, .85); font-size: .9rem; }
.ff-league-hero-avatars { display: flex; align-items: center; margin-top: 14px; }
.ff-league-hero .ff-lc-av { border-color: rgba(255, 255, 255, .55); }
.ff-league-hero .ff-lc-av-more { color: rgba(255, 255, 255, .9); }
.ff-league-hero .ff-league-actions .mud-button-outlined { color: #fff; border-color: rgba(255, 255, 255, .55); }
/* White CTA reads clearly on the indigo gradient (primary-on-primary would vanish). */
.ff-league-hero .ff-league-actions .mud-button-filled { background: #fff; color: var(--mud-palette-primary); }

.ff-league-card-accent { border-color: var(--mud-palette-primary); }

/* Invite code + copy button on one line, wrapping on phones. */
.ff-invite-row2 { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 4px 0 2px; }

@media (max-width: 599.98px) {
    .ff-league-hero { padding: 18px; }
    .ff-league-hero-body { flex-direction: column; }
    .ff-league-hero .ff-league-actions { width: 100%; }
    .ff-league-hero .ff-league-actions .mud-button-root { flex: 1; }
}

/* ---- 18B: draft hub segments ---- */
/* My Team â€” the "your week" metrics panel below the lineup card (rank + delta, best/worst slot, season
   sparkline). All theme-var driven; wraps to one column on phones. */
.ff-mt-week { max-width: 720px; margin-top: 16px; }
.ff-mt-tiles { display: flex; flex-wrap: wrap; gap: 10px; }
.ff-mt-tile {
    flex: 1 1 120px; padding: 10px 12px; border-radius: 10px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default);
}
.ff-mt-tile .k {
    display: block; color: var(--mud-palette-text-secondary); font-size: .68rem;
    text-transform: uppercase; letter-spacing: .05em;
}
.ff-mt-tile .v { display: block; font-weight: 800; font-size: 1.35rem; font-variant-numeric: tabular-nums; margin-top: 2px; }
.ff-mt-tile .v .of { font-size: .8rem; font-weight: 600; color: var(--mud-palette-text-secondary); }
.ff-mt-tile .v.pos { color: var(--mud-palette-success); }
.ff-mt-tile .v.neg { color: var(--mud-palette-error); }
.ff-mt-tile .v.even { color: var(--mud-palette-text-secondary); }
.ff-mt-callouts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.ff-mt-callout {
    display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px;
    font-size: .8rem; border: 1px solid var(--mud-palette-lines-default);
}
.ff-mt-callout .ff-pos { width: auto; min-width: 30px; padding: 2px 7px; font-size: .58rem; }
.ff-mt-callout b { font-variant-numeric: tabular-nums; }
.ff-mt-callout.best { background: rgba(76, 175, 80, .10); }
.ff-mt-callout.worst { background: rgba(244, 67, 54, .08); }
.ff-mt-spark { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
.ff-mt-spark .k {
    flex: 0 0 auto; color: var(--mud-palette-text-secondary); font-size: .68rem;
    text-transform: uppercase; letter-spacing: .05em;
}
.ff-mt-spark-wrap { flex: 1 1 auto; min-width: 0; }

/* Queue â€” card rows: [drag] [pos pill] [name + meta (+ matchup chip)] [up / down / delete]. Queue
   behavior is unchanged; this block only restyles the row (18B is visual-only for the queue). */
.ff-q-list { display: flex; flex-direction: column; gap: 8px; }
.ff-q-row {
    display: grid; grid-template-columns: 22px 44px 1fr auto; align-items: center; gap: 10px;
    padding: 8px 12px; border-radius: 10px; background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
}
.ff-q-drag { flex: 0 0 auto; cursor: grab; }
.ff-q-row > .ff-pos { min-width: 44px; }
.ff-q-main { min-width: 0; }
.ff-q-name { font-weight: 600; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ff-q-meta {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: .74rem; color: var(--mud-palette-text-secondary);
}
.ff-q-meta > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ff-q-actions { display: inline-flex; align-items: center; flex: 0 0 auto; }
@media (max-width: 400px) {
    .ff-q-row { grid-template-columns: 18px 38px 1fr auto; gap: 6px; padding: 8px 8px; }
    .ff-mt-tile { flex-basis: 100px; }
}

/* ---- 18C: game day ----
   The Game Day facelift (Phase 18.4). Layers over the existing .ff-gd-* live layer (Â§2.1): a league
   mini-ticker, a rich pre-kickoff / off-season hero, rank-movement chips (Â§11 Q5), avatars on the
   board rows, and a projected-vs-actual pace strip + per-slot "projected remaining" (research-on
   only, Â§3.2/Â§3.4). All theme-var-driven; mobile-first (single column, chips wrap) at 360px. */

/* League mini-ticker â€” a compact strip of live/final/upcoming counts + the current leader. */
.ff-gd-ticker {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
    margin: 0 0 18px; padding: 11px 14px; border-radius: 12px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default);
}
.ff-gd-ticker .tk {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: .95rem; font-weight: 700; font-variant-numeric: tabular-nums;
}
.ff-gd-ticker .tk em { font-style: normal; font-weight: 500; font-size: .78rem; color: var(--mud-palette-text-secondary); }
.ff-gd-ticker .tk .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--mud-palette-text-disabled); }
.ff-gd-ticker .tk.live.on { color: #ef4444; }
.ff-gd-ticker .tk.live.on .dot { background: #ef4444; animation: ff-pulse 1.4s ease-in-out infinite; }
.ff-gd-ticker .tk.lead { margin-left: auto; font-weight: 500; font-size: .82rem; color: var(--mud-palette-text-secondary); }
.ff-gd-ticker .tk.lead strong { color: var(--mud-palette-text-primary); }
.ff-gd-ticker .tk.lead .mud-icon-root { font-size: 1.05rem; color: #d4af37; }
@media (max-width: 599.98px) {
    .ff-gd-ticker .tk.lead { margin-left: 0; flex-basis: 100%; }
}

/* Pre-kickoff / off-season hero â€” the first-class case (most visits are non-gameday). */
.ff-gd-hero {
    display: flex; gap: 18px; align-items: flex-start;
    padding: 22px; border-radius: 16px; margin-bottom: 20px;
    background: linear-gradient(135deg,
        rgba(var(--mud-palette-primary-rgb), .12), rgba(var(--mud-palette-primary-rgb), .03));
    border: 1px solid rgba(var(--mud-palette-primary-rgb), .22);
}
.ff-gd-hero-badge {
    flex: none; width: 56px; height: 56px; border-radius: 14px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(var(--mud-palette-primary-rgb), .16); color: var(--mud-palette-primary);
}
.ff-gd-hero-badge .mud-icon-root { font-size: 2rem; }
.ff-gd-hero.prekick .ff-gd-hero-badge { animation: ff-pulse 2s ease-in-out infinite; }
.ff-gd-hero-body { min-width: 0; }
.ff-gd-hero-body h2 { margin: 2px 0 6px; font-size: 1.25rem; font-weight: 800; }
.ff-gd-hero-body p { margin: 0; color: var(--mud-palette-text-secondary); font-size: .9rem; line-height: 1.5; }
.ff-gd-hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 14px; }
.ff-gd-hero-hint { color: var(--mud-palette-text-secondary); font-size: .82rem; }
.ff-gd-hero-proj { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.ff-gd-hero-proj span {
    display: flex; flex-direction: column; gap: 2px; padding: 8px 16px; border-radius: 10px;
    background: var(--mud-palette-surface); border: 1px solid var(--mud-palette-lines-default);
}
.ff-gd-hero-proj em { font-style: normal; font-size: .66rem; text-transform: uppercase; letter-spacing: .05em; color: var(--mud-palette-text-secondary); }
.ff-gd-hero-proj b { font-size: 1.3rem; font-weight: 800; font-variant-numeric: tabular-nums; }
@media (max-width: 599.98px) {
    .ff-gd-hero { flex-direction: column; gap: 12px; padding: 18px; }
    .ff-gd-hero-badge { width: 48px; height: 48px; }
}

/* Rank-movement chip (My Team head + board rows) â€” up/down/flat vs the last completed week. */
.ff-gd-rankwrap { display: inline-flex; align-items: center; gap: 6px; }
.ff-rankmove {
    display: inline-flex; align-items: center; font-size: .7rem; font-weight: 700;
    font-variant-numeric: tabular-nums; padding: 1px 6px; border-radius: 999px; line-height: 1.4;
}
.ff-rankmove.up { color: #10b981; background: rgba(16, 185, 129, .14); }
.ff-rankmove.down { color: #ef4444; background: rgba(239, 68, 68, .14); }
.ff-rankmove.flat { color: var(--mud-palette-text-secondary); background: var(--mud-palette-background-grey); }

/* My Team projected-vs-actual pace strip (research on): now / projected / on the field. */
.ff-gd-pace {
    display: flex; gap: 10px; margin: 4px 14px 10px; padding-top: 12px;
}
.ff-gd-pace .cell {
    flex: 1 1 0; text-align: center; padding: 8px 6px; border-radius: 10px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default);
}
.ff-gd-pace .cell.hi { background: rgba(var(--mud-palette-primary-rgb), .1); border-color: rgba(var(--mud-palette-primary-rgb), .28); }
.ff-gd-pace .cell .k { display: block; font-size: .64rem; text-transform: uppercase; letter-spacing: .05em; color: var(--mud-palette-text-secondary); }
.ff-gd-pace .cell .v { display: block; font-weight: 800; font-size: 1.15rem; font-variant-numeric: tabular-nums; margin-top: 3px; }
.ff-gd-pace .cell.hi .v { color: var(--mud-palette-primary); }

/* Per-slot points column: banked total, with projected-remaining beneath it when present. */
.ff-gd-ptcol { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.ff-gd-proj { font-size: .66rem; font-weight: 600; color: var(--mud-palette-primary); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Board rows gain an avatar column (rk | avatar | who/sub | pts). */
.ff-gd-board-row { grid-template-columns: 30px auto 1fr auto; }
.ff-gd-board-av { flex: none; }
.ff-gd-board-main .sub .proj { color: var(--mud-palette-primary); font-weight: 600; }
/* ---- end 18C ---- */

/* ---- 18H: league buzz ---- */
/* Member League Buzz is a week-at-a-time archive: a bounded week picker, then recap + power ranking
   side-by-side on desktop (.ff-cols already stacks at <=960px), with the awards card beneath. */
.ff-buzz { max-width: 980px; }

.ff-buzz-nav {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px;
}
.ff-buzz-count { margin-left: 4px; }

.ff-buzz-head { margin: 4px 0 12px; font-weight: 700; }

.ff-buzz-cols { margin-bottom: 16px; }
.ff-buzz-awards { max-width: 640px; }

.ff-buzz-card { height: 100%; display: flex; flex-direction: column; }
.ff-buzz-missing { padding: 8px 0; font-style: italic; }

.ff-buzz-loading { display: flex; justify-content: center; padding: 32px 0; }
/* ---- end 18H ---- */

/* ---- 18J: research surfaces ----
   Breakouts (risers + decliners side-by-side on desktop, stacked at <md) and Pool changes (an
   on-page pool-scope banner + one-line per-section explainers). Reuses the global .ff-cols grid
   (2-col â‰¥960px, single column below); the additions here are the 18J-specific hooks. Mobile-first,
   all theme-var-driven; the banner wraps and the columns stack at 360px. */

/* Breakouts two-column board â€” the panels stretch to equal height so the two sides line up. */
.ff-breakouts-cols { margin-bottom: 12px; }
.ff-breakouts-cols > .ff-panel { height: 100%; display: flex; flex-direction: column; }
.ff-breakouts-cols > .ff-panel > .ff-panel-body { flex: 1 1 auto; }

/* Pool-changes scope banner â€” the week's draftable pool, stated on-page. */
.ff-scope-banner {
    display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px;
    margin: 0 0 16px; padding: 11px 14px; border-radius: 12px;
    background: rgba(var(--mud-palette-primary-rgb), .09);
    border: 1px solid rgba(var(--mud-palette-primary-rgb), .22);
    font-size: .9rem; line-height: 1.4;
}
.ff-scope-banner .mud-icon-root { color: var(--mud-palette-primary); font-size: 1.15rem; flex: none; }
.ff-scope-banner .lbl { color: var(--mud-palette-text-secondary); }
.ff-scope-banner strong { font-weight: 700; }

/* One-line section explainer â€” sits between a panel head and its list. */
.ff-section-explainer {
    padding: 8px 16px; font-size: .8rem; line-height: 1.45;
    color: var(--mud-palette-text-secondary);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}
/* ---- end 18J ---- */

/* ---- 18K: draft board ---- */
/* The Draft Board overhaul (Phase 18.12): equal-height Top-N / movers panels, stronger
   heating-up / cooling-off styling, and a virtualized, filterable, sortable rankings table.
   Client-side only over the already-fetched board (0 new endpoints, Â§11 Q11). Mobile-first:
   filters stack at 360px and the table keeps a horizontal scroll with a sticky first column. */

/* Equal-height top panels â€” the shorter of the two stretches to the row's tallest (MudGrid items
   are align-items:stretch), so the chart and movers panels always match on desktop. */
.ff-18k-eqpanel { height: 100%; display: flex; flex-direction: column; }
.ff-18k-eqpanel > .ff-panel-body { flex: 1 1 auto; min-height: 0; }
.ff-18k-eqpanel .ff-chart { min-height: 320px; }

/* Risers & fallers â€” clear color-blocked headers separate the two sections from their rows. */
.ff-18k-movers { display: flex; flex-direction: column; gap: 14px; }
.ff-18k-mv { border: 1px solid var(--mud-palette-lines-default); border-radius: 12px; overflow: hidden; }
.ff-18k-mv-head {
    display: flex; align-items: center; gap: 7px;
    padding: 8px 12px; font-size: .74rem; font-weight: 800;
    text-transform: uppercase; letter-spacing: .05em;
}
.ff-18k-mv-head .mud-icon-root { font-size: 1.05rem; }
.ff-18k-mv.up .ff-18k-mv-head { color: #0f9d6b; background: rgba(16, 185, 129, .14); border-bottom: 1px solid rgba(16, 185, 129, .28); }
.ff-18k-mv.down .ff-18k-mv-head { color: #d64545; background: rgba(239, 68, 68, .13); border-bottom: 1px solid rgba(239, 68, 68, .26); }
.ff-18k-mv-rows { padding: 2px 12px; }
.ff-18k-mv-row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 8px 0; border-bottom: 1px solid var(--mud-palette-lines-default); cursor: pointer;
}
.ff-18k-mv-row:last-child { border-bottom: 0; }
.ff-18k-mv-row:hover .who { text-decoration: underline; }
.ff-18k-mv-row .who { font-size: .86rem; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-18k-mv-row .who .muted { color: var(--mud-palette-text-secondary); font-weight: 400; font-size: .76rem; }
.ff-18k-mv-row .delta { font-weight: 700; font-size: .9rem; font-variant-numeric: tabular-nums; flex: none; }
.ff-18k-mv-row .delta.pos { color: var(--mud-palette-success); }
.ff-18k-mv-row .delta.neg { color: var(--mud-palette-error); }
.ff-18k-mv-empty { padding: 8px 0; color: var(--mud-palette-text-secondary); font-size: .8rem; font-style: italic; }

/* Filter toolbar â€” a row of controls that wraps, then stacks full-width on small screens. */
.ff-18k-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 6px 6px 12px; }
.ff-18k-filters .ff-18k-f-search { flex: 2 1 220px; min-width: 160px; }
.ff-18k-filters .ff-18k-f-conf { flex: 1 1 170px; min-width: 150px; }
.ff-18k-filters .ff-18k-f-min { flex: 0 1 120px; min-width: 100px; }
.ff-18k-filters .ff-18k-f-reset { flex: none; text-transform: none; margin-left: auto; }

/* Bounded, scrollable table body so virtualization keeps the DOM small; header sticks on scroll. */
.ff-18k-tablewrap { max-height: 68vh; overflow: auto; border-top: 1px solid var(--mud-palette-lines-default); }
.ff-18k-table thead th {
    position: sticky; top: 0; z-index: 2;
    background: var(--mud-palette-surface);
    box-shadow: inset 0 -1px 0 var(--mud-palette-lines-default);
}
.ff-18k-table th.sortable { cursor: pointer; user-select: none; }
.ff-18k-table th.sortable:hover { color: var(--mud-palette-text-primary); }
.ff-18k-table th.sortable.active { color: var(--mud-palette-primary); }
.ff-18k-table .ff-18k-ar { display: inline-block; width: 1em; margin-left: 2px; font-size: .7em; }

.ff-18k-noresults {
    display: flex; align-items: center; gap: 8px; justify-content: center;
    padding: 28px 12px; color: var(--mud-palette-text-secondary); font-size: .9rem;
}

/* 360px: table stays a clean horizontal scroll with a sticky first column so the name never
   scrolls out of view; filters stack full-width. */
@media (max-width: 599.98px) {
    .ff-18k-filters { gap: 8px; }
    .ff-18k-filters .ff-18k-f-search,
    .ff-18k-filters .ff-18k-f-conf,
    .ff-18k-filters .ff-18k-f-min { flex: 1 1 100%; min-width: 0; }
    .ff-18k-filters .ff-18k-f-reset { margin-left: 0; align-self: flex-end; }
    .ff-18k-table thead th:first-child,
    .ff-18k-table tbody td:first-child {
        position: sticky; left: 0; z-index: 1; background: var(--mud-palette-surface);
    }
    .ff-18k-table thead th:first-child { z-index: 3; }
}
/* ---- end 18K ---- */

/* ---- 18F: member profile ---- */
/* Career teaser tiles â€” reuse the ff-stat-tile look but pack tighter (auto-fit, smaller min) so the
   strip is 2-up at 360px and 4-up on desktop without a wall of whitespace. */
.ff-profile-teaser {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px;
}
.ff-profile-teaser .ff-stat-tile { padding: 10px 12px; }

/* A small "full career â†’" affordance on the teaser panel head (head is already space-between). */
.ff-profile-career-link { font-size: .82rem; font-weight: 600; color: var(--mud-palette-primary); white-space: nowrap; }
.ff-profile-career-link:hover { text-decoration: underline; }

/* Shared-league chips + badge chips share a wrapping flex row. */
.ff-profile-chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* A shared-league chip: icon + team name + muted league name, wraps cleanly at 360px. */
.ff-profile-chip {
    display: inline-flex; align-items: center; gap: 8px; max-width: 100%;
    padding: 6px 12px; border-radius: 999px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default);
}
.ff-profile-chip .mud-icon-root { color: var(--mud-palette-primary); flex: none; }
.ff-profile-chip-main { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ff-profile-chip-sub { font-size: .8rem; color: var(--mud-palette-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* A badge chip: a real, career-derived accolade in the primary tint (never a fake placeholder). */
.ff-profile-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px; border-radius: 999px; font-weight: 600; font-size: .85rem;
    color: var(--mud-palette-primary);
    background: rgba(var(--mud-palette-primary-rgb), .1);
    border: 1px solid rgba(var(--mud-palette-primary-rgb), .28);
}
.ff-profile-badge .mud-icon-root { color: var(--mud-palette-primary); flex: none; }
/* A badge that links (the career page's head-to-head edges) reads exactly like its static siblings. */
a.ff-profile-badge { text-decoration: none; }
a.ff-profile-badge:hover { border-color: rgba(var(--mud-palette-primary-rgb), .6); }

/* The career page's share menu: pinned to the end of the hero row, brand-tinted trigger. Colored
   here rather than through a component parameter so the hero owns its own look. */
.ff-share-menu { margin-left: auto; flex: none; }
.ff-share-menu .mud-icon-button { color: var(--mud-palette-primary); }
/* ---- end 18F ---- */

/* ---- 18I: records first-season ----
   The Records Book first-season experience (Phase 18.10): a warm empty state for a league with no
   scored weeks, section headers separating "this season" from "all-time", and the head-to-head
   picker's disabled/explained state when fewer than two members have scored. Mobile-first â€” the
   welcome card and H2H explainer stack at 360px; theme-var driven. */

/* Section header above a records grid (only shown when both this-season and all-time blocks render). */
.ff-records-section + .ff-records-section { margin-top: 26px; }
.ff-records-section-head {
    display: flex; align-items: center; gap: 8px;
    margin: 0 0 12px; font-size: 1.05rem; font-weight: 800;
}

/* First-season welcome â€” replaces the bare "no records yet" line for a zero-scored-week league. */
.ff-first-season {
    display: flex; gap: 18px; align-items: flex-start;
    padding: 22px; border-radius: 16px; margin: 4px 0;
    background: linear-gradient(135deg,
        rgba(var(--mud-palette-primary-rgb), .12), rgba(var(--mud-palette-primary-rgb), .03));
    border: 1px solid rgba(var(--mud-palette-primary-rgb), .22);
}
.ff-first-season-badge {
    flex: none; width: 56px; height: 56px; border-radius: 14px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(var(--mud-palette-primary-rgb), .16); color: var(--mud-palette-primary);
}
.ff-first-season-badge .mud-icon-root { font-size: 2rem; }
.ff-first-season-body { min-width: 0; }
.ff-first-season-body h2 { margin: 2px 0 6px; font-size: 1.25rem; font-weight: 800; }
.ff-first-season-body p { margin: 0; color: var(--mud-palette-text-secondary); font-size: .9rem; line-height: 1.5; }
.ff-first-season-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 14px; }
.ff-first-season-hint { color: var(--mud-palette-text-secondary); font-size: .82rem; }
@media (max-width: 599.98px) {
    .ff-first-season { flex-direction: column; gap: 12px; padding: 18px; }
    .ff-first-season-badge { width: 48px; height: 48px; }
}

/* Head-to-head picker disabled/explained state (fewer than two members with a scored week). */
.ff-h2h-empty { display: flex; gap: 14px; align-items: flex-start; }
.ff-h2h-empty > .mud-icon-root { flex: none; margin-top: 2px; }
.ff-h2h-empty-ttl { font-weight: 700; margin-bottom: 2px; }
/* ---- end 18I ---- */

/* ---- 21A: analytics fallback ---- */
/* Per-board chip shown when a scouting board falls back to an older cached year (offseason, the
   selected year hasn't published yet). Sits above the board body; wraps its note on narrow phones
   so 360px never overflows. */
.ff-fallback-chip { margin: 4px 8px 8px; }
.ff-fallback-chip .mud-chip { height: auto; max-width: 100%; white-space: normal; }
.ff-fallback-chip .mud-chip .mud-chip-content { white-space: normal; line-height: 1.3; padding-top: 3px; padding-bottom: 3px; }

/* ---- 21G/21H: new scouting boards (wepa value + projection accuracy) ---- */
/* The one-line explainer captions above each new board: muted, tight, and full-width so they wrap
   cleanly at 360px instead of crowding the filters. */
.ff-wepa-explainer,
.ff-projacc-explainer {
    font-size: .78rem;
    line-height: 1.35;
    color: var(--mud-palette-text-secondary);
}

/* ---- C1: tabs-native + scroll-top + chart host ---- */

/* #37: native touch scroll for MudTabs strips on phones (no prev/next chevrons).
   Opt-in via Class="ff-tabs-native" on a MudTabs. MudBlazor clips the strip with
   .mud-tabs-tabbar-content { overflow: hidden } and scrolls it by JS-translating
   .mud-tabs-tabbar-wrapper through the (now hidden) arrows; hiding the arrows alone
   would strand the overflow, so we also expose native overflow scroll and neutralize
   the transform. The active-tab slider uses left, not transform, so it stays aligned. */
@media (max-width: 599.98px) {
    .ff-tabs-native .mud-tabs-scroll-button { display: none; }
    .ff-tabs-native .mud-tabs-tabbar-content {
        overflow-x: auto; overflow-y: hidden;
        -webkit-overflow-scrolling: touch; scrollbar-width: none; /* Firefox */
    }
    .ff-tabs-native .mud-tabs-tabbar-content::-webkit-scrollbar { display: none; }
    .ff-tabs-native .mud-tabs-tabbar-wrapper { transform: none !important; }
}

/* #40: the floating scroll-to-top FAB (ScrollToTopButton.razor). FF has no competing
   FAB, so it stays bottom-right at every width; on phones it lifts a little to clear
   the iOS Safari accessory strip. */
.ff-scroll-top {
    position: fixed; right: 1.25rem; bottom: 1.5rem;
    z-index: 1200; box-shadow: 0 4px 12px rgba(0, 0, 0, .25);
}
@media (max-width: 599.98px) {
    .ff-scroll-top { bottom: 1rem; }
}

/* #50: chart host reset. ECharts injects a wrapper div and a <canvas>; without a
   block/line-height reset the canvas carries an inline baseline gap (a few-pixel
   overflow) that trips an ancestor's overflow:auto into a short internal scrollbar and
   breaks equal-height panels. Clip any residual sub-pixel bleed too. Safe with ECharts
   tooltips because the interop sets confine:true, keeping tooltip DOM inside the box.
   Scoped off .ff-chart-empty (the empty state AND the error overlay both carry that
   class) so their centered flex text keeps a normal line-height and layout. */
.ff-chart { overflow: hidden; }
.ff-chart > div:not(.ff-chart-empty) { line-height: 0; }
.ff-chart > div:not(.ff-chart-empty), .ff-chart canvas { display: block; }

/* ---- 22A: stats tabs ---- */

/* #42 Â§3.3: on the /stats tabs a chosen tab must show its boards expanded at every width.
   AnalyticsPanel (the shell behind every RankedStatCard) starts collapsed on phones â€” its body div
   carries `d-none d-sm-block` and a `d-inline-flex d-sm-none` chevron toggles it. Once the boards are
   split across five tabs (one to three per tab) that per-card collapse is redundant friction, so within
   the stats tab body we force the body visible and hide the now-dead chevron. Scoped to .ff-stats-body
   so no other AnalyticsPanel caller is affected (the shared InitiallyOpen param is 22B's job). */
.ff-stats-body .mud-card > .d-none.d-sm-block { display: block !important; }
.ff-stats-body .mud-card-header .d-inline-flex.d-sm-none { display: none !important; }

/* ---- 22F: point types ---- */
/* The filter row above the editable list: search grows, "Active only" sits to its right. */
.ff-pt-filters { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 12px; }
.ff-pt-search { flex: 1 1 240px; min-width: 200px; }

/* The value cell pairs the numeric field with an unsaved-change dot. */
.ff-pt-valcell { display: inline-flex; align-items: center; gap: 8px; }
.ff-pt-dirty { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
    background: var(--mud-palette-warning); }

/* Sticky bottom save bar: full-width fixed on phones, inline-right on wider screens. It only
   asserts itself (border + shadow) when there are unsaved changes. */
.ff-pt-savebar { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    position: sticky; bottom: 0; z-index: 4; margin-top: 16px; padding: 10px 14px; border-radius: 12px;
    background: var(--mud-palette-surface); border: 1px solid transparent; transition: border-color .15s, box-shadow .15s; }
.ff-pt-savebar.dirty { border-color: var(--mud-palette-lines-default);
    box-shadow: 0 -2px 10px rgba(0, 0, 0, .16); }
.ff-pt-savecount { font-size: .85rem; color: var(--mud-palette-text-secondary); font-weight: 600; }
.ff-pt-saveactions { display: flex; gap: 8px; align-items: center; }

@media (max-width: 599.98px) {
    .ff-pt-savebar { position: fixed; left: 0; right: 0; bottom: 0; margin: 0; border-radius: 0;
        border-left: 0; border-right: 0; }
    .ff-pt-savebar.dirty { border-color: var(--mud-palette-lines-default); }
}

/* ---- 22E: draft order ---- */
/* Inline add-member row (no expansion panel) — select grows, button hugs. */
.ff-do-add { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px; }
.ff-do-add-select { flex: 1 1 auto; max-width: 340px; }
/* Reorderable pick list: drag handle, big pick number, avatar+name, row actions. */
.ff-do-list { display: flex; flex-direction: column; }
.ff-do-row {
    display: grid; grid-template-columns: 24px 30px 1fr auto; align-items: center; gap: 10px;
    padding: 8px 10px; border-bottom: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
}
.ff-do-row:last-child { border-bottom: 0; }
.ff-do-row.on-clock { background: var(--mud-palette-success-hover); border-radius: 8px; }
.ff-do-drag { cursor: grab; }
.ff-do-pick {
    font-variant-numeric: tabular-nums; font-weight: 700; font-size: .9rem; text-align: center;
    color: var(--mud-palette-text-secondary);
}
.ff-do-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ff-do-name { font-weight: 600; font-size: .92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-do-actions { display: inline-flex; align-items: center; flex: 0 0 auto; }
/* T-29: the start/status panel. Status text on the left, the one action on the right; both stack and
   the button goes full width at 360px so it stays a one-thumb tap. */
.ff-do-start {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 10px; padding: 12px 14px;
}
.ff-do-start-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ff-do-clock-mark { margin-right: 6px; }
@media (max-width: 640px) {
    .ff-do-start { flex-direction: column; align-items: stretch; }
}
/* Sticky action bar so Randomize/Save stay reachable one-handed on long lists. */
.ff-do-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px;
    padding: 10px 4px; position: sticky; bottom: 0; z-index: 3;
    background: var(--mud-palette-surface); border-top: 1px solid var(--mud-palette-lines-default);
}
@media (max-width: 640px) {
    .ff-do-row { grid-template-columns: 20px 26px 1fr auto; gap: 6px; padding: 8px 4px; }
    .ff-do-add-select { max-width: none; }
}

/* ---- 23A: ai working indicator ---- */

/* ============================================================
   AI working indicator (Phase 23) — the "it's doing cool work"
   state. Purple sparkle + orbit + text sheen; status text is
   rotated from C#. Motion is CSS-only and yields to reduced-motion.
   ============================================================ */
.ff-ai-working { display: inline-flex; align-items: center; gap: 10px; }
/* Punch 2026-07-25: the Block variant is full-width flex so the stack CENTERS in its host card
   instead of hugging the left edge (the root's inline-flex shrinks to content width otherwise). */
.ff-ai-working.block { display: flex; width: 100%; flex-direction: column; text-align: center; gap: 12px; padding: 20px 16px; }
.ff-ai-text { display: inline-flex; flex-direction: column; min-width: 0; }
.ff-ai-working.block .ff-ai-text { align-items: center; }
.ff-ai-cancel { flex: none; }

/* Mark: a breathing core sparkle + a small orbiting satellite. */
.ff-ai-spark { position: relative; width: 26px; height: 26px; flex: none; color: var(--mud-palette-primary); }
.ff-ai-working.block .ff-ai-spark { width: 40px; height: 40px; }
.ff-ai-spark .core { position: absolute; inset: 0; display: grid; place-items: center;
    animation: ff-ai-breathe 2s ease-in-out infinite; }
.ff-ai-spark .core .mud-icon-root { font-size: inherit; width: 100%; height: 100%; }
.ff-ai-spark .sat { position: absolute; top: 50%; left: 50%; width: 6px; height: 6px; margin: -3px 0 0 -3px;
    border-radius: 50%; background: var(--mud-palette-primary); opacity: .85;
    animation: ff-ai-orbit 2.6s linear infinite; }

@keyframes ff-ai-breathe {
    0%, 100% { opacity: .55; transform: scale(.92) rotate(-4deg); }
    50%      { opacity: 1;   transform: scale(1.12) rotate(6deg); }
}
@keyframes ff-ai-orbit {
    from { transform: rotate(0deg) translateX(12px) rotate(0deg); }
    to   { transform: rotate(360deg) translateX(12px) rotate(-360deg); }
}

/* Status line: a clipped gradient sheen sweeps the text. */
.ff-ai-status { font-size: .9rem; font-weight: 600; line-height: 1.35;
    background: linear-gradient(90deg,
        var(--mud-palette-text-secondary) 30%,
        var(--mud-palette-primary) 50%,
        var(--mud-palette-text-secondary) 70%);
    background-size: 220% 100%;
    -webkit-background-clip: text; background-clip: text; color: transparent;
    animation: ff-ai-sheen 2.4s ease-in-out infinite; }
@keyframes ff-ai-sheen { 0% { background-position: 160% 0; } 100% { background-position: -60% 0; } }

/* Fallback: where background-clip:text is unsupported the text would clip to transparent
   and vanish, so paint it solid primary instead (risk #2). */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
    .ff-ai-status { background: none; color: var(--mud-palette-primary);
        -webkit-text-fill-color: currentColor; animation: none; }
}

.ff-ai-sub { font-size: .74rem; color: var(--mud-palette-text-secondary); }

/* Block-variant indeterminate track. */
.ff-ai-track { position: relative; width: min(260px, 70%); height: 3px; border-radius: 3px; overflow: hidden;
    background: rgba(var(--mud-palette-primary-rgb), .16); }
.ff-ai-track::after { content: ""; position: absolute; top: 0; bottom: 0; left: -40%; width: 40%;
    border-radius: inherit; background: var(--mud-palette-primary);
    animation: ff-ai-slide 1.4s ease-in-out infinite; }
@keyframes ff-ai-slide { 0% { left: -40%; } 100% { left: 100%; } }

/* Opt-in busy sweep a parent can add to its own MudButton. */
.ff-ai-btn-busy {
    background: linear-gradient(110deg,
        var(--mud-palette-primary) 20%,
        var(--mud-palette-primary-darken, #C74A00) 50%,
        var(--mud-palette-primary) 80%) !important;
    background-size: 200% 100% !important;
    animation: ff-ai-btn-sheen 2.2s ease-in-out infinite;
}
@keyframes ff-ai-btn-sheen { 0% { background-position: 100% 50%; } 100% { background-position: -100% 50%; } }

/* Better than the reference: honor reduced-motion. Text still rotates
   (a content change is not vestibular motion); the visuals settle. */
@media (prefers-reduced-motion: reduce) {
    .ff-ai-spark .core, .ff-ai-track::after, .ff-ai-btn-busy { animation: none; }
    .ff-ai-spark .sat { display: none; }
    .ff-ai-status { animation: none; background: none;
        -webkit-text-fill-color: currentColor; color: var(--mud-palette-primary); }
    .ff-ai-track::after { left: 0; width: 100%; opacity: .5; }
}

/* ---- 22D: scoring analysis ---- */
/* #44: the generated member Scoring Analysis page. The optional AI "how this league scores" summary
   sits in a soft callout above the per-category rule tables; the point value reads as a compact,
   emphasized number in each row. */
.ff-scoring-summary { padding: 12px 14px; border-radius: 12px;
    background: var(--mud-palette-background-grey); border: 1px solid var(--mud-palette-lines-default); }
.ff-scoring-summary-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
    color: var(--mud-palette-text-secondary); }
.ff-scoring-pts { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---- punch-A ---- */

/* #(buzz) League Buzz desktop: recap, power ranking, and weekly awards go three-across at desktop
   widths (>=961px), overriding the shared 2-col .ff-buzz-cols/.ff-cols grid. Mobile keeps the
   single-column stack from .ff-cols (max-width:960px). */
@media (min-width: 961px) {
    .ff-buzz-cols { grid-template-columns: 1fr 1fr 1fr; }
}

/* #62 (a): the Scouting season boards are fixed-height scrolling cards laid out two-across. Clip each
   card so a table's sticky header / wide row can never paint outside its rounded box and overlap the
   neighboring board (or the sticky segmented tabs). The .ff-chart-empty/tooltip overlays render in a
   portal, so this does not clip them. */
.ff-scout-boards .mud-card { overflow: hidden; }

/* #55: the Teams-hub "By season" table sparklines live in a td.spark host that the #50 .ff-chart clip
   did not fully tame (the cell's own line-height left a baseline gap that tripped a residual internal
   scrollbar in the Trend column). Zero the host line-height and reinforce the chart clip here. */
.ff-table td.spark { line-height: 0; }
.ff-table td.spark .ff-chart,
.ff-table td.spark .ff-chart > div { overflow: hidden; line-height: 0; }

/* ---- 25A: conference hub ---- */
/* The per-season membership note under the conference hero (realignment honesty). Muted, small, and
   aligned with its info icon, matching the page-sub tone without competing with the headline. */
.ff-conf-note {
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--mud-palette-text-secondary);
    font-size: 0.8rem;
}
.ff-conf-note .mud-icon-root { font-size: 1rem; }

/* ---- batch-D ---- */

/* #1: League Buzz — the three-across recap / power-ranking / awards row was capped at ~980px, so it
   left dead space on wider desktops. Let the whole buzz column (and thus the equal 1fr 1fr 1fr grid
   inside it) use the full content width at desktop; the mobile single-column stack (.ff-cols at
   <=960px) is unchanged. */
@media (min-width: 961px) {
    .ff-buzz { max-width: none; }
}

/* #2: Draft "My Team" — the weekly lineup card and the "Your week" metrics panel stacked because the
   wrap was a single capped column. Lay them out as two equal columns that fill the row on desktop
   (mirroring the .ff-cols grid), stacking on phones; the card internals are untouched, and the
   "Your week" panel drops its stacked-era cap and top margin so the two tops line up. */
.ff-team-wrap { max-width: none; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 960px) { .ff-team-wrap { grid-template-columns: 1fr; } }
.ff-team-wrap > .ff-mt-week { max-width: none; margin-top: 0; }

/* #4: /stats player cells — keep the player-page link and the compact weekly-breakdown toggle on one
   line, with the toggle reading as a quiet secondary control. */
.ff-stats-name-cell { display: inline-flex; align-items: center; gap: 2px; }
.ff-stats-drill { opacity: .65; }
.ff-stats-drill:hover { opacity: 1; }

/* ---- 26: public shell ---- */

/* Most public chrome lives in scoped .razor.css (PublicLayout / Landing); this block holds only the
   genuinely GLOBAL public concern: the single-source brand accent colour.

   --ff-brand is the one place the public brand accent is defined (owner item 21). The public brand
   mark accents, hero eyebrow/glyph, and how-it-works step badges all key off it. It is the ratified
   logo orange (#F85B01), a fixed brand constant that reads on both light and dark surfaces. The
   MudBlazor theme primary is also this orange, so accents stay consistent with buttons/links.

   Swappable brand pipeline (owner item 21): the canonical mark is wwwroot/brand/mark-square.png and
   the full lockups are logo-dark.png (white text, dark surfaces) / logo-light.png (black text, light
   surfaces). Swap those assets and rerun tools/brand/Generate-BrandAssets.ps1 to re-derive every
   favicon / PWA icon / OG share card; retint here and in App.razor's MudTheme to change the accent. */
:root {
    --ff-brand: #F85B01;
}

/* Public marketing pages must never introduce horizontal overflow at any width (mobile-first
   guard); the scoped layouts size with relative units, this is the belt-and-braces backstop. */
.ff-public .ff-public-body {
    overflow-x: hidden;
}

/* ---- Auth surfaces (Login / Register / Forgot / Reset / Verify) ----
   Single-source card so every auth page reads as a sibling: one branded card centered on the dark
   base, orange brand hairline, sporty eyebrow, consistent typography and rhythm. All five live under
   PublicLayout, so the site banner + footer wrap them. Global (not scoped) on purpose: one definition
   keeps the pages identical, replacing the duplicated per-page recovery CSS. Rides MudBlazor palette
   vars so light and dark both read; accents key off --ff-brand (the ratified logo orange). Mobile-first,
   verified usable at 375px. No em dashes in copy (CI guard). */
.ff-auth {
    padding-top: 40px;
    padding-bottom: 56px;
}

.ff-auth-card {
    position: relative;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 16px;
    padding: 32px 22px 26px;
    overflow: hidden;
    box-shadow: 0 12px 34px rgba(0, 0, 0, .18);
}

/* Orange brand hairline across the top of every auth card (the small brand touch). */
.ff-auth-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: var(--ff-brand, var(--mud-palette-primary));
}

.ff-auth-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0 0 10px;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ff-brand, var(--mud-palette-primary));
}

.ff-auth-eyebrow .mud-icon-root {
    font-size: 1.05rem;
}

.ff-auth-title {
    margin: 0 0 6px;
    font-size: 1.6rem;
    line-height: 1.15;
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--mud-palette-text-primary);
}

.ff-auth-lede,
.ff-auth-help {
    margin: 0 0 22px;
    font-size: .95rem;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary);
}

.ff-auth-help {
    margin-top: 4px;
}

.ff-auth-footer {
    margin-top: 22px;
    text-align: center;
    font-size: .9rem;
    color: var(--mud-palette-text-secondary);
}

.ff-auth-legal {
    margin-top: 16px;
    text-align: center;
    font-size: .78rem;
    line-height: 1.5;
    color: var(--mud-palette-text-secondary);
}

@media (min-width: 600px) {
    .ff-auth {
        padding-top: 56px;
    }

    .ff-auth-card {
        padding: 40px 34px 32px;
    }

    .ff-auth-title {
        font-size: 1.85rem;
    }
}

/* ---- sticky footer (owner fix, final) ----
   Standard sticky footer: .mud-main-content is a min-height:100dvh flex column and the content region
   (.ff-public-body / .ff-appshell-body, scoped) grows (flex:1) to fill, so <SiteFooter/> is pinned to
   the VIEWPORT BOTTOM with no gap below it on short pages, and is pushed down naturally on long pages.
   (An earlier 60dvh "trail the content" variant left dead space BELOW the footer on tall screens; this
   restores the footer to the bottom, which is what an "actual footer" means.)
   Legacy notes below kept for history.
   ---- original notes ----
   Problem (original): <SiteFooter/> renders in normal content flow. MudBlazor's .mud-layout is
   height:100% and is NOT a flex container, so .mud-main-content's own `flex:1 1 auto` is inert and the
   content area collapses to its content height. On SHORT pages that left the footer floating
   mid-viewport with dead space beneath it. First fix: promote .mud-main-content to a flex column with
   `min-height:100vh` and grow the content region (flex:1 0 auto) to fill it, which pinned the footer to
   the exact viewport bottom. That killed the float but introduced #37: on short / sparse pages the
   grown content region left a large EMPTY VOID between the top-anchored content and the bottom-pinned
   footer.

   Refined model (#37): do NOT force the full viewport (that is what jammed the footer to the very
   bottom and opened the void above it). Instead .mud-main-content is a plain flex column and the content
   region (.ff-public-body / .ff-appshell-body, in each layout's scoped sheet) carries a SENSIBLE
   min-height (60dvh) plus a comfortable bottom margin. Result: on a short page the content keeps a
   reasonable presence and the footer trails it a comfortable, intentional distance below (footer lands
   in the lower third, ~70% down on a laptop) instead of being jammed to the bottom with a huge void
   above it, and instead of floating up mid-screen. On a normal / long page the content exceeds the
   60dvh floor, so the footer simply trails the content and is pushed down / below the fold naturally
   (never fixed, never overlaid). Any leftover slack on a very tall monitor sits below the footer as
   ordinary, seamless page background rather than as an ugly hole above it.

   Why global (not the scoped .razor.css files): .mud-main-content is emitted by the MudMainContent
   child component, so it carries no parent scope id and cannot be reached from PublicLayout /
   MainLayout scoped CSS. The content-region items (.ff-public-body, .ff-appshell-body) DO live in
   their layouts' scope and carry the min-height + margin there.

   box-sizing:border-box + the column flex keep the appbar padding-top and the content stacking
   predictable; the fixed appbar/drawer are out of normal flow and add no document height. The content
   region uses 60dvh with a 60vh fallback so it tracks the real viewport under mobile browser chrome.
   Mobile-first: verified at 375px (content taller than the floor there, so the footer just trails it;
   column flex, no fixed widths, no horizontal overflow). */
.ff-public .mud-main-content,
.ff-appshell .mud-main-content {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    min-height: 100vh;
    min-height: 100dvh;
}

/* ---- 30E: ownership transfer ---- */

/* The commissioner-retirement panel on the members page and the nominee's Accept/Decline prompt.
   Both reuse the existing MudCard/MudAlert chrome; these rules only handle the responsive layout of
   the prompt row so the action buttons wrap under the copy on narrow viewports. */
.ff-transfer-panel {
    margin-bottom: 1.5rem;
}

.ff-transfer-prompt-body {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    justify-content: space-between;
}

.ff-transfer-prompt-actions {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
}

@media (max-width: 600px) {
    .ff-transfer-prompt-actions {
        width: 100%;
    }
}

/* 28H: account danger zone (self-service delete + export). A subtle error-tinted panel edge marks the
   destructive section; the goodbye state centers on the post-deletion sign-out. Reuses palette vars so
   it tracks light/dark automatically. */
.ff-danger-zone { border: 1px solid var(--mud-palette-error); }
.ff-danger-zone .ff-panel-head { border-bottom-color: var(--mud-palette-error); }
.ff-delete-confirm {
    border-left: 3px solid var(--mud-palette-error);
    padding-left: 12px;
    margin-top: 8px;
}
.ff-goodbye { max-width: 560px; margin-left: auto; margin-right: auto; }

/* ============================================================
   Wave 2 mobile polish (#54): app-bar league-name truncation.
   The switcher control is capped (max-width: 42vw on <=600px, above). These rules let the whole
   MudButton chain shrink below its content so a long league name TRUNCATES with an ellipsis
   ("Gridiron Test Le...") instead of overflowing and being clipped on both ends. The name is wrapped
   in .ff-league-switcher-name (LeagueSwitcher.razor) so text-overflow has a single-line target.
   These reach MudBlazor internals, so they live here (global) rather than in scoped CSS.
   ============================================================ */
.ff-league-switcher.mud-button-root,
.ff-league-switcher .mud-button-root {
    min-width: 0;
    max-width: 100%;
}
.ff-league-switcher .mud-button-label {
    min-width: 0;
    overflow: hidden;
    flex-wrap: nowrap;
}
.ff-league-switcher-name {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ============================================================================================
   WAVE 2 PUBLIC REDESIGN — shared motion utilities (single appended block; owner-ratified).

   These are GLOBAL because the scroll-reveal state is gated behind html.ff-js-motion (an ancestor
   of the scoped page elements), which Blazor scoped sheets cannot own, and because the same reveal
   utility + shared keyframes are reused across every public page. Page-specific look (hero, cards,
   badges) stays in each page's scoped .razor.css. Nothing here bleeds into the authed app: the
   .ff-reveal / .ff-hero-* hooks only exist on the public marketing markup.

   Motion contract (js/public-motion.js): markup is authored VISIBLE. The module adds .ff-js-motion
   only when it is safe to animate, so SSR / crawlers / no-JS / prefers-reduced-motion all render the
   fully visible page with zero movement. The reduced-motion @media below is belt-and-braces.
   ============================================================================================ */

/* Scroll-reveal: hidden pre-state applies ONLY once JS has opted in via html.ff-js-motion. */
.ff-js-motion .ff-reveal {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity .6s cubic-bezier(.22, .61, .36, 1),
                transform .6s cubic-bezier(.22, .61, .36, 1);
    will-change: opacity, transform;
}

.ff-js-motion .ff-reveal.is-visible {
    opacity: 1;
    transform: none;
}

/* Staggered reveal within a section so grouped items cascade rather than snap in together. */
.ff-js-motion .ff-reveal.ff-reveal--d1 { transition-delay: .08s; }
.ff-js-motion .ff-reveal.ff-reveal--d2 { transition-delay: .16s; }
.ff-js-motion .ff-reveal.ff-reveal--d3 { transition-delay: .24s; }
.ff-js-motion .ff-reveal.ff-reveal--d4 { transition-delay: .32s; }

/* Shared entrance keyframe for on-load hero content (plays once, no observer needed). Uniquely
   named so it never collides with a MudBlazor / scoped animation. */
@keyframes ffFadeUp {
    from { opacity: 0; transform: translateY(22px); }
    to   { opacity: 1; transform: none; }
}

/* Respect the user's motion preference everywhere: kill reveals, on-load entrances, and the hero
   accent sweep. (public-motion.js already skips hiding under this preference; this covers the pure
   CSS on-load animations too.) */
@media (prefers-reduced-motion: reduce) {
    .ff-js-motion .ff-reveal {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
    .ff-anim-load,
    .ff-anim-load > * {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
    }
    .ff-hero-streak,
    .ff-hero-orb { animation: none !important; }
}

/* ============================================================================================
   AI + MOBILE REFINEMENT (M1, 2026-07-24) — hub header single row + AI prose link affordance.
   The campaign's only append to this sheet (single-owner rule), covering two items:

   R1 — the Player / Team / Conference hub headers keep every action button on ONE row at 360px.
   These are additive modifier classes layered on top of .ff-detail-title; the base rule above
   (~:296) is deliberately NOT modified, because six other surfaces (PlayerDetailPage x2, GameDay,
   PlayerCompare, TeamCompareView, ConferenceCompareView) share it and carry a value block or a
   status badge rather than an action cluster. The whole fix is min-width:0 on the name zone: a
   flex item will not shrink below its min-content width by default, which is exactly why the
   single-level row wrapped. With the name zone able to shrink, it absorbs all the pressure and the
   action cluster (flex-shrink:0, nowrap) can never be pushed onto a second row. The Hot/Cold form
   badge stays inside the name zone and simply wraps within it on narrow phones.

   R2 — prose links inside .ff-md (assistant, stat search, League Buzz, Home recap, constitution,
   member bio, activity feed) get an underline. On touch there is no hover, so the underline is the
   affordance; it also satisfies "not by color alone" independently of the brand orange.
   ============================================================================================ */

.ff-hub-title { flex-wrap: nowrap; align-items: flex-start; gap: 10px; }

.ff-title-name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    min-width: 0;   /* the elastic zone: without this the row cannot shrink and wraps instead */
    flex: 1 1 auto;
}

.ff-title-name h1 { min-width: 0; overflow-wrap: anywhere; }

.ff-title-actions {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    flex-shrink: 0;
    margin-left: auto;
    gap: 0;   /* Size.Small icon buttons already carry their own padding */
}

/* Phone: tighten the gaps and let the name scale between the clamp floor and today's 1.7rem. */
@media (max-width: 599.98px) {
    .ff-hub-title { gap: 6px; }
    .ff-title-name { gap: 6px; }
    .ff-title-name h1 { font-size: clamp(1.05rem, 4.6vw, 1.7rem); line-height: 1.2; }
}

/* R2: touch-visible link affordance for rendered markdown (MarkdownLite emits allow-listed
   internal links only, so no external URL can ever pick this up). */
.ff-md a {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}

/* ============================================================================================
   Punch 2026-07-25 (P-12) — page-level loading rows center in their host card, matching the AI
   working indicator's centering fix. The spinner + "Loading x" text used to hug the card's left
   edge; ff-loading is the one shared treatment for every block-level loading state. (The tiny
   inline spinners beside section titles are a different, deliberate pattern and keep their spot;
   LeagueBuzz's ff-buzz-loading was already centered and now shares this class family.)
   ============================================================================================ */
.ff-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 20px 16px;
    text-align: center;
}

/* ============================================================================================
   Punch 2026-07-26 (P-13 / P-16) — the ResponsiveList mobile card, opt-in refinements.

   P-13: CompactMobile packs the card's stat columns TWO PER LINE as label-over-value mini cells
   (the .ff-tile label/value idiom, shrunk). One full-width labeled line per stat left the middle of
   every row empty and capped a card at ~4 stats; the grid fits ~6 in the same height, which is what
   lets the conference/team lists un-hide their previously desktop-only detail columns. An odd stat
   count spans its last cell (.span2) across both columns so the grid never ends on a ragged half row.

   P-16: FullWidthOnMobile renders a long explainer column (Breakouts "Why", Pool changes "What
   happened") as an unlabeled caption-size muted line under the name lines, instead of a label/value
   pair whose right-aligned value wrapped into a split. Both boards now share the one row idiom.

   Both blocks only ever render inside the card container (.d-sm-none), so they are mobile-only by
   construction and need no media query of their own.
   ============================================================================================ */
.ff-rl-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 14px;
    margin-top: 4px;
}

.ff-rl-stat { display: flex; flex-direction: column; min-width: 0; }
.ff-rl-stat.span2 { grid-column: 1 / -1; }

.ff-rl-stat .k {
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    line-height: 1.3;
    color: var(--mud-palette-text-secondary);
}

.ff-rl-stat .v {
    font-size: .9rem;
    font-weight: 600;
    line-height: 1.35;
    overflow-wrap: anywhere;   /* a long school or player name must never widen the card */
}

.ff-rl-note {
    margin-top: 6px;
    font-size: .78rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
    overflow-wrap: anywhere;
}

/* ============================================================================================
   Punch 2026-07-27 (P-24) — the research boards' structured context row. Breakouts and Pool
   changes each render their existing prose line, then a wrapping row of tiny label+value chips
   built from NULLABLE DTO fields, so a row with no numbers renders no chip row at all. Shared
   classes on purpose: the owner reads the two boards as one family.

   On mobile the chips live inside the card's full-width .ff-rl-note caption line (P-16), on
   desktop inside a left-aligned table cell — both start-aligned, so no breakpoint flip is
   needed. flex-wrap keeps the row inside 360px; each chip is nowrap so a "share 7.4% to 50%"
   value never breaks mid-number.
   ============================================================================================ */
/* Spans, not divs: the desktop cell may sit inside inline flow; display promotes them here. */
.ff-ctx-why { display: block; line-height: 1.4; }

.ff-ctx-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 4px;
    justify-content: flex-start;
}

.ff-ctx-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    max-width: 100%;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--mud-palette-background);
    border: 1px solid var(--mud-palette-lines-default);
    font-size: .68rem;
    line-height: 1.7;
    white-space: nowrap;
}

.ff-ctx-chip .lbl {
    font-size: .62rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--mud-palette-text-secondary);
}

.ff-ctx-chip .val { font-weight: 600; }

/* The reference-scoring disclaimer that sits above either board whenever a points chip can show. */
.ff-ctx-note {
    margin: 0 0 12px;
    font-size: .75rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
}


/* ============================================================================================
   Punch 2026-07-27 (P-20) — My Team "Your week" enrichment. Extends the 18B .ff-mt-* idiom with
   three additions: a section rule (.ff-mt-sec) that groups the panel into scannable blocks, the
   slot-vs-league-average bars (.ff-mt-slots), and the denser season strip (.ff-mt-strip).
   Mobile first: the strip is 2-up at 360px and 4-up from 600px, and the bar row is a grid whose
   only elastic column is the track, so the pos pill and the numbers never shrink into slivers.
   ============================================================================================ */

.ff-mt-sec {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 16px 0 8px;
    border-top: 1px solid var(--mud-palette-lines-default);
    padding-top: 12px;
}

.ff-mt-sec .k {
    color: var(--mud-palette-text-secondary);
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}

.ff-mt-sec .sub { color: var(--mud-palette-text-secondary); font-size: .7rem; flex: 0 0 auto; }

/* One bar row: [pos pill] [elastic track] [numbers]. */
.ff-mt-slots { display: flex; flex-direction: column; gap: 8px; }

.ff-mt-slot {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
}

.ff-mt-slot > .ff-pos { min-width: 44px; }

.ff-mt-bar {
    position: relative;
    height: 10px;
    border-radius: 999px;
    background: var(--mud-palette-background-grey);
    border: 1px solid var(--mud-palette-lines-default);
    overflow: hidden;
}

.ff-mt-bar .fill { position: absolute; inset: 0 auto 0 0; border-radius: 999px; }
.ff-mt-bar .fill.over { background: var(--mud-palette-success); }
.ff-mt-bar .fill.under { background: var(--mud-palette-warning); }

/* The league-average tick sits on top of the fill, so "am I over or under" is readable without
   relying on the fill color alone. */
.ff-mt-bar .tick {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: var(--mud-palette-text-primary);
    opacity: .65;
}

.ff-mt-slot .nums { font-size: .78rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ff-mt-slot .nums b { font-weight: 700; }
.ff-mt-slot .nums i { font-style: normal; color: var(--mud-palette-text-secondary); }

/* Season strip: denser than .ff-mt-tile so eight numbers on one panel still scan. */
.ff-mt-strip { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

.ff-mt-stat {
    padding: 8px 10px;
    border-radius: 10px;
    background: var(--mud-palette-background-grey);
    border: 1px solid var(--mud-palette-lines-default);
    min-width: 0;
}

.ff-mt-stat .k {
    display: block;
    color: var(--mud-palette-text-secondary);
    font-size: .64rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}

.ff-mt-stat .v {
    display: block;
    font-weight: 700;
    font-size: 1.05rem;
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
    overflow-wrap: anywhere;
}

.ff-mt-stat .v .of { font-size: .72rem; font-weight: 600; color: var(--mud-palette-text-secondary); }

.ff-mt-note { color: var(--mud-palette-text-secondary); font-size: .78rem; padding: 4px 0; }

@media (min-width: 600px) {
    .ff-mt-strip { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 400px) {
    .ff-mt-slot { grid-template-columns: 38px minmax(0, 1fr) auto; gap: 6px; }
    .ff-mt-slot > .ff-pos { min-width: 38px; }
    .ff-mt-slot .nums { font-size: .72rem; }
}

/* ============================================================================================
   Punch 2026-07-27 (P-17) - Scoreboard Season Total row expansion (ScoreboardSeasonPanel).
   The Season tab used to expand into the same current-week lineup the This Week tab shows; it now
   expands into a season summary. Deliberately reuses the ff-sb-panel indent and the ff-sb-lineup-*
   loading/empty treatments above, so only the season-specific rows are new here. Mobile-first: the
   contributor row is a 3-column grid that collapses to name + points at 360px, and the stat line
   wraps rather than scrolling.
   ============================================================================================ */
.ff-sb-season { display: flex; flex-direction: column; gap: 2px; }

.ff-sb-season-head {
    font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary); padding: 2px 2px 4px;
}

.ff-sb-carry {
    display: grid; grid-template-columns: 20px 1fr auto; align-items: center; gap: 10px;
    padding: 6px 2px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-sb-carry-rank {
    font-size: .68rem; font-weight: 700; font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-secondary); text-align: center;
}
.ff-sb-carry-main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ff-sb-carry-main .name {
    font-size: .87rem; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ff-sb-carry-sub { font-size: .68rem; color: var(--mud-palette-text-secondary); }
.ff-sb-carry .pt { font-variant-numeric: tabular-nums; font-weight: 700; font-size: .87rem; }

/* The compact season line: best week, average per week, weeks won. The last contributor row's own
   bottom border is the divider, so this block adds spacing rather than a second rule. */
.ff-sb-season-line {
    display: flex; flex-wrap: wrap; gap: 6px 16px;
    margin-top: 8px;
}
.ff-sb-season-stat { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ff-sb-season-stat .k {
    font-size: .62rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}
.ff-sb-season-stat .v { font-size: .84rem; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ============================================================================================
   Punch 2026-07-27 (P-25) — Records Book head-to-head picker. The wrap-based row let the two
   member selects land on different lines with different partners (A beside "vs", B beside the
   Compare button), so they rendered at different widths on a phone. Under 600px each control
   takes its own full-width line: both selects identical, the "vs" divider centered, the button
   full width. Desktop keeps the single-row layout above.
   ============================================================================================ */
@media (max-width: 599.98px) {
    .ff-rivalry-picker > .mud-input-control { flex: 1 1 100%; min-width: 0; }
    .ff-rivalry-picker .ff-rivalry-vs { flex: 1 1 100%; text-align: center; }
    .ff-rivalry-picker > .mud-button-root { flex: 1 1 100%; }
}

/* ============================================================================================
   Moat on Display MD-D - the /leaders full-landscape leaderboard.

   The board is intentionally a wide table (rank, player, team, position, games, points, the picked
   stat, and the headline stat line), so at phone widths it scrolls INSIDE its own container and the
   page body never scrolls sideways. The rank and player columns stay pinned while the numbers scroll
   past, so a value is always attached to a name. The pinned cells carry the surface background (and
   a hairline on the right edge of the name column) so scrolled content passes underneath cleanly.
   ============================================================================================ */
.ff-leaders-note { margin-top: -8px; }
.ff-leaders-toolbar { align-items: flex-end; }
.ff-leaders-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ff-leaders-table { min-width: 100%; }
.ff-leaders-table td.muted { color: var(--mud-palette-text-secondary); }
/* Fixed and box-sized so the sticky offset of the name column below is exact, whatever the ambient
   box model. 40px holds a two-digit rank at the table's 8px cell padding. */
.ff-leaders-table th.rankcol, .ff-leaders-table td.rankcol {
    width: 40px; min-width: 40px; box-sizing: border-box; padding-left: 8px; padding-right: 8px;
}

@media (max-width: 599.98px) {
    /* The one-control-per-line stack that used to live here is superseded by .ff-filter-grid (triage
       T-1, in the appended block at the end of this file): six full-width rows consumed the whole
       first phone viewport before any data rendered. */

    .ff-leaders-table th.rankcol, .ff-leaders-table td.rankcol {
        position: sticky; left: 0; z-index: 2;
        background: var(--mud-palette-surface);
    }
    .ff-leaders-table th.namecol, .ff-leaders-table td.namecol {
        position: sticky; left: 40px; z-index: 2;
        background: var(--mud-palette-surface);
        box-shadow: 1px 0 0 0 var(--mud-palette-lines-default);
    }
    /* The striped/hover rows would otherwise show through the pinned cells. */
    .ff-leaders-table tbody tr:hover td.rankcol,
    .ff-leaders-table tbody tr:hover td.namecol { background: var(--mud-palette-surface); }
}
/* ============================================================================================
   Wake the Rails B1 (FI-16) - bulk invite outcomes on the league members page. One row per
   pasted address: status icon, the address, and a short note. The address column shrinks and
   ellipsizes before the note wraps, so a long address never pushes the note off a 360px screen.
   ============================================================================================ */
.ff-bulk-invite-results { display: grid; gap: 6px; }
.ff-bulk-invite-row {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: .82rem; line-height: 1.35; min-width: 0;
}
.ff-bulk-invite-email {
    font-weight: 600; min-width: 0; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ff-bulk-invite-note { color: var(--mud-palette-text-secondary); }

/* ============================================================================================
   Saturday Habit C1 (2026-07-27) — install-to-notify onboarding.
   .ff-push-notice is the notification center's quiet "alerts are off on this device" line: a
   caption-weight row that wraps onto its own lines at 360px rather than squeezing the button off
   the edge. .ff-pwa-inline-icon lets the iOS Share glyph sit on the text baseline inside the
   add-to-home-screen instructions.
   ============================================================================================ */
.ff-push-notice {
    display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
    margin: 0 0 12px; min-width: 0;
}
.ff-push-notice .mud-typography { flex: 1 1 220px; min-width: 0; }
.ff-pwa-inline-icon { vertical-align: text-bottom; margin: 0 2px; }

/* ============================================================================================
   Saturday Habit C5 (FI-26) - the Game Day "watch your roster" TV slate. Two additive surfaces
   over the already-cached CFBD media feed, both invisible when that feed knows nothing about a
   school (the normal pre-warm state): a per-slot line under the player name ("Sat 3:30 PM on
   ABC") and the compact by-kickoff "Your slate" block on the My Team card. Everything keys off
   the MudBlazor palette vars so light and dark both read; the slate rows collapse to a stacked
   two-line layout under 400px so a 360px phone never scrolls sideways.
   ============================================================================================ */

/* Per-slot line. Sits under the name/game chip inside .ff-gd-main's column flow, so it inherits
   that stack's 3px gap and never widens the grid (min-width:0 + ellipsis do the clamping). */
.ff-gd-slate {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: .7rem; font-weight: 600;
    color: var(--mud-palette-text-secondary);
    min-width: 0; max-width: 100%;
}
.ff-gd-slate .ff-gd-slate-ico { font-size: .95rem; flex: none; opacity: .7; }
/* The clamp lives on the text child, not the flex container: text-overflow only applies to a block
   box, so a bare text node in a flex row would overflow instead of ellipsizing (the same reason
   .ff-gd-chip puts its ellipsis on .opp). Matters for long networks ("Big Ten Network") at 360px. */
.ff-gd-slate .txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The compact by-kickoff block on the member card. */
.ff-gd-slate-list {
    margin: 4px 6px 10px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    overflow: hidden;
}
.ff-gd-slate-list .hd {
    display: flex; align-items: center; gap: 6px;
    padding: 7px 10px;
    font-size: .66rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
    background: var(--mud-palette-background);
}
/* MudBlazor's Size.Small icon is 1.25rem, which dwarfs a .66rem uppercase label; pull it back. */
.ff-gd-slate-list .hd .mud-icon-root { font-size: 1rem; }
.ff-gd-slate-list .row {
    display: grid; grid-template-columns: minmax(80px, auto) minmax(44px, auto) 1fr;
    align-items: center; gap: 4px 10px;
    padding: 7px 10px;
    border-top: 1px solid var(--mud-palette-lines-default);
    font-size: .76rem;
}
.ff-gd-slate-list .row .tm { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ff-gd-slate-list .row .net { font-weight: 600; color: var(--mud-palette-primary); white-space: nowrap; }
.ff-gd-slate-list .row .who {
    min-width: 0; color: var(--mud-palette-text-secondary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Phone: time + network stay on one line, the player names wrap to their own full-width line. */
@media (max-width: 399.98px) {
    .ff-gd-slate-list .row { grid-template-columns: 1fr auto; }
    .ff-gd-slate-list .row .who { grid-column: 1 / -1; }
}

/* ============================================================================================
   Regret Engine RE-B - the member draft-efficiency surfaces.

   Three blocks, all appended here so parallel slices merge cleanly:
     1. Pool spotlight card (Scoreboard, above the tabs): two tiles, stacked on a phone and
        side by side from 600px, with the shared-denominator footnote as desktop-only detail.
     2. Capture chip on every standings row (decision D1): the compact percent always shows;
        its points-left detail earns the meta line only from 600px up.
     3. Personal efficiency panel (expanded own row) and the My Team regret replay rows.

   Percent values are NOT clamped at 100: a member who started a player the computed pool missed
   can honestly capture more than the pool's best legal roster, so nothing here assumes a 0..100
   range or draws a fill bar that would break past it.
   ============================================================================================ */

/* -- 1. Pool spotlight card ---------------------------------------------------------------- */
.ff-eff-card { margin-bottom: 16px; }
.ff-eff-week { font-size: .78rem; color: var(--mud-palette-text-secondary); font-variant-numeric: tabular-nums; }
.ff-eff-loading { display: flex; justify-content: center; padding: 18px; }

.ff-eff-spot { display: grid; grid-template-columns: 1fr; gap: 12px; }
.ff-eff-tile {
    display: flex; flex-direction: column; gap: 3px; min-width: 0;
    padding: 12px 14px; border-radius: 12px;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-background);
}
.ff-eff-tile.mvp { border-left: 3px solid var(--mud-palette-success); }
.ff-eff-tile.away { border-left: 3px solid var(--mud-palette-warning); }
.ff-eff-tile .k {
    display: flex; align-items: center; gap: 6px;
    font-size: .62rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}
.ff-eff-tile .nm { font-size: 1rem; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-eff-tile .nm.muted { color: var(--mud-palette-text-secondary); }
.ff-eff-tile .pts { font-size: .9rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.ff-eff-tile .sub { font-size: .72rem; color: var(--mud-palette-text-secondary); }

/* The shared denominator is context, not the headline: it earns its line only at desktop width. */
.ff-eff-note { display: none; margin-top: 12px; font-size: .74rem; color: var(--mud-palette-text-secondary); }

/* Entity-linked names stay visually quiet inside a dense card (the Batch B link convention). */
.ff-eff-name.ff-link { color: inherit; }
.ff-eff-name.ff-link:hover { color: var(--mud-palette-primary); }

@media (min-width: 600px) { .ff-eff-spot { grid-template-columns: 1fr 1fr; } }
@media (min-width: 960px) { .ff-eff-note { display: block; } }

/* -- 2. Capture chip on the standings rows -------------------------------------------------- */
.ff-sb-chip {
    flex: 0 0 auto; white-space: nowrap;
    font-size: .62rem; font-weight: 700; font-variant-numeric: tabular-nums;
    padding: 1px 6px; border-radius: 999px;
    background: rgba(var(--mud-palette-primary-rgb), .16); color: var(--mud-palette-primary);
}
/* Phone rows keep the compact badge only; the points-left detail returns with the room for it. */
.ff-sb-chipdetail { display: none; font-variant-numeric: tabular-nums; }
@media (min-width: 600px) { .ff-sb-chipdetail { display: inline; } }

/* -- 3. Personal panel + regret replay ------------------------------------------------------ */
.ff-eff-panel { display: flex; flex-direction: column; gap: 10px; padding: 8px 2px 12px; }
.ff-eff-none { font-size: .82rem; color: var(--mud-palette-text-secondary); }
.ff-eff-headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ff-eff-pct { font-size: 1.6rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.ff-eff-of { font-size: .78rem; color: var(--mud-palette-text-secondary); font-variant-numeric: tabular-nums; }

.ff-eff-stats { display: flex; flex-wrap: wrap; gap: 8px; }
.ff-eff-stat {
    flex: 1 1 120px; min-width: 0; display: flex; flex-direction: column; gap: 2px;
    padding: 8px 10px; border-radius: 10px; background: var(--mud-palette-background);
}
.ff-eff-stat .k {
    font-size: .6rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}
.ff-eff-stat .v { font-size: .95rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.ff-eff-stat .v.good { color: var(--mud-palette-success); }

.ff-eff-away { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: .8rem; }
.ff-eff-away .k { color: var(--mud-palette-text-secondary); font-size: .72rem; }
.ff-eff-away .nm { font-weight: 600; min-width: 0; }
.ff-eff-away .pts { font-variant-numeric: tabular-nums; font-weight: 700; }
.ff-eff-footnote { font-size: .7rem; color: var(--mud-palette-text-secondary); }

.ff-eff-replay { max-width: 720px; margin-top: 16px; }
.ff-eff-replay-sub {
    padding: 10px 14px 6px; font-size: .78rem;
    color: var(--mud-palette-text-secondary); font-variant-numeric: tabular-nums;
}

/* One line per slot at phone width (played on row 1, the comparison wrapped under it), two
   columns from 600px. The position chip keeps the ff-roster-row rhythm either way. */
.ff-eff-rr {
    display: grid; grid-template-columns: 52px 1fr; gap: 4px 12px; align-items: center;
    padding: 11px 14px; border-bottom: 1px solid var(--mud-palette-lines-default);
}
.ff-eff-rr:last-child { border-bottom: 0; }
.ff-eff-rr .played { grid-column: 2; display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.ff-eff-rr .played .nm { font-size: .89rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-eff-rr .played .nm.empty { color: var(--mud-palette-text-secondary); font-style: italic; }
.ff-eff-rr .played .pts { font-variant-numeric: tabular-nums; font-weight: 600; margin-left: auto; }
.ff-eff-rr .best {
    grid-column: 2; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
    font-size: .76rem; color: var(--mud-palette-text-secondary); min-width: 0;
}
.ff-eff-rr .best .nm { font-weight: 600; color: var(--mud-palette-text-primary); min-width: 0; }
.ff-eff-rr .best .pts { font-variant-numeric: tabular-nums; font-weight: 700; }
/* Fallback rgb triple so the praise tint still lands if the theme ever stops emitting the var. */
.ff-eff-rr.optimal { background: rgba(var(--mud-palette-success-rgb, 0, 200, 83), .07); }

.ff-eff-praise { font-weight: 700; color: var(--mud-palette-success); white-space: nowrap; }
.ff-eff-gain { font-weight: 700; color: var(--mud-palette-warning); font-variant-numeric: tabular-nums; }
.ff-eff-tag {
    font-size: .6rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    padding: 1px 5px; border-radius: 6px;
    background: var(--mud-palette-background); color: var(--mud-palette-text-secondary);
}

/* A league-configurable slot label outside the shipped five gets a neutral chip rather than
   borrowing another position's colour. */
.ff-pos.slot { background: #64748b; }

@media (min-width: 600px) {
    .ff-eff-rr { grid-template-columns: 52px minmax(0, 1fr) minmax(0, 1fr); }
    .ff-eff-rr .best { grid-column: 3; justify-content: flex-end; }
}

/* ============================================================================================
   Moat MD-B (2026-07-27) - defense-vs-position surfaces.
   The band chip (DvpBandChip), the per-team card row (DvpGrid, Teams hub Defense tab), the Player
   Hub scouting line, and the draft-board row chip. Colour comes from the BAND only, and it reuses
   the 16C trap/boom palette on purpose so DvP and matchup tags read as one research language:
   a stingy defense is the hard end (red/amber), a soft one is the opportunity end (blue/green).
   Breakpoints follow the plan matrix: 360 = chip glyph only, 600 = chip + "vs WR", 960 = chip +
   the full phrase; the card row stacks at 360, goes three-across at 600, and shows the landscape
   rank line at 960. Nothing here can widen the page (the card row is its own grid).
   ============================================================================================ */
.ff-dvp-chip {
    display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0;
    font-size: .6rem; font-weight: 700; line-height: 1.7; padding: 0 6px;
    border-radius: 8px; white-space: nowrap;
}
.ff-dvp-chip.top10 { color: var(--mud-palette-error); background: rgba(244, 67, 54, .16); }
.ff-dvp-chip.tough { color: var(--mud-palette-warning); background: rgba(255, 152, 0, .16); }
.ff-dvp-chip.average { color: var(--mud-palette-text-secondary); background: rgba(158, 158, 158, .16); }
.ff-dvp-chip.soft { color: var(--mud-palette-info); background: rgba(3, 169, 244, .16); }
.ff-dvp-chip.bottom10 { color: var(--mud-palette-success); background: rgba(76, 175, 80, .16); }
.ff-dvp-chip.none { color: var(--mud-palette-text-secondary); background: transparent; padding: 0 4px; font-weight: 600; }
.ff-dvp-chip .ff-dvp-pos { opacity: .75; }

/* Full band word from 600px; the 3-letter form below it (the matchup-chip idiom). */
.ff-dvp-short { display: none; }
@media (max-width: 599.98px) {
    .ff-dvp-full { display: none; }
    .ff-dvp-short { display: inline; }
}

/* ---- DvpGrid: one defense's QB/RB/WR cards ---- */
.ff-dvp-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.ff-dvp-card {
    border: 1px solid var(--mud-palette-lines-default); border-radius: 10px;
    padding: 10px 12px; min-width: 0;
}
.ff-dvp-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ff-dvp-card-val {
    margin-top: 6px; font-size: 1.25rem; font-weight: 700; font-variant-numeric: tabular-nums;
    display: flex; align-items: baseline; gap: 5px;
}
.ff-dvp-card-val.muted { color: var(--mud-palette-text-secondary); font-weight: 600; }
.ff-dvp-card-val .u { font-size: .68rem; font-weight: 600; color: var(--mud-palette-text-secondary); }
.ff-dvp-card-sub { font-size: .72rem; color: var(--mud-palette-text-secondary); margin-top: 2px; }
.ff-dvp-card-rank { display: none; font-size: .72rem; color: var(--mud-palette-text-secondary); margin-top: 2px; }
.ff-dvp-explainer { font-size: .72rem; color: var(--mud-palette-text-secondary); margin-top: 10px; }

@media (min-width: 600px) {
    .ff-dvp-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 960px) {
    .ff-dvp-card-rank { display: block; }
}

/* ---- Player Hub scouting line ---- */
.ff-dvp-scout { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ff-dvp-scout-phrase { font-size: .85rem; font-weight: 600; min-width: 0; }

/* ---- Draft board row chip ----
   The chip shares the existing pick-main cell (no new grid column), so the row keeps its four
   columns at every width and the team/owner line still ellipsizes instead of pushing the chip out. */
.ff-pick-row .pick-subline { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ff-pick-row .pick-subline > .pick-sub { min-width: 0; flex: 0 1 auto; }
.ff-dvp-inline { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; }
.ff-dvp-inline-pos, .ff-dvp-inline-full { display: none; }
.ff-dvp-inline-pos, .ff-dvp-inline-full {
    font-size: .68rem; color: var(--mud-palette-text-secondary); white-space: nowrap;
}
@media (min-width: 600px) {
    .ff-dvp-inline-pos { display: inline; }
}
@media (min-width: 960px) {
    .ff-dvp-inline-pos { display: none; }
    .ff-dvp-inline-full { display: inline; }
}
/* ---- Regret Engine RE-D: League Buzz gains the Pool Spotlight card ----
   The base .ff-buzz-cols grid is 3-across at >=961px (recap / power ranking / weekly awards). When a
   week also has a Pool Spotlight card the row would leave a lone fourth card stranded, so the grid
   drops to a clean 2x2. The modifier only lands when the card is actually present, which means a
   deploy with Features:DraftEfficiencyEnabled dark keeps today's exact three-across layout. Mobile is
   untouched: .ff-cols already stacks to one column at <=960px, so every card is full width at 360px. */
@media (min-width: 961px) {
    .ff-buzz-cols.has-spotlight { grid-template-columns: 1fr 1fr; }
}
/* ---- end RE-D ---- */

/* ============================================================================================
   Regret Engine RE-C (2026-07-27) - the season draft-efficiency leaderboard, the Scoreboard's
   fourth tab.

   The board is a seven-column table (rank, member, average capture, weeks counted, best, worst,
   trend), which is wider than a 360px phone. It therefore scrolls INSIDE its own container and the
   page body never scrolls sideways (the MD-D /leaders rule, reused verbatim): below 600px the rank
   and member columns pin to the left edge so a percent is always attached to a name, and the pinned
   cells carry the surface background so scrolled content passes underneath cleanly.

   The trend column is desktop detail (plan §9 breakpoint matrix: sparkline hidden at 360, present
   from 600). Hiding the CELL rather than the chart keeps the header and body column counts in step,
   so the pinned offsets stay exact at every width.

   Nothing here is loaded while Features:DraftEfficiencyEnabled is dark: the tab that mounts the
   board does not render at all, so these rules simply never match.
   ============================================================================================ */
.ff-eff-season-note {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    padding: 6px 6px 10px; font-size: .78rem; color: var(--mud-palette-text-secondary);
}
.ff-eff-season-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ff-eff-season-table { min-width: 100%; }
.ff-eff-season-table td.muted { color: var(--mud-palette-text-secondary); }
/* The week under a best/worst percent is context, not a second number to scan. */
.ff-eff-season-table .wk {
    display: block; font-size: .66rem; font-weight: 400;
    color: var(--mud-palette-text-secondary);
}
/* Fixed and box-sized so the sticky offset of the member column below is exact, whatever the
   ambient box model. 40px holds a two-digit rank at the table's 8px cell padding. */
.ff-eff-season-table th.rankcol, .ff-eff-season-table td.rankcol {
    width: 40px; min-width: 40px; box-sizing: border-box; padding-left: 8px; padding-right: 8px;
}
.ff-eff-season-table th.sparkcol, .ff-eff-season-table td.sparkcol {
    width: 130px; padding-top: 2px; padding-bottom: 2px;
}

@media (max-width: 599.98px) {
    .ff-eff-season-table th.sparkcol, .ff-eff-season-table td.sparkcol { display: none; }

    .ff-eff-season-table th.rankcol, .ff-eff-season-table td.rankcol {
        position: sticky; left: 0; z-index: 2;
        background: var(--mud-palette-surface);
    }
    .ff-eff-season-table th.namecol, .ff-eff-season-table td.namecol {
        position: sticky; left: 40px; z-index: 2;
        background: var(--mud-palette-surface);
        box-shadow: 1px 0 0 0 var(--mud-palette-lines-default);
    }
    /* The hover row would otherwise show through the pinned cells. */
    .ff-eff-season-table tbody tr:hover td.rankcol,
    .ff-eff-season-table tbody tr:hover td.namecol { background: var(--mud-palette-surface); }
}
/* ---- end RE-C ---- */

/* ================================================================================================
   Owner testing triage (2026-07-27) - phone findings from the owner's live dev session on an
   iPhone. Two surfaces, one theme: a phone viewport is a budget, and these two spent it on chrome.

   T-1  /leaders filter row. Five or six outlined selects at one per line filled the entire first
        viewport before a single leader rendered. .ff-filter-grid packs them TWO PER ROW below
        600px, which is the density idiom the app already settled on for phones (P-13's
        .ff-rl-stats two-column mobile card, and the .ff-rivalry-picker flex-basis collapse). Six
        rows become three (measured at 360px: the six pickers occupy 190px of height instead of the
        ~400px the stack cost); an odd control count spans its last cell so the grid never ends
        ragged (P-13's .span2 rule, expressed structurally so no markup has to know its parity). It is
        a named, reusable class rather than a page rule so the next filter-heavy surface can adopt
        it without a second invention. Desktop is untouched: above 600px the element is still the
        plain .ff-toolbar flex row it has always been.

   T-3/T-4  Draft Efficiency season board. Two problems with one cause, the row box. Every number
        below is MEASURED in a 360px viewport against this stylesheet, before and after:
        - Density. The best/worst cells stacked a percent over a "Week 3" caption, so EVERY row was
          47px tall whether or not those columns were even on screen. Below 600px the caption rides
          INLINE after the percent instead ("92% Week 3"), which is the same information on one line,
          and the cell padding tightens to 5px/8px. A row measures 36px, so a ten member league costs
          about a quarter less scrolling.
        - Tap targets do not shrink with the rows. The member link's hit box was only ever its text
          line (the old row height was cell padding, which sits OUTSIDE the anchor), so the anchor is
          given its own vertical padding here: measured, the hit box GROWS from 17px to 25px while
          the row still shrinks by 11px.
        - The clipped WEEKS column. At 360px the four columns that matter measured 380px wide
          (40 rank + 163 member + 105 capture + 72 weeks), so "11 of 14" was cut 20px short at the
          viewport edge and the in-container scroll (which is by design) had to be used just to read
          the headline number. Three trims fix it without hiding anything: the 8px horizontal cell
          padding above, the member column capped and ellipsized, and the "Avg capture" header
          abbreviating to "Avg" below 600px (the .ff-dvp-full / .ff-dvp-short idiom, reused
          verbatim). Measured after: 40 + 124 + 41 + 64 = 269px, so all four land inside a 360px
          viewport with room to spare and best/worst/trend are what the member scrolls to. The table
          is still 443px wide, so the horizontal scroll stays where the design put it (inside the
          container, never the page body).

   T-5  is a copy change in the component, not CSS: .ff-eff-season-scope is the line that names the
        pool a capture is measured against, so a season whose weeks carry no conference restriction
        reads as "measured against all of FBS", not as a broken number.
   ================================================================================================ */

/* ---- T-1: the two-per-row phone filter grid ---- */
/* Above 600px this class contributes nothing; the element is its .ff-toolbar flex row. */
@media (max-width: 599.98px) {
    .ff-filter-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px 10px;
        align-items: end;
        margin-bottom: 12px;
    }
    /* The pickers carry per-control inline max-widths sized for the desktop row; in the grid the
       track is the width, so they must be free to fill it. The selector is the direct child, whatever
       it is: a MudSelect's OWN root is div.mud-select and it carries the inline Style, while
       .mud-input-control is a level further in. (The stack rule this block replaces targeted
       .mud-input-control as a direct child, which never matched anything.) !important is required
       because the width being beaten is an inline style. */
    .ff-filter-grid > * {
        max-width: 100% !important;
        width: 100%;
        min-width: 0;
    }
    /* An odd control count (the Week picker only exists in single-week view) would otherwise leave a
       ragged half row, so the last control spans both tracks. */
    .ff-filter-grid > *:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ---- T-5: the line that names the pool a capture is measured against ---- */
/* Sits directly under .ff-eff-season-note (which carries its own 10px bottom pad), reads as a
   caption rather than a second headline, and wraps to two lines at 360px without crowding. */
.ff-eff-season-scope {
    margin-top: -8px;
    padding: 0 6px 10px;
    font-size: .72rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
}

/* ---- T-3 / T-4: season efficiency board density at phone widths ---- */
/* The short column header is hidden by default and swaps in below 600px (DvP chip idiom). */
.ff-eff-dense .ff-col-short { display: none; }

@media (max-width: 599.98px) {
    .ff-eff-dense .ff-col-full { display: none; }
    .ff-eff-dense .ff-col-short { display: inline; }

    /* The row box. 8px horizontal is what buys the WEEKS column its place inside 360px. */
    .ff-eff-season-table.ff-eff-dense th,
    .ff-eff-season-table.ff-eff-dense td { padding: 5px 8px; }

    /* The best/worst week caption stops being a second line. Same words, one line, muted. */
    .ff-eff-season-table.ff-eff-dense .wk {
        display: inline;
        margin-left: 5px;
        font-size: .68rem;
    }

    /* The member column is capped and ellipsized so one long display name cannot push the capture
       and weeks columns off the viewport. It is a pinned column, so the cap also fixes how much of
       the row the pinned block eats. */
    .ff-eff-season-table.ff-eff-dense td.namecol,
    .ff-eff-season-table.ff-eff-dense th.namecol { max-width: 108px; }
    .ff-eff-season-table.ff-eff-dense td.namecol > * {
        display: inline-block;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        vertical-align: middle;
        /* The tap target the tightened row must not cost: the anchor's own box, not the cell's. */
        padding: 4px 0;
    }
}
/* ---- end owner testing triage 2026-07-27 ---- */

/* ---- Season Readiness SR-B: the shared preseason / fallback note (plan §9) ----
   PreseasonNote reuses the ff-empty-state chrome, so this block only adds what the note needs on top
   of it: a readable measure and copy that WRAPS instead of truncating at 360px, which is the §6 SR-B
   acceptance line. No new panel type, no new colors, nothing that renders while the surrounding
   ff-empty-state is absent. Reused as-is by SR-C (Home / Welcome) and SR-D (the research fallback
   note), so keep it generic. */
.ff-season-note {
    /* Two comfortable lines at phone width rather than one long ellipsized one. */
    max-width: 46ch;
    margin: 0 auto;
    /* ff-empty-state centers its children; long sentences still need explicit wrapping because a
       school or member name inside the copy can be a single unbreakable token. */
    overflow-wrap: anywhere;
}

.ff-season-note .ff-season-note-link {
    /* Sits directly under the copy as its own tap target, at the 44px thumb-target guidance rather
       than MudButton Small's default height, because on a phone this is the note's only control. */
    margin-top: 4px;
    min-height: 44px;
}

@media (max-width: 599.98px) {
    .ff-season-note {
        max-width: 100%;
        /* ff-empty-state's 34px/20px is generous for a note that is the whole panel on a phone. */
        padding: 22px 14px;
    }
}
/* ---- end Season Readiness SR-B ---- */

/* ============================================================================================
   Season Readiness SR-D — the research fallback note (.ff-research-note).

   The line every research board shows while the picker is on the new season and the data is still
   last season's. It is a NOTE, not an alert: muted text, a small leading icon, and the same visual
   weight as .ff-ctx-note, because for the next month this is the normal state rather than a problem.

   360px: the icon is pinned (flex: 0 0 auto) and the sentence wraps under itself instead of
   truncating or pushing the row wider than the viewport, per the §9 breakpoint matrix.
   ============================================================================================ */

.ff-research-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 0 0 12px;
    font-size: .78rem;
    line-height: 1.4;
    color: var(--mud-palette-text-secondary);
}

.ff-research-note .mud-icon-root {
    flex: 0 0 auto;
    font-size: 1rem;
    margin-top: 1px;
}

.ff-research-note > span {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Inside a panel body the note sits under the head, so it carries the body's own gutter. */
.ff-panel-body > .ff-research-note { margin-bottom: 8px; }
/* ---- end Season Readiness SR-D ---- */

/* ---- DR-0: staleness + turn attention ---- */
/* The freshness affordance the Room already had, given to the Board (plan §9). It rides inside the
   existing .ff-toolbar flex row, so it wraps to a second line on phones instead of pushing the
   Year/Week selects off screen. */
.ff-draft-fresh {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.ff-draft-updated {
    font-size: .72rem;
    line-height: 1.2;
    white-space: nowrap;
    color: var(--mud-palette-text-secondary);
}

/* Below 400px the caption is noise next to a 120px select pair: the stale strip carries the signal
   and the Refresh icon button stays. */
@media (max-width: 399.98px) {
    .ff-draft-updated { display: none; }
}

/* The stale strip. Deliberately NOT an error alert: the board underneath still holds good data, and
   replacing it with an error would be a worse lie than the staleness itself. Full-width above the
   content on phones, an inline chip from 600px up. */
.ff-draft-stale {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 10px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--mud-palette-background-grey);
    color: var(--mud-palette-text-secondary);
    font-size: .78rem;
    line-height: 1.35;
}

.ff-draft-stale-txt {
    min-width: 0;
    flex: 1 1 auto;
}

@media (min-width: 600px) {
    .ff-draft-stale {
        width: auto;
        display: inline-flex;
    }
    .ff-draft-stale-txt { flex: 0 1 auto; }
}
/* ---- end DR-0 ---- */

/* ---- Season Readiness SR-F: the commissioner readiness checklist ---- */
/* Plan §9: at 360px the checklist is a VERTICAL LIST, one row per line, never a table. A table here
   would either overflow horizontally or truncate the detail sentence, and the detail sentence is the
   entire value of the row. The mark stays in its own fixed column so the labels line up; everything
   else is a single wrapping text column that grows downward instead of sideways. */

.ff-srf-score {
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

/* The shared panel head is a single non-wrapping row. On the narrowest phones let the "n of 7 ready"
   counter drop under the title rather than squeeze it, since neither is worth truncating. */
@media (max-width: 419.98px) {
    .ff-srf-head {
        flex-wrap: wrap;
        row-gap: 2px;
    }
}

.ff-srf-lede {
    margin: 0 0 10px;
    color: var(--mud-palette-text-secondary);
}

.ff-srf-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ff-srf-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 9px 10px;
    border-radius: 10px;
    border: 1px solid transparent;
    background: var(--mud-palette-background-grey);
}

/* Status is carried by the icon's color AND by a left border, so the panel does not depend on color
   alone to say "this one needs you" (the same belt-and-braces the draft stale strip uses). */
.ff-srf-row.is-action { border-left: 3px solid var(--mud-palette-error); }
.ff-srf-row.is-attention { border-left: 3px solid var(--mud-palette-warning); }
.ff-srf-row.is-ready { border-left: 3px solid var(--mud-palette-success); }

.ff-srf-mark {
    flex: 0 0 auto;
    margin-top: 1px;
}

.ff-srf-txt {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}

.ff-srf-label {
    font-size: .84rem;
    font-weight: 600;
    line-height: 1.3;
}

.ff-srf-detail,
.ff-srf-when,
.ff-srf-who {
    font-size: .76rem;
    line-height: 1.4;
    color: var(--mud-palette-text-secondary);
    /* Long conference lists and week lists must wrap, not push the panel wide. */
    overflow-wrap: anywhere;
}

.ff-srf-when { font-variant-numeric: tabular-nums; }

.ff-srf-link {
    font-size: .76rem;
    align-self: flex-start;
    /* A 32px-tall tap target on a phone without stretching the row on desktop. */
    padding: 3px 0;
}

/* From tablet up the rows can breathe: the detail sits on one line beside a wider label column. */
@media (min-width: 600px) {
    .ff-srf-row { padding: 10px 12px; gap: 12px; }
    .ff-srf-label { font-size: .88rem; }
}
/* ---- end Season Readiness SR-F ---- */

/* ============================================================================================
   T-30: ambient live-draft indicator (LiveDraftIndicator.razor). ONE appended block.

   Two placements off the same state: a compact pill in the app bar and a bare dot beside the Draft
   drawer link. Global rather than scoped because the nav dot is rendered INSIDE MudNavLink's anchor,
   which a scoped sheet cannot reach.

   Colour carries the meaning: a live draft is the info accent, and YOUR turn is the success accent
   and pulses (the pulse is reserved for the state that actually needs the member to act). Both accents
   come from the MudBlazor palette variables, so light mode and dark mode are correct by construction.

   Mobile: from 600px down the pill drops its label and becomes a dot-sized target, so it can never
   crowd the brand mark, the league switcher, or the user menu at 360px. The nav dot is inline and 8px
   and therefore cannot wrap the Draft link at any width.
   ============================================================================================ */

.ff-livedraft {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

.ff-livedraft-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--mud-palette-info);
    flex: none;
}

/* Your turn: the accent changes AND it pulses, so the two states are distinguishable without colour
   alone (the label and the tooltip carry the same distinction for anyone who cannot see it). */
.ff-livedraft.mine .ff-livedraft-dot {
    background: var(--mud-palette-success);
    animation: ff-pulse 1.4s ease-in-out infinite;
}

/* MudTooltip wraps the pill in its own inline-block root, which would sit on the toolbar's text
   baseline instead of its centre line. Make that wrapper a plain flex item so the pill is centred at
   both bar heights (the desktop bar is 72px, the phone bar is the MudBlazor default). */
.ff-appbar .mud-tooltip-root {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

/* --- app-bar pill --- */
.ff-livedraft-pill {
    gap: 6px;
    height: 30px;
    padding: 0 11px;
    margin-right: 4px;
    border-radius: 999px;
    text-decoration: none;
    border: 1px solid rgba(255, 255, 255, .28);
    background: rgba(255, 255, 255, .10);
    color: inherit;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    white-space: nowrap;
    transition: background .15s, border-color .15s;
}

.ff-livedraft-pill:hover {
    background: rgba(255, 255, 255, .18);
    text-decoration: none;
}

.ff-livedraft-pill.mine {
    border-color: var(--mud-palette-success);
    color: var(--mud-palette-success);
}

/* Phone: the label goes, the dot stays. A 30px round target still clears the tap minimum next to the
   bell, and the app bar keeps the layout the owner already signed off on. */
@media (max-width: 600px) {
    .ff-livedraft-pill {
        width: 30px;
        padding: 0;
        justify-content: center;
        margin-right: 0;
    }

    .ff-livedraft-pill .ff-livedraft-label {
        display: none;
    }
}

/* --- nav-drawer dot --- */
.ff-livedraft-navdot {
    margin-left: 8px;
    vertical-align: middle;
}
/* ---- end T-30 live-draft indicator ---- */

/* ============================================================================================
   LANDING BOOT STALL (2026-07-28) — scroll-reveal arming.

   Appended block, single owner. The Wave 2 reveal rules further up hide EVERY .ff-reveal element
   the moment html.ff-js-motion appears. public-motion.js is lazy-imported after PublicLayout's
   first render, so on a cold load that class arrived roughly 700ms AFTER the landing page had
   already painted (measured on a 4x-throttled main thread) and sections the visitor was looking at
   faded back out to opacity 0 before fading in again. That blink is the owner's "the bottom half of
   the page takes a few seconds to appear".

   Hiding is now opt-in per element: public-motion.js adds .ff-reveal--armed, and only ever to an
   element whose top edge is still below the fold, from a MutationObserver callback that runs before
   the next paint. This rule neutralises the pre-state for everything it has not armed. Specificity
   (0,3,0) beats the (0,2,0) rules above, so the Wave 2 block needs no edit, and the no-JS / crawler
   / prefers-reduced-motion contracts are untouched: without JS neither class is ever added and the
   markup stays authored visible.
   ============================================================================================ */

.ff-js-motion .ff-reveal:not(.ff-reveal--armed) {
    opacity: 1;
    transform: none;
    /* Only an armed element is ever promoted; unarmed ones never animate, so no layer for them. */
    will-change: auto;
}

/* Arming must SNAP, never fade out. The late init sweep arms whatever is still below the fold, and
   with the Wave 2 transition in force that read as a 0.6s fade to nothing just off-screen, which a
   visitor scrolling at that moment could catch. Transitions are governed by the after-change style,
   so dropping the transition only while armed-and-not-yet-revealed still leaves the reveal itself
   animating: adding .is-visible stops this rule matching and the Wave 2 transition takes over. */
.ff-js-motion .ff-reveal.ff-reveal--armed:not(.is-visible) {
    transition: none;
}
/* ---- end LANDING BOOT STALL ---- */

/* ============================================================================================
   33A: DRAFT CLOSED — the post-lock closing state in the draft room.

   The Complete branch keeps the inline button row it has always had (its rendered markup has to stay
   byte-identical to what shipped). The Closed branch is new, so it gets a class instead, and one rule
   the inline version never had: below 400px the two onward buttons stack to full width rather than
   wrapping to a lonely second row. 360px is the primary drafting width and "My Team" / "The Board" are
   the only actions left on a closed draft, so they are worth a full-width tap target each.
   ============================================================================================ */

.ff-draft-closed-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

@media (max-width: 399.98px) {
    .ff-draft-closed-actions {
        flex-direction: column;
        align-items: stretch;
    }
}
/* ---- end 33A draft closed ---- */

/* ============================================================================================
   33D: DRAFT RECAP — the post-draft surfaces shared by the room panel, the /draft/recap route,
   and the career page.

   These classes are SHARED between components, which is why they live here rather than in
   DraftRecap.razor.css: a scoped stylesheet only reaches the markup authored in its own file, and
   the grade card and the tendencies card are rendered from three different hosts.

   Two house rules are expressed in the CSS itself. Nothing here can make the page body scroll
   horizontally (the league board owns its own overflow container, in the page's scoped file), and
   every interactive target stays at or above 44px at 360px, which is the width members draft at.
   ============================================================================================ */

/* ---------------------------------------------------------------- the room panel ---- */

.ff-recap-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 14px;
    margin-bottom: 16px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    background: var(--mud-palette-surface);
}

.ff-recap-panel.pending {
    font-size: .85rem;
    opacity: .75;
}

.ff-recap-panel-head {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ff-recap-panel-head .ttl {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .75;
}

.ff-recap-panel-none {
    font-size: .84rem;
    opacity: .75;
}

/* A real link, sized as a tap target rather than a line of text with a hit box the height of its
   own font. 44px is the floor at every width, not only the small one. */
.ff-recap-panel-more {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-size: .82rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--mud-palette-primary);
}

.ff-recap-panel-more:hover {
    text-decoration: underline;
}

/* ----------------------------------------------------------------- the grade card ---- */

.ff-recap-grade {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* 360px stacks the letter above the number; 600px and up sets them side by side. The grade is the
   headline, so it never competes for width with the sentence explaining it. */
.ff-recap-grade-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

.ff-recap-grade-lines {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.ff-recap-letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 56px;
    min-height: 44px;
    padding: 4px 14px;
    border-radius: 10px;
    font-size: 1.5rem;
    font-weight: 800;
    line-height: 1;
    color: #fff;
    background: #64748b;
}

/* The board's inline form. Still a comfortable row target, just not a headline. */
.ff-recap-letter.sm {
    min-width: 40px;
    min-height: 28px;
    padding: 2px 8px;
    font-size: .9rem;
    border-radius: 6px;
}

/* Absolute bands, never curved, so a letter's colour is a property of the letter alone and two
   surfaces can never disagree about what a B looks like. */
.ff-recap-letter.great { background: #10b981; }
.ff-recap-letter.good  { background: #3b82f6; }
.ff-recap-letter.fair  { background: #f59e0b; }
.ff-recap-letter.poor  { background: #ef4444; }

.ff-recap-capture {
    font-size: .95rem;
    font-weight: 600;
    line-height: 1.35;
}

.ff-recap-rank {
    font-size: .82rem;
    opacity: .78;
}

.ff-recap-lens,
.ff-recap-unfilled,
.ff-recap-projected {
    font-size: .74rem;
    opacity: .65;
    line-height: 1.45;
}

.ff-recap-grade.compact .ff-recap-lens {
    font-size: .7rem;
}

/* The picks. One column at 360px, two from 960px: a five-slot roster reads as one glance on a
   phone and as a block on a desktop. */
.ff-recap-picks {
    display: grid;
    grid-template-columns: 1fr;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.ff-recap-pick {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 4px 8px;
    border-radius: 8px;
    background: var(--mud-palette-background-gray);
    min-width: 0;
}

.ff-recap-pick-name {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 600;
    font-size: .85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ff-recap-pick-team {
    flex: 0 1 auto;
    min-width: 0;
    font-size: .72rem;
    opacity: .65;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ff-recap-pick-value {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-left: auto;
}

.ff-recap-pick-value .rk {
    font-size: .66rem;
    opacity: .6;
}

.ff-recap-pick-value .vl {
    font-size: .85rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.ff-recap-gap,
.ff-recap-ai {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--mud-palette-background-gray);
}

.ff-recap-gap .k,
.ff-recap-ai .k {
    font-size: .64rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .6;
}

.ff-recap-gap .v,
.ff-recap-ai .v {
    font-size: .82rem;
    line-height: 1.45;
}

.ff-recap-forward {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-size: .8rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--mud-palette-primary);
}

.ff-recap-forward:hover {
    text-decoration: underline;
}

/* ------------------------------------------------------------ the tendencies card ---- */

.ff-recap-tend {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    background: var(--mud-palette-surface);
}

.ff-recap-tend.error {
    font-size: .82rem;
    opacity: .7;
}

.ff-recap-tend-head {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ff-recap-tend-head .ttl {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .75;
}

.ff-recap-tend-lead {
    font-size: .95rem;
    font-weight: 600;
    line-height: 1.35;
}

/* The first-timer line is a DESIGNED state, so it reads as a welcome and not as a shrunken error. */
.ff-recap-tend-first {
    font-size: .9rem;
    line-height: 1.45;
}

/* One tendency per row at 360px: these are sentences, and sentences do not sit side by side. */
.ff-recap-tend-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding-left: 18px;
    font-size: .82rem;
    line-height: 1.5;
}

.ff-recap-tend-skel {
    height: 14px;
    border-radius: 4px;
    background: var(--mud-palette-background-gray);
    opacity: .6;
}

.ff-recap-tend-skel:last-child {
    width: 62%;
}

/* --------------------------------------------------------------- 600px and wider ---- */

@media (min-width: 600px) {
    .ff-recap-grade-head {
        flex-direction: row;
        align-items: center;
        gap: 14px;
    }
}

@media (min-width: 960px) {
    .ff-recap-picks {
        grid-template-columns: 1fr 1fr;
    }
}
/* ---- end 33D draft recap ---- */

/* ============================================================================================
   34C: PRACTICE DRAFT ROOM (/mock).

   Two jobs. First, make practice unmistakable: an info-accent banner and a 2px info-accent left
   border on every practice card, so a screenshot pasted into the league chat can never be mistaken
   for the live room. Second, hold the §9 breakpoint matrix, which differs from the live board on
   purpose: the practice board drops the school at 360px (a practice pool has no matchup context to
   scan for) and the trend below 960px, where the live board keeps the school and shrinks instead.

   Everything here is prefixed ff-practice- and the board rows are scoped under .ff-practice-board,
   so the live draft room's own rules are untouched.
   ============================================================================================ */

/* --- the banner: a landmark, and the one element that renders in every state ----------------- */

.ff-practice-banner {
    border-left: 3px solid var(--mud-palette-info);
    background: rgba(33, 150, 243, .12);
}

/* Sticky from 600px so it stays on screen while the board scrolls. Deliberately STATIC at phone
   widths: a pinned bar there costs a fifth of the viewport members actually draft from. */
@media (min-width: 600px) {
    .ff-practice-banner {
        position: sticky;
        top: 0;
        z-index: 5;
    }
}

/* --- the practice card accent ---------------------------------------------------------------- */

.ff-practice-card {
    border-left: 2px solid var(--mud-palette-info);
}

/* --- setup panel ----------------------------------------------------------------------------- */

.ff-practice-setup-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
}

/* One column of full-width selects on a phone, with tap targets that survive a thumb. */
.ff-practice-setup-grid .mud-input-control {
    min-height: 44px;
}

.ff-practice-setup-hint {
    display: block;
    margin-top: 10px;
}

.ff-practice-setup-actions {
    margin-top: 16px;
}

@media (min-width: 600px) {
    .ff-practice-setup-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 960px) {
    .ff-practice-setup-grid {
        max-width: 640px;
        margin: 0 auto;
    }

    .ff-practice-setup-actions {
        max-width: 640px;
        margin-left: auto;
        margin-right: auto;
    }
}

/* --- on-the-clock header --------------------------------------------------------------------- */

.ff-practice-clockhead {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
}

.ff-practice-clockhead-who {
    flex: 1 1 200px;
    min-width: 0;
}

.ff-practice-clockhead-clock {
    flex: 0 0 auto;
    margin-left: auto;
}

/* 360px: the seat and the pick counter stack, and the clock drops below them. */
@media (max-width: 599.98px) {
    .ff-practice-clockhead {
        flex-direction: column;
    }

    .ff-practice-clockhead-clock {
        margin-left: 0;
    }
}

/* --- the selected pick ------------------------------------------------------------------------ */

/* "Draft <name> (practice)" is a long label by design, so let it wrap rather than overflow, and give
   it the full width on a phone where it is the only action that matters. */
.ff-practice-rec .mud-button-root {
    white-space: normal;
}

@media (max-width: 599.98px) {
    .ff-practice-rec {
        flex-direction: column;
        align-items: stretch;
    }

    .ff-practice-rec .mud-button-root {
        width: 100%;
    }
}

/* --- the board: head, filter chips, rows ------------------------------------------------------ */

.ff-practice-bahead {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* The #55 behaviour, unscoped (the live room's copy lives in a component-scoped stylesheet): the head
   may wrap to two ROWS, but the chip group itself never wraps. It scrolls horizontally as a last
   resort so DEF never drops to a lonely second line at 360px. */
.ff-practice-filters {
    display: flex;
    flex-wrap: nowrap;
    gap: 6px;
    margin-left: auto;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 1px;
}

.ff-practice-filters::-webkit-scrollbar {
    display: none;
}

.ff-practice-filters .mud-button-root {
    flex: 0 0 auto;
    min-width: 0;
    padding: 1px 11px;
}

/* One action per row (select), not the live board's two, so the action track is narrower. */
.ff-practice-board .ff-practice-row {
    grid-template-columns: 30px minmax(0, 1.6fr) 46px minmax(0, 1fr) 46px 52px 44px;
}

/* Below 960px the trend chip goes: it is the least load-bearing cell, and a practice pool is often
   off-season, where a weekly trend means very little. */
@media (max-width: 959.98px) {
    .ff-practice-board .ff-practice-row {
        grid-template-columns: 30px minmax(0, 1.6fr) 46px minmax(0, 1fr) 52px 44px;
    }

    .ff-practice-board .ff-practice-row .ff-draft-trend {
        display: none;
    }
}

/* 360px: tier, name, position, value, action. The school goes too, so a long name gets the room and
   the row never forces the page to scroll sideways. */
@media (max-width: 599.98px) {
    .ff-practice-board .ff-practice-row {
        grid-template-columns: 26px minmax(0, 1fr) 34px 46px 38px;
        gap: 6px;
    }

    .ff-practice-board .ff-practice-row .ff-draft-team {
        display: none;
    }
}

/* --- recent picks ----------------------------------------------------------------------------- */

.ff-practice-recent {
    margin-top: 16px;
}

.ff-practice-recent-list {
    display: flex;
    flex-direction: column;
}

.ff-practice-recent-row {
    display: grid;
    align-items: center;
    gap: 8px;
    grid-template-columns: 36px 48px minmax(0, 1fr) minmax(0, .8fr);
    padding: 4px 2px;
    font-size: .82rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    border-left: 2px solid transparent;
}

.ff-practice-recent-row:last-child {
    border-bottom: none;
}

.ff-practice-recent-row.mine {
    border-left-color: var(--mud-palette-success);
    background: rgba(76, 175, 80, .10);
}

.ff-practice-recent-no {
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}

.ff-practice-recent-name {
    font-weight: 600;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ff-practice-recent-seat {
    color: var(--mud-palette-text-secondary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 360px: three rows, no seat column. The trim is CSS so the markup is identical at every width. */
@media (max-width: 599.98px) {
    .ff-practice-recent-row {
        grid-template-columns: 32px 42px minmax(0, 1fr);
        font-size: .78rem;
    }

    .ff-practice-recent-seat {
        display: none;
    }

    .ff-practice-recent-list .ff-practice-recent-row:nth-child(n+4) {
        display: none;
    }
}

/* --- abandon and completion actions ------------------------------------------------------------ */

/* Outside the room card and below the board, so a mis-tap while scanning cannot end the session. */
.ff-practice-abandon {
    display: flex;
    justify-content: center;
    margin: 4px 0 24px;
}

.ff-practice-complete-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}

/* The stack is the LAST resort, not the phone default: at 399.98px it was firing at 360px, where the
   owner found two two-word buttons spending two rows of a phone. The compact sizing that holds them on
   one line lives in the practice-polish block at the end of this file. */
@media (max-width: 339.98px) {
    .ff-practice-complete-actions {
        flex-direction: column;
        align-items: stretch;
    }
}
/* ---- end 34C ---- */

/* ================================================================================================
   34D: PRACTICE CLOCK, AUTO-PICK, AND ABANDON.

   Appended after the 34C block rather than woven into it, so the two slices merge cleanly and a
   reviewer can see exactly what the clock pass added. Three small surfaces, and all three are phone
   cases: the note that says the clock picked for you, the confirmation in front of the one
   irreversible action on the page, and the on-the-clock header with a live countdown beside it.
   ================================================================================================ */

/* --- the "your clock ran out" note ------------------------------------------------------------ */

/* Two lines on purpose: the sentence names WHAT happened, the line under it names what was taken.
   A member reading this was looking somewhere else a moment ago, so neither half can be a glance. */
.ff-practice-autopick-head {
    font-weight: 600;
}

.ff-practice-autopick-detail {
    margin-top: 2px;
    font-size: .85rem;
    opacity: .92;
}

/* --- the abandon confirmation ----------------------------------------------------------------- */

/* 360px: stack the actions and reverse them, so the destructive button sits at the bottom where a
   thumb lands deliberately and "keep practicing" is not one mis-tap away from it. Full width so
   neither of them is a small target. */
@media (max-width: 599.98px) {
    .ff-practice-abandon-dialog .mud-dialog-actions {
        flex-direction: column-reverse;
        align-items: stretch;
        gap: 8px;
    }

    .ff-practice-abandon-dialog .mud-dialog-actions .mud-button-root {
        width: 100%;
        margin: 0;
    }
}

/* --- the header with a live countdown in it --------------------------------------------------- */

/* PickClock renders a chip once the deadline passes; MudBlazor's own chip margin would push it out
   of line with the seat text it sits beside. */
.ff-practice-clockhead-clock .mud-chip {
    margin: 0;
}

/* 360px: the clock is already stacked under the seat line by the 34C rule, so give it the full
   width there instead of letting a long countdown wrap against the card edge. */
@media (max-width: 599.98px) {
    .ff-practice-clockhead-clock {
        width: 100%;
    }
}
/* ---- end 34D ---- */

/* ================================================================================================
   34E: THE PRACTICE COMPLETION RECAP.

   Appended at the tail, after 34D, so the four practice slices merge cleanly and a reviewer can see
   exactly what the closing moment added.

   ONE FAMILY, TWO DENOMINATIONS. This deliberately borrows the 33D recap's visual language rather
   than inventing a parallel one: the same gray row plate, the same tabular-numeric value weight, the
   same tiny uppercase key over its value. The practice recap and the live recap are the same
   argument (what you took, against what was there), so they should not look like two products.

   THE ROW SHAPE IS THE §9 BREAKPOINT MATRIX, exactly. 360px stacks the pair vertically, your pick
   above and the best available below, because a side-by-side comparison in 160px of width is not a
   comparison. 600px puts them in two columns. 960px adds the delta as its own column: it is the
   pair's conclusion, and it only earns a column once there is room to read the pair beside it.
   ================================================================================================ */

.ff-practice-recap {
    margin-top: 16px;
}

/* --- the headline number, and why there is no grade beside it -------------------------------- */

/* The 33D house rule, carried across: the captured total never renders without the total it is
   measured against, so this line is a number a member can check rather than a claim. */
.ff-practice-recap-capture {
    font-size: .95rem;
    font-weight: 600;
    line-height: 1.35;
}

.ff-practice-recap-lens {
    margin-top: 2px;
    font-size: .74rem;
    opacity: .65;
    line-height: 1.45;
}

/* --- the per-pick rows ------------------------------------------------------------------------ */

.ff-practice-recap-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
}

/* 360px: one column. Slot line, then what you took, then what was still there. */
.ff-practice-recap-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: center;
    gap: 4px 10px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--mud-palette-background-gray);
    min-width: 0;
}

.ff-practice-recap-where {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.ff-practice-recap-where .rd {
    font-size: .7rem;
    opacity: .6;
    white-space: nowrap;
}

/* Your pick and the best that was there share one shape on purpose: the comparison is only honest if
   neither side is presented more prominently than the other. */
.ff-practice-recap-took,
.ff-practice-recap-best {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.ff-practice-recap-took .k,
.ff-practice-recap-best .k {
    flex: 0 0 auto;
    font-size: .62rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .6;
}

.ff-practice-recap-took .v,
.ff-practice-recap-best .v {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .85rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ff-practice-recap-took .tm {
    margin-left: 6px;
    font-size: .72rem;
    font-weight: 400;
    opacity: .65;
}

.ff-practice-recap-took .vl,
.ff-practice-recap-best .vl {
    flex: 0 0 auto;
    margin-left: auto;
    font-size: .85rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* --- the delta chip: 960px and up only (§9) --------------------------------------------------- */

.ff-practice-recap-delta {
    display: none;
}

.ff-practice-recap-delta .chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 2px 6px;
    border-radius: 6px;
    font-size: .7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
    line-height: 1.25;
}

/* Calm on purpose. Practice is exactly where leaving value on the board is supposed to happen, so
   the gap is stated, not scolded; only taking the top of the board earns a colour. */
.ff-practice-recap-delta .chip.top {
    background: rgba(76, 175, 80, .18);
    color: var(--mud-palette-success);
}

.ff-practice-recap-delta .chip.off,
.ff-practice-recap-delta .chip.none {
    background: rgba(158, 158, 158, .16);
    color: var(--mud-palette-text-secondary);
}

/* --- the "no summary came down" state --------------------------------------------------------- */

.ff-practice-recap-missing {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
}

/* --- 600px: the pair sits side by side -------------------------------------------------------- */

@media (min-width: 600px) {
    .ff-practice-recap-row {
        grid-template-columns: 118px minmax(0, 1fr) minmax(0, 1fr);
        gap: 6px 12px;
    }
}

/* --- 960px: and the delta gets its own column ------------------------------------------------- */

@media (min-width: 960px) {
    .ff-practice-recap-row {
        grid-template-columns: 126px minmax(0, 1fr) minmax(0, 1fr) 104px;
    }

    .ff-practice-recap-delta {
        display: block;
    }
}
/* ---- end 34E ---- */

/* ================================================================================================
   Audit 2026-07-29 (finding G8): the member's finished practice drafts, under the setup panel.

   One row per session: date and size on the left, the captured value pair on the right. The whole row
   is the link, because a date and a value pair are both things a thumb will aim at, and it carries a
   44px minimum height so the tap target is honest on a phone.

   THE SHAPE ITSELF MOVED to the practice-polish block at the end of this file, under the
   ff-practice-history-* names it should have had from the start. It shipped calling its rows
   ff-practice-recent-*, which the room's recent-PICKS list had already defined as a rigid four-track
   grid a few hundred lines above; the picks grid won, this row's anchor was laid out inside its 36px
   first track, and the captured value rendered one character per line on every width. Nothing is left
   here on purpose: the two lists must not share a selector again, and an empty block is a clearer
   record of that than a deleted one.
   ================================================================================================ */
/* ---- end audit 2026-07-29 ---- */

/* ================================================================================================
   PRACTICE POLISH (owner walk-through on a real phone, 2026-07-29).

   Appended at the tail so the four practice slices above stay readable as what each of them shipped.
   Four items, and three of them are the same phone.

   THE ROOT CAUSE BEHIND THE DEAD VERTICAL SPACE, because it is worth naming once. Two practice
   containers flip to `flex-direction: column` below 600px while their main child still carries
   `flex: 1 1 200px`, written back when the axis was horizontal. flex-basis is a MAIN-axis size, so the
   flip silently re-reads "200px wide" as "200px tall", and an auto-height column container has no free
   space to redistribute, so the child is laid out at exactly 200px whatever its content measures.
   Measured at 360px: the on-the-clock block rendered 200px tall around ~50px of text (about 150px of
   nothing between the pick counter and the clock line) and the selected-pick body rendered 200px tall
   around ~66px (about another 134px). That is the whole band the owner photographed, and because it is
   fixed geometry rather than anything data-driven it was there with and without picks made. The fix is
   to give the basis back to the content on the axis where a fixed one means nothing.
   ================================================================================================ */

/* --- P3: the dead band, and the rhythm underneath it ------------------------------------------ */

@media (max-width: 599.98px) {
    /* Both halves of the axis-flip above. The 600px-and-up rules keep their 200px basis, which is
       correct there: the axis is horizontal and it is what stops the clock crowding the seat line. */
    .ff-practice-clockhead-who {
        flex: 0 0 auto;
    }

    .ff-practice-rec .ff-draft-rec-body {
        flex: 0 0 auto;
    }

    /* With the false 200px gone, the header's own gap is all that is left between the seat line and the
       countdown, and 8px there is a gap between columns rather than between two lines of one thought. */
    .ff-practice-clockhead {
        gap: 2px;
    }

    /* Same argument inside the selected pick: 14px was the gap between the pick and its button while
       the two sat side by side, and it reads as a hole once they are stacked. */
    .ff-practice-rec {
        gap: 8px;
    }
}

/* --- P4: what the other seats did while you waited -------------------------------------------- */

/* A CHILD of .ff-practice-clockhead, so one element sits in the right place on both layouts: between
   the seat line and the countdown on a phone (where the header is a column, and the order rule below
   puts it there), and on its own full-width row under both from 600px (where it is a row).

   width:100% rather than a percentage flex-basis on purpose. It means the same thing on either axis,
   which is precisely the property the bug above did not have. */
.ff-practice-since {
    order: 0;
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    margin-top: 2px;
}

@media (max-width: 599.98px) {
    .ff-practice-clockhead-clock {
        order: 1;
    }
}

.ff-practice-since-lbl {
    font-size: .72rem;
    font-weight: 600;
    line-height: 1.3;
    opacity: .75;
}

/* ONE line, always. The chips scroll sideways as a last resort rather than wrapping, so the strip costs
   the room exactly two lines at 360px and can never push the member's own pick card below the fold,
   which is the one thing it must not do. Same behaviour as the board's filter chips further up. */
.ff-practice-since-chips {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 6px;
    margin-top: 3px;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 1px;
}

.ff-practice-since-chips::-webkit-scrollbar {
    display: none;
}

.ff-practice-since-chip {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 5px;
    min-width: 0;
    padding: 2px 8px 2px 6px;
    border-radius: 999px;
    background: var(--mud-palette-background-gray);
    font-size: .7rem;
    line-height: 1.4;
}

.ff-practice-since-chip .st {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    opacity: .6;
}

/* The shared slot chip at its natural size: .ff-pos was written to fill a fixed grid track, and this is
   a pill. */
.ff-practice-since-chip .ff-pos {
    flex: 0 0 auto;
    width: auto;
    min-width: 24px;
    padding: 1px 5px;
    font-size: .56rem;
    border-radius: 5px;
}

.ff-practice-since-chip .nm {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 16ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.ff-practice-since-more {
    flex: 0 0 auto;
    align-self: center;
    font-size: .68rem;
    font-variant-numeric: tabular-nums;
    opacity: .6;
    white-space: nowrap;
}

/* --- P4, the other half: the list below counts the same story --------------------------------- */

/* The strip says what happened since your last turn; this says how much has happened at all. A member
   who scrolls down to check the strip should find the two agreeing rather than a second, shorter tale. */
.ff-practice-recent-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 4px;
}

.ff-practice-recent-total {
    flex: 0 0 auto;
    font-size: .72rem;
    font-variant-numeric: tabular-nums;
    opacity: .65;
    white-space: nowrap;
}

/* --- P1: the finished-sessions list, in its own class family ---------------------------------- */

/* Moved here from the G8 block above, which named these rows ff-practice-recent-* and so handed them to
   the room's recent-PICKS grid: the anchor landed in that grid's 36px first track and "140.8 of 141.1"
   wrapped one character per line, on desktop and phone alike. The picks grid is correct for its own
   component and is untouched.

   Deliberately flex rather than a grid with breakpoints. The date and the size are one group that goes
   to a second line only when it genuinely will not fit, and the value keeps its own width and its place
   at the right on every width, with no media query deciding for it. */
.ff-practice-history-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 8px 0 0;
    padding: 0;
    list-style: none;
}

.ff-practice-history-row {
    min-width: 0;
}

.ff-practice-history-link {
    display: flex;
    align-items: center;
    gap: 4px 12px;
    min-height: 44px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--mud-palette-background-gray);
    color: inherit;
    text-decoration: none;
    min-width: 0;
}

.ff-practice-history-link:hover,
.ff-practice-history-link:focus-visible {
    background: var(--mud-palette-action-default-hover);
}

.ff-practice-history-meta {
    display: flex;
    flex: 1 1 auto;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 1px 10px;
    min-width: 0;
}

.ff-practice-history-meta .wh {
    font-weight: 600;
    white-space: nowrap;
}

.ff-practice-history-meta .wt {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .72rem;
    opacity: .65;
}

/* The one hard rule of this row: the captured value and the total it is measured against are a single
   number, and it does not break on any width. */
.ff-practice-history-link .vl {
    flex: 0 0 auto;
    margin-left: auto;
    font-size: .8rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    opacity: .85;
}

/* --- P2: both ways onward at 360px (CORRECTED, owner testing round 2026-07-29) ----------------- */

/* THIS BLOCK PREVIOUSLY TRIED TO HOLD THE TWO ACTIONS ON ONE ROW AND COULD NOT. Kept as a correction
   in place rather than as a fresh override, because the reasoning is what was wrong, not the selector.

   The measurement that justified the compact chrome ("115px + 8px + 160px = 283px") was wrong by about
   139px: it counted the label text only. Re-measured in a browser at a true 360px viewport, against the
   real MudBlazor 8 button rules and the app's own Roboto:

     content box of the card         294px   (360 - 32 pa-4 - 2 card border - 32 card-content padding)
     "Practice again"                172.6px
     "Back to the draft room"        241.8px
     + the 8px gap                   422.4px total, i.e. 128px MORE than there is

   The omissions were MudBlazor's own button typography (text-transform: uppercase and the .02857em
   letter-spacing both come from --mud-typography-button-*, so the label is measured in caps, not in
   the sentence case the markup is written in) and the start-icon box (.mud-button-icon-start is a 16.8px
   glyph plus 8px margin-inline-end less 4px margin-inline-start, so 20.8px a button).

   AND THE COMPACT SIZING COULD NEVER HAVE WORKED ANYWAY, whatever the numbers were. The container is
   flex-wrap: wrap, and flexbox assigns items to lines from their HYPOTHETICAL main sizes before any
   flex-shrink is resolved (CSS Flexbox 9.3 line breaking, then 9.7 per line). So flex: 0 1 auto shrinks
   an item only against the other items that landed on its own line: once the pair is too wide it is
   already on two lines, and every item is then alone on a line with nothing to shrink against. That is
   exactly what shipped, and it is what the owner kept photographing: not the <340px stack rule (which
   does not match at 360px) but a WRAP, rendering two ragged left-aligned rows of unequal width (145.8px
   and 207.7px) in shrunken .78rem type.

   The decisive number: the wrap already cost 96px of height. A deliberate stack costs 96px too. The
   compact chrome bought exactly zero vertical space; it only made the two rows look accidental and the
   labels and tap targets smaller. So the honest fix is to stop pretending and stack them properly,
   full width and at the sizing every other button in the app has. It is also robust in the way the old
   rule was not: it holds at 320px, at any label length, and under any font fallback.

   (One row is not reachable at 360px by any means worth having. Even stripping BOTH icons AND the
   uppercase AND cutting the padding to 8px measures 273px, leaving 21px of slack on the exact current
   strings, and it costs the buttons their icons and the house Material look to get there. Shortening
   the secondary label does not rescue it either: "Live draft", at ten characters, still measures 314.6px
   against the 294px box. Two icon-bearing Material buttons simply do not fit a 294px row.)

   This supersedes the <340px column rule in the 34C block above, which is now merely a narrower case of
   the same thing and is left in place as-is. */
@media (max-width: 599.98px) {
    .ff-practice-complete-actions {
        flex-direction: column;
        align-items: stretch;
    }

    /* No shrink and no squeeze: each button owns the full 294px row, keeps the app's standard button
       type and padding, and clears the 44px thumb target on its own. */
    .ff-practice-complete-actions .mud-button-root {
        flex: 0 0 auto;
        min-height: 44px;
    }
}
/* ---- end practice polish ---- */

/* ================================================================================================
   ROUND FIXES: PRACTICE ROOM (owner testing round on the prod-restored dev database, 2026-07-29).

   Appended at the tail so the polish block above still reads as what that round shipped. Three items
   here (the fourth, the completion actions, is a correction inside the block above because it is that
   block's own reasoning that was wrong). Every number quoted below was measured in a browser at a real
   360px viewport against the app's own Roboto and the real MudBlazor 8.15 rules, not estimated.
   ================================================================================================ */

/* --- A1: the since-picks chip is anchored on the overall pick number --------------------------- */

/* Leading and the only bold thing in the chip. The seat beside it keeps the dimmed .st treatment it
   already had, so the pair reads as "#23" first and "which seat" second at a glance, which is the order
   the questions actually arrive in. Tabular figures so a scrolling run of chips does not jitter. */
.ff-practice-since-chip .pk {
    flex: 0 0 auto;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The chip now carries four things instead of three, and it lives on a line that scrolls rather than
   wraps, so the two number tokens give some of their room back to the name. */
.ff-practice-since-chip {
    gap: 4px;
}

/* --- A2: the auto-pick alert, as small as it can honestly be at 360px -------------------------- */

/* TWO LINES, AND IT TOOK BOTH A TRIM AND A REWRITE. Measured as shipped at 360px the alert was FOUR
   text lines and 102px tall: the MudAlert gives the message a 228px box (294px card, less 16px+16px
   alert padding, less a 22px icon and its 12px margin), and BOTH sentences overflowed it.

   The width this block recovers is 46px, all of it: 34px of icon gutter plus 6px a side of alert
   padding, taking the message box to 274px. That was enough for the second sentence (measured: 4 of 5
   sample "We took X for your Y slot." lines now hold one line, against 1 of 5 at 228px) and nowhere
   near enough for the first, which read "Your practice pick clock ran out, so we picked for you." and
   needed 388px. No trim reaches 388px, and shrinking type to close a 114px gap would land near 11px on
   the one alert in the app that exists to be read by somebody who was looking away.

   So the sentence itself was shortened, in PracticeDraftService.ClockRanOutMessage and its identical
   client fallback in PracticeRoomPanel: "Your practice clock ran out." at 197.7px, which is one line in
   the 274px box at 360px and still one line in the 234px box at 320px. The clause that went is the one
   the second line already states concretely, so the alert lost redundancy rather than information.
   Result: 102px and four lines become 60px and two.

   The icon is the biggest single lever (34px of the 46px) and the one worth spending: the warning
   severity band and the bold first sentence both still say "something happened while you were away".
   Scoped to .ff-practice-autopick so the room's other alerts keep the stock chrome. */
@media (max-width: 599.98px) {
    .ff-practice-autopick {
        padding-left: 10px;
        padding-right: 10px;
    }

    .ff-practice-autopick .mud-alert-icon {
        display: none;
    }

    .ff-practice-autopick .mud-alert-message {
        padding-top: 4px;
        padding-bottom: 4px;
    }

    /* Two sentences of one thought, so they sit closer than the stock 1.5 leading puts them. */
    .ff-practice-autopick .ff-practice-autopick-head {
        line-height: 1.28;
    }

    .ff-practice-autopick .ff-practice-autopick-detail {
        line-height: 1.35;
    }
}

/* --- A3: the whole pick history, on request ---------------------------------------------------- */

/* Expanded, the list defeats the phone trim (the 34C rule hides every row past the third below 600px)
   and becomes a bounded scroll region instead. 260px is about six rows on a phone and seven on a
   desktop: enough that the reveal obviously revealed something, capped so a 40-pick draft cannot push
   the board and the abandon control off the bottom of the room. */
.ff-practice-recent-list.all {
    max-height: 260px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

@media (max-width: 599.98px) {
    .ff-practice-recent-list.all .ff-practice-recent-row:nth-child(n+4) {
        display: grid;
    }
}

.ff-practice-recent-more {
    display: flex;
    justify-content: center;
    margin-top: 2px;
}

/* The session is short enough that only the phone trim is hiding anything, so the control is real on a
   phone and would be a button that does nothing from 600px up. */
@media (min-width: 600px) {
    .ff-practice-recent-more.phone-only {
        display: none;
    }
}
/* ---- end round fixes: practice room ---- */

/* ================================================================================================
   TESTING ROUND 2026-07-29, SLICE B - the live room and the recap surfaces.

   Appended at the tail in its own block, so the slices above stay readable as what each of them
   shipped and a parallel slice's block can land beside this one without a merge conflict.

   Four items: the Buzz grid gains a fifth card, the on-the-clock banner gains a pick counter, the room
   gains a recent-picks strip, and the dashboard gains a draft-grade teaser. Every selector here is new;
   nothing above is redefined. (The 2026-07-29 audit note a few hundred lines up is the reason for the
   ff-room-* prefix on the strip: the practice room's ff-practice-recent-* grid is a rigid four-track
   layout, and the two lists must never share a selector again.)
   ================================================================================================ */

/* ---- B1: League Buzz gains the draft-recap card ------------------------------------------------
   The base .ff-buzz-cols grid is 3-across from 961px. A fourth or fifth card would strand a lone card
   on a second row, so a week carrying an extra card drops the grid to a clean two columns, exactly as
   the Pool Spotlight modifier already does. The modifier only lands when the card is actually present,
   so a deploy with Features:PostDraftRecapEnabled dark keeps today's exact three-across layout. Mobile
   is untouched: .ff-cols already stacks to one column at 960px and below. */
@media (min-width: 961px) {
    .ff-buzz-cols.has-draft-recap { grid-template-columns: 1fr 1fr; }
}

/* ---- B3a: the on-the-clock banner's pick counter -----------------------------------------------
   The counter is a caption UNDER the name rather than a parenthetical after it, because at 360px the
   old "(Pick #2)" pushed a long display name onto a second line inside an h6. min-width:0 is what lets
   the name ellipsize inside the flex row instead of forcing the alert wider than the card. */
.ff-draft-onclock {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* ---- B3b: the room's recent-picks strip --------------------------------------------------------
   Four tracks: slot chip, name, school, owner. The name is the only elastic one, so a long school or a
   long display name can never squeeze it out of the row. */
.ff-room-recent {
    margin: 0 0 16px;
}

.ff-room-recent-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 2px;
}

.ff-room-recent-total {
    font-size: .72rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.ff-room-recent-list {
    display: flex;
    flex-direction: column;
}

.ff-room-recent-row {
    display: grid;
    align-items: center;
    gap: 8px;
    grid-template-columns: 48px minmax(0, 1fr) minmax(0, .7fr) minmax(0, .7fr);
    padding: 4px 2px;
    font-size: .82rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.ff-room-recent-row:last-child {
    border-bottom: none;
}

.ff-room-recent-name {
    font-weight: 600;
}

.ff-room-recent-team,
.ff-room-recent-by {
    color: var(--mud-palette-text-secondary);
}

.ff-room-recent-name,
.ff-room-recent-team,
.ff-room-recent-by {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 360px: three rows, and the school goes rather than the owner. WHO took it is the point of the strip
   (a run is made of who is taking what), and the school is already on every board row below it. The
   trim is CSS, so the markup is identical at every width. */
@media (max-width: 599.98px) {
    .ff-room-recent-row {
        grid-template-columns: 42px minmax(0, 1fr) minmax(0, .8fr);
        font-size: .78rem;
    }

    .ff-room-recent-team {
        display: none;
    }

    .ff-room-recent-list .ff-room-recent-row:nth-child(n+4) {
        display: none;
    }
}

/* ---- B5: the dashboard's draft-grade teaser ----------------------------------------------------
   The letter and the capture line sit on one row from 600px and stack at 360px, where a 56px badge
   plus a full sentence does not fit beside itself. The sentence carries its own denominator, which is
   the one rule every grade surface keeps. */
.ff-home-grade {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.ff-home-grade-line {
    min-width: 0;
    flex: 1 1 220px;
    font-size: .9rem;
    font-weight: 600;
    line-height: 1.35;
}
/* ---- end testing round slice B ---- */

/* ================================================================================================
   New-league polish (2026-07-29): the post-activation league page at /leagues/{id}
   ------------------------------------------------------------------------------------------------
   The "Get your league rolling" panel is the first screen a PAYING commissioner sees after checkout,
   so it is styled as a designed step sequence: numbered markers threaded on a connecting rail, a
   primary-tinted card that reads as a moment rather than another outlined tile, and three FILLED
   action buttons whose hierarchy comes from colour (one primary, two neutral) instead of from mixing
   variants.

   WHY THIS LIVES IN app.css AND NOT IN A SCOPED CommissionerChecklist.razor.css: Blazor CSS isolation
   only stamps its b-<hash> scope attribute on plain HTML elements written in the .razor file. Child
   COMPONENTS do not receive it, so a scoped rule can never reach the element MudCard, MudText or
   MudButton renders. The component previously carried a scoped file in which every rule aimed at a Mud
   component (the card margin, the 44px CTA tap target, the marker, the done-state title) silently did
   nothing. That file was deleted and its intent rebuilt here, where the selectors actually match.
   app.css is the last stylesheet loaded (after MudBlazor.min.css and the scoped bundle), so these
   equal-specificity rules win on cascade order without needing !important.
   ================================================================================================ */

.ff-checklist {
    /* The hero above contributes its own 22px bottom margin, so only the gap DOWN to the Roster /
       Season cards is set: a little wider than the 16px grid gutter, so the onboarding panel reads as
       its own moment instead of merging into the rhythm of the standing info cards. */
    margin: 0 0 24px;
    border-color: var(--mud-palette-primary);
    /* Fades out above the buttons, so the card is tinted at the top without washing the CTAs. */
    background-image: linear-gradient(180deg,
        rgba(var(--mud-palette-primary-rgb), .08),
        rgba(var(--mud-palette-primary-rgb), 0) 190px);
}

.ff-checklist-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 20px;
}

.ff-checklist-eyebrow {
    color: var(--mud-palette-primary);
    font-weight: 700;
    letter-spacing: .09em;
}

.ff-checklist-title {
    font-weight: 700;
    letter-spacing: -.01em;
}

.ff-checklist-lede {
    color: var(--mud-palette-text-secondary);
    max-width: 52ch;
}

.ff-checklist-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 22px;
}

.ff-checklist-step {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 14px;
}

/* The rail that turns three rows into one sequence. It runs from just below a marker to the top of
   the next one, so it never pokes out past the final step. */
.ff-checklist-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 14px;
    top: 34px;
    bottom: -26px;
    width: 2px;
    border-radius: 1px;
    background: var(--mud-palette-lines-default);
}

.ff-checklist-mark {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    box-sizing: border-box;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--mud-palette-primary);
    /* Opaque, so the rail passes behind the marker rather than through it. */
    background: var(--mud-palette-surface);
    color: var(--mud-palette-primary);
    font-size: .88rem;
    font-weight: 700;
    line-height: 1;
}

/* "Members joined" is the only step that can prove its own completion, so it is the only marker that
   swaps its number for a check. Steps that have no completion signal keep their number rather than
   showing a checkbox that never ticks. */
.ff-checklist-step.done .ff-checklist-mark {
    background: var(--mud-palette-success);
    border-color: var(--mud-palette-success);
    color: #fff;
}

.ff-checklist-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    min-width: 0;
    /* Optically centres the title against the 30px marker. */
    padding-top: 2px;
}

.ff-checklist-step-title {
    font-weight: 600;
    line-height: 1.3;
}

.ff-checklist-step-sub {
    color: var(--mud-palette-text-secondary);
    max-width: 52ch;
}

.ff-checklist-step.done .ff-checklist-step-title {
    opacity: .85;
}

.ff-checklist-cta {
    align-self: flex-start;
    margin-top: 9px;
    min-height: 44px;
    box-shadow: none;
}

.ff-checklist-cta:hover {
    box-shadow: none;
}

/* Steps 2 and 3 stay FILLED (the ask was buttons, not links) and drop one level of emphasis through
   colour alone. MudBlazor's default-coloured filled button is already a neutral tonal fill; the border
   is what keeps it reading as a deliberate button against the tinted card, in both themes. */
.ff-checklist-cta-quiet {
    border: 1px solid var(--mud-palette-lines-default);
}

/* 360px: each step ends in one confident full-width thumb target, and the marker column tightens to
   buy back horizontal room for the copy. */
@media (max-width: 599.98px) {
    .ff-checklist-steps { gap: 20px; }
    .ff-checklist-step { gap: 12px; }

    .ff-checklist-cta {
        align-self: stretch;
        width: 100%;
    }
}

/* ---- the open invite code, below the panel ----
   The code is 16 characters (Guid "N" slice). Measured at 360px: at Typo.h5 with .14em tracking it
   renders 264px wide inside the 295px of content width a phone actually has after MudContainer and
   MudCard padding. It fits, barely, at 90% of the row: COPY is squeezed off to its own line anyway and
   the code crowds both card edges. Below 600px the code steps down to ~168px, is allowed to wrap
   rather than overflow if a future code is longer, and COPY takes a deliberate full-width 44px row
   instead of an accidental one. */
@media (max-width: 599.98px) {
    .ff-invite-row2 { gap: 10px; }

    .ff-invite-card .ff-invite-code {
        font-size: 1.05rem;
        letter-spacing: .08em;
        overflow-wrap: anywhere;
        min-width: 0;
    }

    .ff-invite-card .ff-invite-row2 .mud-button-root {
        width: 100%;
        min-height: 44px;
    }
}
/* ---- end new-league polish ---- */

/* ================================================================================================
   ConfirmDangerDialog: the one destructive-action confirmation
   ------------------------------------------------------------------------------------------------
   Appended as its own block so parallel slices merge cleanly. Every selector here keys off a class
   the component renders itself, never MudBlazor's .mud-dialog-* internals: the dialog is hosted
   through MudDialogProvider, so styling its host's classes would be betting on where MudBlazor
   chooses to put them.

   Supersedes the 34D .ff-practice-abandon-dialog rule further up this file. That rule is now dead
   (MockDraft confirms through this component instead and no longer emits the class); it is left in
   place rather than deleted from the middle of the file so concurrent branches do not conflict.
   ================================================================================================ */

/* The warning icon and the title read as one line, and the icon never shrinks the title's measure. */
.ff-danger-dialog-head {
    display: flex;
    align-items: center;
    gap: 10px;
}

.ff-danger-dialog-head .mud-icon-root {
    flex: 0 0 auto;
}

/* The question is the loud part; the consequence is a quieter second line under it rather than a
   run-on sentence, so "what am I about to do" and "what else goes with it" are separately scannable. */
.ff-danger-dialog-consequence {
    display: block;
    margin-top: 8px;
    opacity: .85;
}

/* Desktop: Cancel left, the destructive button right, both at the trailing edge. The wrapper carries
   the layout (not .mud-dialog-actions) and spans the row so the phone rules below have something to
   stretch against. */
.ff-danger-dialog-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    width: 100%;
}

/* 360px: one full-width button per row, and NOT reversed. DOM order is Cancel then Confirm, so a
   plain column leaves the destructive button bottom-most, furthest from where a thumb already sits
   after tapping whatever opened this. 44px keeps both of them real touch targets. */
@media (max-width: 599.98px) {
    .ff-danger-dialog-actions {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
    }

    .ff-danger-dialog-actions .mud-button-root {
        width: 100%;
        margin: 0;
        min-height: 44px;
    }
}
/* ---- end ConfirmDangerDialog ---- */

/* ---- PD-20 (2026-07-30): league hero action row at phone width ----
   At a 360px viewport the action row measures 260px (360 minus the pa-4 shell, the MudContainer
   gutters, and the hero's own 18px phone padding). The rule at line 788 gives all three buttons
   flex:1, so they split into 81.3px thirds, which leaves a ~49px label box. "MEMBERS" and
   "SETTINGS" are single words and simply overflow it, but "ENTER LEAGUE" has a space to break on,
   so its label wraps to two lines and that one button renders 61px tall against its neighbours'
   36.5px. nowrap on its own would only convert the wrap into an overflow. So the two outlined
   controls keep the shared row (126px each), and the primary action takes a full-width row of its
   own (260px), which is also the prominence it should have. All three then measure 36.5px tall and
   nothing overflows. Enter League is already last in source order in LeagueOverview.razor, so a
   100% flex-basis wraps it down with no markup reordering.
   Specificity: the .mud-button-root rule ties with the line-788 rule at (0,3,0) and wins on source
   order; the .ff-league-enter rule is a compound (0,4,0) and wins outright, so neither depends on
   the order of the two lines inside this block. Every selector is prefixed .ff-league-hero, so the
   non-hero .ff-league-actions uses (line 609) are untouched. Plain members render one button, where
   the block is a no-op that still yields a full-width primary. */
@media (max-width: 599.98px) {
    .ff-league-hero .ff-league-actions {
        flex-wrap: wrap;
    }

    .ff-league-hero .ff-league-actions .mud-button-root {
        flex: 1 1 0;
        white-space: nowrap;
    }

    .ff-league-hero .ff-league-actions .mud-button-root.ff-league-enter {
        flex: 1 0 100%;
    }
}
/* ---- end PD-20 league hero action row ---- */

/* =========================================================================================   36B: the practice reveal
   ------------------------------------------------------------------------------------------------
   Appended as its own block at the tail so parallel slices merge cleanly (36C appends below this
   one). Everything here is scoped to a class PracticeRoomPanel / PracticeSincePicks renders itself,
   so nothing in it can reach the live draft room, which reuses PickClock and the ff-draft-* row
   furniture unmodified.

   Three things, and only three: the seat that is about to pick, the chip that lands when it does,
   and the control that ends the show. There is deliberately NO spinner and no progress bar (Q11):
   the pick is already made and stored server-side, so a loading indicator would be a lie. The seat
   name plus a chip that appears is the same information without the false claim, and it survives
   reduced motion and a screen reader, neither of which a spinner does.
   ================================================================================================ */

/* The seat on the clock while the room is still catching up. The INFO accent, because that is
   practice's whole visual identity in this app (the banner and the room card's 2px left border are
   both info), and because the one thing this surface must never do is borrow the live product's
   primary orange. A colour change only: the sentence itself already says who is picking, so a member
   with a monochrome display or a screen reader loses nothing. */
.ff-practice-pending {
    color: var(--mud-palette-info);
}

/* One beat, one chip. The strip renders newest-first and the chips are keyed on the pick number, so
   the arriving element is genuinely new and genuinely at the head of the line; a short slide in from
   the right is then the whole animation, and it reads as "this just happened" rather than as motion
   for its own sake. */
.ff-practice-since-chip {
    animation: ff-practice-chip-in 220ms ease-out both;
}

@keyframes ff-practice-chip-in {
    from {
        opacity: 0;
        transform: translateX(8px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* Reduced motion: the chips simply appear. The reveal's information is the text, not the movement,
   so removing the movement removes nothing. */
@media (prefers-reduced-motion: reduce) {
    .ff-practice-since-chip {
        animation: none;
    }
}

/* "Skip to my pick" stands in for the draft button until the reveal ends, in its own row rather than
   inside the recommendation card, so it exists in the states that card does not render. Trailing edge
   from 600px, where the draft button also sits at the right of the card. */
.ff-practice-skiprow {
    display: flex;
    align-items: center;
    justify-content: flex-end;
}

.ff-practice-skiprow .mud-button-root {
    min-height: 44px;
}

/* 360px: full width and a real 44px touch target, matching the draft button it replaces (the
   .ff-practice-rec phone rule further up this file does the same for that one). */
@media (max-width: 599.98px) {
    .ff-practice-skiprow {
        align-items: stretch;
    }

    .ff-practice-skiprow .mud-button-root {
        width: 100%;
        margin: 0;
    }
}
/* ---- end 36B ---- */

/* ---- 36C: practice setup ---- */
/* ================================================================================================
   Phase 36C (PD-6): the practice setup surface reads as an invitation rather than a settings form.

   Appended as one clearly-commented block at the tail so the parallel 36B slice (the reveal in the
   room) merges cleanly beside it; neither block names a selector the other owns.

   Three rules run through everything here:
     - The accent is INFO, never the app's primary orange. .ff-league-hero owns the orange gradient
       and it is the loudest "this is your real league" signal in the product; practice must never be
       mistakable for the live room (34C section 5.8), so the hero borrows that structure and none of
       its palette.
     - No horizontal PAGE scroll at 360px. The three chip rows scroll inside themselves, which is what
       lets the twelve-seat "your seat" row show its whole decision space without wrapping.
     - Every tap target is at least 44px.

   The 34C .ff-practice-setup-grid rules further up this file are now dead (the four MudSelects they
   laid out are gone). They are left in place rather than deleted from the middle of the file, the same
   way the 34D abandon-dialog rule was, so a concurrent branch does not have to resolve a conflict in a
   region neither slice is really editing. .ff-practice-setup-hint and .ff-practice-setup-actions are
   still live and still used.
   ================================================================================================ */

/* --- the hero: replaces the page head in the setup state only --------------------------------- */

.ff-practice-hero {
    border-radius: 16px;
    padding: 22px 24px;
    color: #fff;
    background: linear-gradient(135deg, var(--mud-palette-info), var(--mud-palette-info-darken, #1976D2));
}

/* The gradient runs the full width like every other hero; only the TEXT is capped, so it lines up with
   the 640px column the setup card's own contents take at desktop widths. */
.ff-practice-hero-inner {
    min-width: 0;
}

.ff-practice-hero-eyebrow {
    display: block;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .85);
}

.ff-practice-hero-title {
    margin: 4px 0 0;
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -.01em;
    color: #fff;
}

.ff-practice-hero-sell {
    margin: 8px 0 0;
    max-width: 52ch;
    font-size: .95rem;
    color: rgba(255, 255, 255, .9);
}

/* The hook and its link sit on one row and wrap to two on a narrow phone, rather than the link being
   dragged onto a line of its own by a number that happens to be long. */
.ff-practice-hero-hook {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 12px;
    margin: 14px 0 0;
    font-size: .95rem;
}

.ff-practice-hero-hook .vl {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.ff-practice-hero-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: #fff;
    font-weight: 600;
    text-decoration: underline;
}

@media (max-width: 599.98px) {
    .ff-practice-hero {
        padding: 18px;
    }

    .ff-practice-hero-title {
        font-size: 1.45rem;
    }
}

@media (min-width: 960px) {
    .ff-practice-hero-inner {
        max-width: 640px;
        margin: 0 auto;
    }
}

/* --- the four choices ------------------------------------------------------------------------- */

.ff-practice-choices {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.ff-practice-choice {
    min-width: 0;
}

.ff-practice-choice-label {
    display: block;
    margin-bottom: 6px;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

/* The board's own filter-row behaviour (.ff-practice-filters), reused for a decision rather than a
   filter: one line, never wrapping, scrolling horizontally as a last resort. Twelve seats is thirteen
   chips, and a wrap there would push the card taller than the viewport on a phone. */
.ff-practice-chiprow {
    display: flex;
    flex-wrap: nowrap;
    gap: 6px;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
}

.ff-practice-chiprow::-webkit-scrollbar {
    display: none;
}

.ff-practice-chiprow .mud-button-root {
    flex: 0 0 auto;
    min-width: 44px;
    min-height: 44px;
    padding: 4px 12px;
    white-space: nowrap;
}

/* The pool is the one choice that changes what you are practising, so it is the one that gets words.
   Stacked full width on a phone, two columns from 600px. */
.ff-practice-pool {
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
}

.ff-practice-pooltile {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 100%;
    min-height: 44px;
    padding: 12px 14px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    background: var(--mud-palette-background-gray);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.ff-practice-pooltile:hover:not(:disabled),
.ff-practice-pooltile:focus-visible {
    border-color: var(--mud-palette-info);
}

/* Selected is the info accent again, and the padding compensates for the thicker border so choosing a
   tile cannot shift the one beside it. */
.ff-practice-pooltile.sel {
    border: 2px solid var(--mud-palette-info);
    padding: 11px 13px;
    background: rgba(33, 150, 243, .12);
}

.ff-practice-pooltile:disabled {
    opacity: .6;
    cursor: default;
}

.ff-practice-pooltile .t {
    font-weight: 700;
    font-size: .95rem;
}

.ff-practice-pooltile .d {
    font-size: .78rem;
    opacity: .75;
}

@media (min-width: 600px) {
    .ff-practice-pool {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* --- what a practice rep gives you ------------------------------------------------------------ */

.ff-practice-rep-strip {
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
}

.ff-practice-rep-strip li {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
    font-size: .85rem;
}

.ff-practice-rep-strip .mud-icon-root {
    flex: 0 0 auto;
    color: var(--mud-palette-info);
}

@media (min-width: 600px) {
    .ff-practice-rep-strip {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* Desktop: the choices and the rep strip take the same 640px column the setup grid always did, so the
   card contents stay one measure instead of three. */
@media (min-width: 960px) {
    .ff-practice-choices,
    .ff-practice-rep-strip {
        max-width: 640px;
        margin-left: auto;
        margin-right: auto;
    }
}

/* --- the history rows lead with the number ---------------------------------------------------- */

/* Overrides the P1 rules above rather than replacing them: the row is still a flex anchor whose value
   pair sits outside the wrapping meta group and never breaks. What changed is which of the two reads
   first, and at what size. On a phone the pair takes its own line above the meta; from 600px it goes
   back to the right-hand end of a single row, where it has always been. */
.ff-practice-history-link {
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
}

.ff-practice-history-link .vl {
    margin-left: 0;
    font-size: 1.05rem;
    font-weight: 700;
    opacity: 1;
}

/* The date stops being the loud part of the row. */
.ff-practice-history-meta .wh {
    font-weight: 400;
    font-size: .72rem;
    opacity: .65;
}

@media (min-width: 600px) {
    .ff-practice-history-link {
        flex-direction: row;
        align-items: center;
        gap: 4px 12px;
    }

    .ff-practice-history-meta {
        order: 1;
    }

    .ff-practice-history-link .vl {
        order: 2;
        margin-left: auto;
    }
}
/* ---- end 36C ---- */
/* ============================================================
   Phase 35C: Draft Order provenance banner + rollover bar.
   Appended block, added 2026-07-30. Two surfaces on
   /commissioner/draft-order:

   1. The provenance banner (.ff-do-prov) inside a MudAlert. On a
      desktop the sentence and Undo share a row; at 360px Undo
      drops to its own full-width 44px line so the fact and the
      action never compete for the same thumb.
   2. The sticky bar, which lost "Clear all queues" (PD-10) and
      gained "Set from last week's finish". Desktop keeps all
      three controls on one row with the finish button trailing;
      at 360px randomize + save share a row and the finish button
      takes a full-width row of its own. The caption is always a
      full-width line under both, so the reason a disabled button
      is disabled sits directly beneath it.
   ============================================================ */
.ff-do-prov {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    width: 100%;
}

.ff-do-prov-text {
    flex: 1 1 240px;
    min-width: 0;
}

.ff-do-prov-undo {
    flex: 0 0 auto;
}

.ff-do-bar-main {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* Trailing edge on a desktop, which is where the old clear-queues button sat, so the muscle memory for
   "the occasional action lives at the far end of this bar" is unchanged. */
.ff-do-bar-finish {
    margin-left: auto;
}

.ff-do-bar-hint {
    flex-basis: 100%;
    min-width: 0;
}

@media (max-width: 599.98px) {
    .ff-do-prov-undo {
        flex: 1 1 100%;
    }

    .ff-do-prov-undo .mud-button-root {
        width: 100%;
        min-height: 44px;
    }

    .ff-do-bar-main {
        flex: 1 1 100%;
    }

    .ff-do-bar-main .mud-button-root {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
    }

    .ff-do-bar-finish {
        flex: 1 1 100%;
        margin-left: 0;
    }

    .ff-do-bar-finish .mud-button-root {
        width: 100%;
        min-height: 44px;
    }
}
/* ---- end Phase 35C draft order provenance ---- */
/* ============================================================================================
   Phase 35D/35E - the winner's conference pick (/draft/conferences)
   Appended as one commented tail block, per the phase's CSS convention. The page reuses
   .ff-conf-chips from the commissioner's picker on purpose: two surfaces that lay the same
   conference list out differently would read as two different lists.
   ============================================================================================ */

/* 360px is the width members actually draft at, so the chips are sized as real touch targets
   first and allowed to grow, rather than shrunk to fit. Two per row at the narrowest width. */
.ff-conf-pick-chips .mud-chip {
    min-height: 44px;
    margin: 0;
}

/* The submit column. Full width and stacked at every width below tablet: the feasibility line
   above it is the thing a member reads last before committing, so nothing shares its row. */
.ff-conf-pick-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}

.ff-conf-pick-actions .mud-button-root {
    min-height: 44px;
    margin: 0;
}

/* The matchup list is the one long payload here. It scrolls inside its own container so the
   page body never scrolls sideways, whatever a conference decides to name its teams. */
.ff-conf-pick-matchups {
    max-width: 100%;
    overflow-x: auto;
    overflow-wrap: anywhere;
}

/* Tablet and up: the submit button stops being a full-width slab and sits at the trailing edge,
   which is where every other confirm control in the app already is. */
@media (min-width: 600px) {
    .ff-conf-pick-actions {
        flex-direction: row-reverse;
        justify-content: flex-start;
        align-items: center;
    }

    .ff-conf-pick-actions .mud-button-root {
        width: auto;
        flex: 0 0 auto;
    }
}
/* ---- end conference pick ---- */

/* ============================================================================================
   Phase 35E - the Home card and the commissioner note for the winner's conference pick
   Appended as one commented tail block, per the phase's CSS convention. The card is a MudAlert,
   so nearly all of its layout is already correct; what is NOT correct by default at 360px is
   the call to action, which MudBlazor renders at its natural width and at a height below the
   44px the rest of this app treats as the minimum touch target.
   ============================================================================================ */

/* The one control on the card is the way into the picker, and the card only ever renders while a
   deadline is running, so it is sized as a real target first: full width on a phone, natural width
   from tablet up where it sits inline like every other card action. */
.ff-conf-pick-card .mud-button-root {
    min-height: 44px;
}

@media (max-width: 599.98px) {
    .ff-conf-pick-card .mud-button-root {
        width: 100%;
        margin: 0;
    }
}

/* The commissioner note under a schedule week's conference value. Caption size, muted, and allowed
   to wrap anywhere, so a long member name cannot push the schedule table sideways at 360px. */
.ff-sched-pick-note {
    font-size: 0.75rem;
    line-height: 1.4;
    opacity: 0.75;
    overflow-wrap: anywhere;
}
/* ---- end 35E ---- */

/* ============================================================================================
   League subpage polish (2026-07-30) - Members & Invites and League Settings page headers
   --------------------------------------------------------------------------------------------
   Appended as one commented tail block, per convention. Both pages open with the same compact
   brand-gradient band, borrowing .ff-league-hero's language (16px radius, primary gradient,
   white text, white outlined back action) without its class: the hero's phone rules are tuned
   for LeagueOverview's three-button action row (PD-20), which these pages do not have. The
   member pages render ONE back control, so the phone rule here is simply "full width, 44px". */
.ff-subpage-head {
    border-radius: 16px;
    padding: 20px 22px;
    margin-bottom: 20px;
    color: #fff;
    background:
        radial-gradient(115% 140% at 88% 0%, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 58%),
        linear-gradient(135deg, var(--mud-palette-primary), var(--mud-palette-primary-darken, #C74A00));
    box-shadow: 0 14px 34px -24px rgba(0, 0, 0, .6);
}

.ff-subpage-head-body {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px 16px;
    flex-wrap: wrap;
}

.ff-subpage-id { min-width: 0; }

.ff-subpage-title {
    color: #fff;
    font-weight: 800;
    letter-spacing: -.02em;
}

.ff-subpage-sub {
    margin-top: 4px;
    color: rgba(255, 255, 255, .85);
    font-size: .9rem;
    overflow-wrap: anywhere;
}

.ff-subpage-head .mud-button-outlined {
    color: #fff;
    border-color: rgba(255, 255, 255, .55);
    white-space: nowrap;
}

@media (max-width: 599.98px) {
    .ff-subpage-head { padding: 16px; }

    .ff-subpage-head .mud-button-outlined {
        width: 100%;
        min-height: 44px;
    }
}

/* The open invite code is a 16-character monospace slug. Everywhere it renders it must be ALLOWED
   to wrap rather than push the whole page into horizontal scroll (the Members & Invites card did
   exactly that at phone width; the letter-spacing makes the run wider than a 360px viewport).
   Later-in-file at equal specificity, so it composes with the base rule at line 588. */
.ff-invite-code {
    overflow-wrap: anywhere;
    min-width: 0;
}

@media (max-width: 599.98px) {
    /* Step the code down wherever it renders at heading size, mirroring the measured treatment
       the new-league card already ships (the .ff-invite-card rule above). */
    .ff-invite-code {
        font-size: 1.05rem;
        letter-spacing: .08em;
    }
}
/* ---- end league subpage polish ---- */

/* ---- CountStepper in the roster slot grid (owner report 2026-07-31) ----
   The slot grid's 110px column minimum was sized for the old compact MudNumericField spinners.
   A CountStepper is three parts (44px button + labeled value field + 44px button, ~200px all in),
   so under the old minimum a phone packed three columns and the value field collapsed to a sliver
   with a clipped label. 210px keeps the stepper whole: two columns on a large phone, one at 360px,
   three or four on desktop. Later-in-file at equal specificity, so it supersedes the base rule. */
.ff-slot-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
    gap: 12px 10px;
}
/* ---- end slot-grid stepper sizing ---- */
