/* ---------------------------------------------------------------------------
   App-level overlays around a match — the start (forming table) and end (results) dialogs, the connection
   toast, the loading skeletons, the play-invite toast, and the profile dialog with its avatar picker.
   Backed by MatchDialog.razor, EndDialog.razor, ConnectionToast.razor, InviteModal.razor, ProfileDialog.razor.

   The two match dialogs carry a platform-owned frame; a game restyles it by redefining --dialog-* from its
   per-game class (game-room--<id>) or by filling the body/result slots. See docs/design/match-dialogs.md.
   --------------------------------------------------------------------------- */
.match-dialog {
    position: fixed;
    inset: 0;
    z-index: var(--z-reading); /* above the room (z-55) and its header */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    --dialog-accent: var(--accent-blue);          /* start: platform blue */
    --dialog-glow:   color-mix(in srgb, var(--accent-blue) 35%, transparent);
}
/* The overlay root is a transparent, full-viewport layer — only the scrim tints and the card is the surface,
   so the finished board stays visible behind it (see docs/design/match-dialogs.md). The root also carries the
   game's own room class (game-room--<id>) so a game can retheme the frame via --dialog-* and scoped
   descendant rules; but a game may set an opaque `background` on that class for its actual room backdrop
   (Azul paints its porcelain table there), which would then paint over the whole board through this fixed,
   inset:0 root. Pin the root transparent so a room background never leaks onto the overlay — the card and the
   --dialog-* properties remain the game's theming seam. The doubled class outranks a game's single-class
   background rule regardless of stylesheet load order. */
.match-dialog.match-dialog {
    background: transparent;
}
.match-dialog--end {
    --dialog-accent: color-mix(in srgb, var(--accent-blue) 45%, var(--text-muted));  /* neutral base (abandoned/spectator) */
    --dialog-glow:   color-mix(in srgb, var(--accent-blue) 24%, transparent);
}
/* The result's mood retints the card: a win celebrates in gold, a draw stays cool blue, a loss reads in a
   dignified slate. Set on the root alongside match-dialog--end (see EndDialog.OutcomeClass). */
.match-dialog--won {
    --dialog-accent: var(--accent-gold);
    --dialog-glow:   var(--hero-glow);
}
.match-dialog--drew {
    --dialog-accent: var(--accent-blue);
    --dialog-glow:   color-mix(in srgb, var(--accent-blue) 30%, transparent);
}
.match-dialog--lost {
    --dialog-accent: color-mix(in srgb, var(--text-muted) 70%, var(--border-color));
    --dialog-glow:   color-mix(in srgb, var(--text-muted) 22%, transparent);
}
.match-dialog__scrim {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--scrim) 42%, transparent);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    animation: mp-fade-in 0.18s ease both;
}
/* The results dialog keeps the finished board in view behind it — the game just played is the backdrop, not
   cleared to bare felt — so its scrim only lightly tints and does not blur the board away. The card's own solid
   surface keeps the standings readable over whatever board shows through. */
.match-dialog--end .match-dialog__scrim {
    background: color-mix(in srgb, var(--scrim) 22%, transparent);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
.match-dialog__card {
    position: relative;
    width: 100%;
    max-width: 24rem;
    /* The dynamic viewport: 100vh is the screen with a mobile browser's toolbars hidden, so a card capped
       against it can extend under a raised toolbar while the body inside it believes it fits. */
    max-height: 88dvh;
    display: flex;
    flex-direction: column;
    padding: 1.5rem 1.4rem 1.3rem;
    border-radius: 1.2rem;
    background: var(--bg-elevated);
    border: 1px solid color-mix(in srgb, var(--dialog-accent) 34%, var(--border-color));
    box-shadow: var(--shadow-lg), 0 0 46px -18px var(--dialog-glow);
    animation: mp-pop-in 0.3s var(--ease-spring) both;
}
.match-dialog__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    text-align: center;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--border-color);
}
.match-dialog__icon { font-size: 2.4rem; line-height: 1; }
.match-dialog__title { margin: 0.2rem 0 0; font-size: 1.5rem; color: var(--text-primary); }
/* The results card is moderately larger and more confident than the forming-table dialog, and its headline
   carries more weight — the win/draw announcement is the moment the modal is built around. width:100% up to
   this cap means every outcome (win/draw/loss, two seats or eight) renders at the same generous width, never
   shrinking to its content. Still clamped so it stays within safe margins on a portrait phone (the frame's
   1.25rem gutter keeps the edges clear). */
.match-dialog--end .match-dialog__card { width: 100%; max-width: 28rem; }
.match-dialog--end .match-dialog__title { font-size: 1.9rem; line-height: 1.05; }
/* The winner celebration: a warm, gold-tinted surface derived from the elevated card colour so the golden
   headline reads in either theme, without hardcoding a cream. */
.match-dialog--won .match-dialog__card {
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent-gold) 11%, var(--bg-elevated)) 0%,
        var(--bg-elevated) 58%);
    border-color: color-mix(in srgb, var(--accent-gold) 42%, var(--border-color));
}
/* The headline is a struck-metal gold: a gradient painted through the glyphs themselves, which is why the
   colour property is transparent and no token can say what the letters read as. The stops are therefore inks
   rather than fills — each is the gold pulled toward the canvas's own text colour, by a share that lightens
   down the sweep — so the sheen follows the canvas the card does. On the table's gold-tinted card the three
   read 3.63:1, 5.35:1 and 8.57:1 against the 3:1 floor this size and weight carry. */
.match-dialog--won .match-dialog__title {
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent-gold) 62%, var(--text-primary)) 0%,
        var(--accent-gold-ink) 55%,
        color-mix(in srgb, var(--accent-gold-deep) 30%, var(--text-primary)) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
/* The header icon sits inside a circular crest — a softly tinted disc, a thin accent ring, and a radial glow,
   all derived from the mood accent (gold win / blue draw / slate loss) so no colour is hardcoded. The trophy/
   handshake/flag reads as a struck medal, not loose emoji. */
.match-dialog--end .match-dialog__icon {
    position: relative;
    z-index: 0;   /* own stacking context, so the ray burst can sit behind the glyph yet above the card */
    display: grid;
    place-items: center;
    width: 3.9rem;
    height: 3.9rem;
    margin-bottom: 0.2rem;
    border-radius: 9999px;
    background: radial-gradient(circle at 50% 42%,
        color-mix(in srgb, var(--dialog-accent) 24%, transparent) 0%,
        color-mix(in srgb, var(--dialog-accent) 9%, transparent) 55%,
        transparent 72%);
    box-shadow:
        inset 0 0 0 1.5px color-mix(in srgb, var(--dialog-accent) 45%, transparent),
        0 6px 20px -8px var(--dialog-glow);
    animation: result-crest 0.5s var(--ease-spring) both;
}
@keyframes result-crest {
    0%   { transform: scale(0.4) rotate(-12deg); opacity: 0; }
    100% { transform: scale(1) rotate(0); opacity: 1; }
}
/* A restrained, symmetric sunburst behind the crest crowns a win. Equal conic wedges masked to a thin ring
   just outside the disc — celebratory but quiet, and it turns slowly rather than flashing. Wins only. */
.match-dialog--won .match-dialog__icon::before {
    content: "";
    position: absolute;
    inset: -0.85rem;
    z-index: -1;
    border-radius: 9999px;
    background: repeating-conic-gradient(from 0deg,
        color-mix(in srgb, var(--accent-gold) 60%, transparent) 0deg 4deg,
        transparent 4deg 24deg);
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 60%, #000 74%, transparent 80%);
            mask: radial-gradient(circle, transparent 56%, #000 60%, #000 74%, transparent 80%);
    opacity: 0.55;
    animation: result-rays 0.6s ease-out both, result-ray-spin 24s linear 0.6s infinite;
}
@keyframes result-rays {
    0%   { opacity: 0; transform: scale(0.55) rotate(-8deg); }
    100% { opacity: 0.55; transform: scale(1) rotate(0); }
}
@keyframes result-ray-spin {
    to { transform: rotate(360deg); }
}
.match-dialog__subtitle {
    margin: 0;
    font-size: 0.72rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.07em;
}
.match-dialog__body {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 0.85rem 0.1rem 0.2rem;
    /* The card caps its height, so the body is the part that scrolls — and a flex item's automatic minimum is
       its content, which would push the actions row past the card's edge with nothing to scroll. min-height: 0
       lets it shrink, which is what makes the overflow reachable at large text sizes and high zoom. */
    min-height: 0;
    overflow-y: auto;
}
.match-dialog__blurb { margin: 0; font-size: 0.9rem; color: var(--text-secondary); line-height: 1.45; text-align: center; }
.match-dialog__how { margin: 0; font-size: 0.82rem; color: var(--text-secondary); line-height: 1.45; text-align: center; }
/* An accented term in the how-to (Bank, brackets, nobles) reads in the app's gold emphasis, lifting it off
   the muted body copy without imposing a per-game hue. The -ink twin is what makes it lift: the bare gold is a
   fill, and an emphasis has to be more readable than the copy around it, not less. */
.match-dialog__how strong { color: var(--accent-gold-ink); }
.match-dialog__note { margin: 0.1rem 0 0; font-size: 0.78rem; color: var(--text-muted); text-align: center; }
.match-dialog__actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-top: 0.9rem;
    margin-top: 0.2rem;
    border-top: 1px solid var(--border-color);
}

/* Seat roster (forming table) */
.seat-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.seat-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.4rem 0.6rem;
    border-radius: 0.6rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    transition: border-color 0.2s ease, background-color 0.2s ease;
}
.seat-row__avatar { --avatar-size: var(--avatar-sm); }
.seat-row__name { flex: 1; min-width: 0; font-size: 0.9rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat-row__you {
    margin-left: 0.35rem;
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent-blue-ink);
    vertical-align: middle;
}
.seat-row__status { font-size: 0.74rem; color: var(--text-muted); white-space: nowrap; }

/* Results ranking (end dialog) */
.result-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.result-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.5rem 0.65rem;
    border-radius: 0.65rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    overflow: hidden;
    /* Each row fades up in turn, so the standings resolve top-down like a settling leaderboard. */
    animation: result-row-in 0.32s var(--ease-spring) both;
}
.result-list .result-row:nth-child(2) { animation-delay: 0.05s; }
.result-list .result-row:nth-child(3) { animation-delay: 0.1s; }
.result-list .result-row:nth-child(4) { animation-delay: 0.15s; }
.result-list .result-row:nth-child(n+5) { animation-delay: 0.2s; }
@keyframes result-row-in {
    0%   { opacity: 0; transform: translateY(6px); }
    100% { opacity: 1; transform: translateY(0); }
}
/* The leaderboard fill: a faint standing bar behind each row, longest for the leader (see
   EndDialog.StandingFill). Sits under the content — the row clips it and the text rides above. */
.result-row::before {
    content: "";
    position: absolute;
    inset: 0;
    width: var(--standing, 0%);
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--text-muted) 16%, transparent) 0%, transparent 100%);
    pointer-events: none;
}
.result-row > * { position: relative; }
.result-row--winner {
    border-color: color-mix(in srgb, var(--accent-gold) 62%, var(--border-color));
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--accent-gold) 17%, var(--bg-card)) 0%,
        color-mix(in srgb, var(--accent-gold) 7%, var(--bg-card)) 100%);
    box-shadow: 0 8px 20px -14px var(--hero-glow);
}
.result-row--winner::before {
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--accent-gold) 26%, transparent) 0%, transparent 100%);
}
/* The champion row gets a single gold shine sweep as the results land — the same sweep the your-turn
   banner uses, run once. */
.result-row--winner::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.5) 50%, transparent 60%);
    transform: translateX(-100%);
    animation: room-shimmer 1.1s ease-out 0.35s 1 both;
    pointer-events: none;
}
.result-row--me { box-shadow: inset 3px 0 0 var(--accent-blue); }
/* A row that is both the winner and the viewer keeps the winner's gold glow behind its own blue edge — a
   plain --me row would otherwise drop the glow (equal specificity, declared later). */
.result-row--winner.result-row--me { box-shadow: inset 3px 0 0 var(--accent-blue), 0 8px 20px -14px var(--hero-glow); }
.result-row__rank {
    display: grid;
    place-items: center;
    width: 2.1rem;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--text-muted);
}
/* A medal glyph (top three) is set larger than the ordinal fallback below it. */
.result-row__rank[data-rank="1"],
.result-row__rank[data-rank="2"],
.result-row__rank[data-rank="3"] { font-size: 1.35rem; }
.result-row--winner .result-row__rank { color: var(--accent-gold-ink); }
.result-row__avatar { --avatar-size: var(--avatar-sm); }
.result-row__name { flex: 1; min-width: 0; font-size: 0.95rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-row__you {
    margin-left: 0.35rem;
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent-blue-ink);
}
.result-row__score { font-size: 1.05rem; font-weight: 700; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.result-row__pts { font-size: 0.7rem; font-weight: 400; color: var(--text-muted); }
/* The champion's score reads a touch larger and in a deep, warm gold, so the eye lands on it first while it
   still keeps a legible contrast on the pale winner row (a lighter gold would wash out against it). */
.result-row--winner .result-row__score {
    font-size: 1.15rem;
    color: color-mix(in srgb, var(--accent-gold-deep) 60%, var(--text-primary));
}

/* Victory confetti: a brief fall of pieces over the winner's card, each piece positioned + timed inline
   (see EndDialog.ConfettiStyle). Absolutely placed over the body, non-interactive, and self-clearing once
   the fall completes. */
.result-confetti {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 2;
}
.result-confetti__bit {
    position: absolute;
    top: -8%;
    width: 0.5rem;
    height: 0.7rem;
    border-radius: 1px;
    opacity: 0;
    animation-name: result-confetti-fall;
    animation-timing-function: cubic-bezier(0.4, 0.1, 0.6, 1);
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
@keyframes result-confetti-fall {
    0%   { transform: translateY(0) rotate(0);       opacity: 0; }
    10%  { opacity: 1; }
    100% { transform: translateY(115%) rotate(540deg); opacity: 0; }
}

/* The result panel slot a game's end-screen summary renders into; the summary's own styling lives in the
   game's stylesheet. */
.result-panel { margin-top: 0.3rem; padding-top: 0.7rem; border-top: 1px dashed var(--border-color); }

@media (prefers-reduced-motion: reduce) {
    .match-dialog__scrim,
    .match-dialog__card,
    .match-dialog__icon,
    .match-dialog--won .match-dialog__icon::before,
    .result-row,
    .result-row--winner::after,
    .result-confetti__bit {
        animation: none;
    }
    /* The sunburst keeps its static opacity (0.55) and stays as a still crown rather than spinning. */
    /* With motion off the confetti never animates in, so keep it hidden rather than frozen at the top. */
    .result-confetti { display: none; }
    .result-confetti__bit { opacity: 0; }
}

/* ---------------------------------------------------------------------------
   Match history (opened from the profile hub) and its replay dialog. The list
   reads the synced player model — no server round-trip.
   --------------------------------------------------------------------------- */

/* Connection trouble toast: a small, non-blocking pill pinned to the lower edge, shown only while the link is
   unstable, reconnecting, or lost. A healthy session shows nothing. It floats above the page but keeps a
   narrow footprint so it never blocks the catalog. */
.conn-toast {
    position: fixed;
    left: 50%;
    bottom: var(--toast-bottom);
    transform: translateX(-50%);
    z-index: var(--z-room-overlay);
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    max-width: calc(100vw - 2rem);
    padding: 0.5rem 0.85rem;
    border-radius: 9999px;
    font-size: 0.85rem;
    font-weight: 600;
    box-shadow: var(--shadow-lg);
    /* A dedicated slide-in that keeps the translateX(-50%) centering — a generic translateY keyframe would
       override the transform and strand the pill off-centre. */
    animation: conn-toast-in 0.28s var(--ease-spring) both;
}
@keyframes conn-toast-in {
    from { opacity: 0; transform: translate(-50%, 14px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}
.conn-toast__dot {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 9999px;
    flex: none;
    background: currentColor;
    animation: mp-pulse-dot 1.4s ease-in-out infinite;
}
/* The pill caps its width, but a nowrap child's automatic flex minimum is the whole string, which no parent
   cap can pull back — the centred pill then hangs off both edges and the Reconnect button lands half
   off-screen. The message is the part that gives. */
.conn-toast__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Degraded-but-connected reads amber; a lost/erroring link reads red. Both stay legible on their tinted pill. */
.conn-toast--warn {
    background: color-mix(in srgb, var(--accent-amber) 12%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--accent-amber) 55%, transparent);
    color: var(--text-status-attention);
}
.conn-toast--error {
    background: color-mix(in srgb, var(--accent-red) 10%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--accent-red) 50%, transparent);
    color: var(--accent-red-ink);
}
.conn-toast__action {
    margin-left: 0.15rem;
    padding: 0.2rem 0.6rem;
    border-radius: 9999px;
    border: 1px solid currentColor;
    background: transparent;
    color: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
}
/* Tracks the pill's own amber/red ink, so the wash reads on the shell's navy as well as on a light canvas. */
.conn-toast__action:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
@keyframes mp-pulse-dot {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
    .conn-toast { animation: none; }
    .conn-toast__dot { animation: none; }
}
/* Loading skeletons: neutral shimmer placeholders that stand in for the session-dependent shelves and the
   profile name while the player model streams in, so the page reads as "your games are loading" rather
   than empty. Shown only once a connect is visibly slow (see Home's _showConnecting), so a fast warm connect
   never flashes them. Purely decorative — the elements are aria-hidden. */
.skeleton {
    position: relative;
    overflow: hidden;
    background: var(--bg-card, rgba(148, 163, 184, 0.12));
    border-radius: 0.75rem;
}
.skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(148, 163, 184, 0.22), transparent);
    animation: skeleton-shimmer 1.3s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
    100% { transform: translateX(100%); }
}
.skeleton--title { width: 8rem; height: 1.15rem; margin-bottom: 0.9rem; border-radius: 0.5rem; }
/* Mirrors .game-card's rail footprint (catalog.css: 13rem wide, 1.15rem radius, widening at 48rem) so the
   hand-off from placeholder to content does not reflow the shelf sideways and down — the one thing a skeleton
   exists to prevent. */
.skeleton--tile  { width: 13rem; height: 14.6rem; flex: 0 0 auto; border-radius: 1.15rem; }
.skeleton--name  { display: inline-block; width: 4.5rem; height: 0.9rem; border-radius: 0.4rem; vertical-align: middle; }
/* The same stop the real tile widens at. */
@media (min-width: 48rem) {
    .skeleton--tile { width: 15rem; height: 16.4rem; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton::after { animation: none; }
}

/* A friend's in-app play invite — a toast pinned above the connection toast's slot, in the brand accent so it
   reads as an invitation rather than a warning. Accepting runs the ordinary private-table join. */
.play-invite {
    position: fixed;
    left: 50%;
    bottom: calc(var(--toast-bottom) + 3.25rem);
    transform: translateX(-50%);
    z-index: calc(var(--z-room-overlay) + 1);
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    max-width: calc(100vw - 2rem);
    padding: 0.6rem 0.95rem;
    border-radius: 9999px;
    font-size: 0.88rem;
    background: var(--bg-primary);
    border: 1px solid var(--accent-blue);
    color: var(--text-primary);
    box-shadow: var(--shadow-lg);
    animation: play-invite-in 0.28s var(--ease-spring) both;
}
@keyframes play-invite-in {
    from { opacity: 0; transform: translate(-50%, 14px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}
.play-invite__text { min-width: 0; }
.play-invite__actions { display: inline-flex; gap: 0.4rem; flex: none; }
/* Compact the play-invite action buttons (chrome.css's default .app-btn sizing is too large for the
   pill). The descendant selector (0,2,0) wins the cascade over .app-btn / .app-btn--sm. */
.play-invite__actions .app-btn { font-size: 0.8rem; padding: 0.3rem 0.75rem; }
@media (prefers-reduced-motion: reduce) {
    .play-invite { animation: none; }
}

/* --- Profile dialog ---------------------------------------------------------
   The account hub opened from the profile pill: avatar + name + guest badge, a basic-stats strip, name-change
   controls, and the log-in-to-an-account upgrade. A wide PanelModal whose header is the profile hero,
   scrolling as a whole — a screen's worth of content rather than a bounded list. Compounded with the
   primitive's card class so these refinements win over the base recipe whatever the sheet order. */
.app-modal__panel.profile-card {
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    gap: 0.6rem;
    padding: 1.25rem;
}
/* The Edit pen: a round icon button beside the close × in the hero, sized to match it so the two read as a pair. */
.profile-card__edit {
    width: 2.75rem;
    height: 2.75rem;
    border: none;
    border-radius: 999px;
    color: var(--text-secondary);
    background: var(--bg-hover);
    font: inherit;
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
}
.profile-card__edit:hover { color: var(--text-primary); }
/* The guest/email account badge carried in the hero meta. */
.profile-card__badge {
    align-self: flex-start;
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent-blue-ink);
    background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--accent-blue) 24%, transparent);
    border-radius: 9999px;
    padding: 0.12rem 0.55rem;
}
/* The inline edit panel revealed under the hero by the Edit pen: the name field + Save/Cancel and the avatar
   picker together. */
.profile-edit-panel { display: flex; flex-direction: column; gap: 0.5rem; }
.profile-edit-panel__actions { display: flex; gap: 0.5rem; }
/* The read-only player-id row: a quiet key/value line at home in the wide card. */
.profile-card__field {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0.1rem;
    border-top: 1px solid var(--border-color);
}
.profile-card__field-key { font-size: 0.8rem; color: var(--text-secondary); }
.profile-card__field-value { font-size: 0.82rem; color: var(--text-muted); font-variant-numeric: tabular-nums; word-break: break-all; text-align: right; }
/* The avatar picker: a wrapped grid of tappable options (the generated disc first, then the presets), the
   chosen one ringed. Each option is a bare button that just frames a PlayerAvatar. */
.avatar-picker { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 0.2rem; }
.avatar-picker__option {
    padding: 3px;
    border: 2px solid transparent;
    border-radius: 9999px;
    background: none;
    cursor: pointer;
    line-height: 0;
    transition: border-color 0.12s ease, transform 0.12s ease;
}
.avatar-picker__option:hover { transform: translateY(-1px); }
.avatar-picker__option--selected { border-color: var(--accent-blue); }
.avatar-picker__avatar { --avatar-size: var(--avatar-lg); }

.profile-card__note {
    margin: 0.9rem 0 0.6rem;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-secondary);
}
/* The gateway into the full match history, which lives under the profile. A row-style button — icon, label, a
   count pill, and a chevron — that reads as "opens another surface". */
.profile-history-btn {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.65rem 0.8rem;
    border-radius: 0.7rem;
    background: color-mix(in srgb, var(--accent-blue) 6%, var(--bg-card));
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font-size: 0.95rem;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.profile-history-btn:hover {
    background: color-mix(in srgb, var(--accent-blue) 10%, var(--bg-card));
    border-color: color-mix(in srgb, var(--accent-blue) 30%, var(--border-color));
}
.profile-history-btn__icon { font-size: 1rem; line-height: 1; }
.profile-history-btn__label { font-weight: 600; }
.profile-history-btn__count {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--accent-blue-ink);
    background: color-mix(in srgb, var(--accent-blue) 14%, var(--bg-card));
    border-radius: 9999px;
    padding: 0.1rem 0.5rem;
}
.profile-history-btn__chev { margin-left: auto; color: var(--text-muted); font-size: 1.1rem; line-height: 1; }

