/* ---------------------------------------------------------------------------
   Group surface — the group page hero and members, its settings and requests, played-together history,
   ongoing-game cards, the group play section (gatherings, schedule, sessions, RSVP), and the clubhouse
   layout that hosts the conversation rail.
   Backed by GroupPage.razor, GroupHero.razor, GroupMembers.razor, GroupSchedule.razor, GroupClubhouse.razor,
   GroupPlaySection.razor, GroupOpenRail.razor, GroupSessionCard.razor, GroupActiveMatchCard.razor,
   GroupShelfCard.razor. The conversation the rail hosts is css/convo.css, voice is css/voice.css, and the
   communities directory is css/communities.css. See docs/design/group.md.
   --------------------------------------------------------------------------- */

/* --- Group page -------------------------------------------------------------
   The full-screen group surface at /group: a hero header (a coloured cover in the group's own hue, with the
   group's monogram, name, member avatars, an online count, and an Invite button) over a secondary tab
   bar and the active section. Reached from the catalog's group-shelf card. See docs/design/group.md. */
.group-page {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding: 0.5rem clamp(1rem, 3vw, 2.5rem) calc(2rem + env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
/* The page before its group has synced (and the page whose group is gone) is one short line, and a line alone
   at the top-left of a full-width surface reads as a broken page rather than a waiting one. Centre it in the
   height the shell gives, so the state occupies the page it is standing in for. */
.group-page--loading {
    flex: 1 0 auto;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 3rem clamp(1rem, 3vw, 2.5rem);
    text-align: center;
}
/* A quiet ring that turns while the group syncs — the one moving thing on an otherwise empty page, so the wait
   reads as work in progress. */
.group-page__spinner {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-pill);
    border: 3px solid color-mix(in srgb, var(--accent-blue) 22%, transparent);
    border-top-color: var(--accent-blue);
    animation: group-page-spin 0.9s linear infinite;
}
@keyframes group-page-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .group-page__spinner { animation: none; }
}

.group-hero {
    border-radius: 1.1rem;
    overflow: hidden;
    border: 1px solid var(--border-color);
    background: var(--bg-elevated);
    box-shadow: var(--shadow-md, 0 1px 3px rgba(0,0,0,0.06));
}
.group-hero__cover {
    position: relative;
    height: 2.75rem;
    /* A soft top-right sheen over the hue gradient so the cover reads as a lit surface rather than a flat slab.
       Decorative only (the element is aria-hidden); the highlight is hue-agnostic white at low alpha. */
    background:
        radial-gradient(120% 200% at 88% -60%, rgba(255, 255, 255, 0.18), transparent 55%),
        linear-gradient(135deg,
            hsl(var(--group-hue, 210) 58% 32%),
            hsl(var(--group-hue, 210) 52% 44%));
}
.group-hero__monogram {
    position: absolute;
    right: 0.85rem;
    top: 0.05rem;
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1;
    color: rgba(255, 255, 255, 0.22);
    font-family: var(--font-display, inherit);
}
.group-hero__body { display: flex; flex-direction: column; gap: 0.55rem; padding: 0.6rem 0.9rem 0.8rem; }
.group-hero__head { display: flex; align-items: center; gap: 0.6rem; }
.group-hero__icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.6rem;
    height: 2.6rem;
    margin-top: -1.55rem;
    border-radius: 0.8rem;
    border: 3px solid var(--bg-elevated);
    background: hsl(var(--group-hue, 210) 55% 47%);
    color: var(--text-on-accent);
    font-size: 1.2rem;
    font-weight: 800;
    font-family: var(--font-display, inherit);
    box-shadow: var(--shadow-md, 0 1px 3px rgba(0,0,0,0.15));
}
.group-hero__titles { display: flex; flex-direction: column; min-width: 0; }
.group-hero__name { margin: 0; font-size: 1.2rem; line-height: 1.1; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-hero__meta { margin: 0.1rem 0 0; font-size: 0.8rem; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-left: auto; }

/* The group's people chip, pinned in the hero's top-right action area beside Invite: the avatar stack with
   the member count, so the hero reads as a living clubhouse. A single quiet chip-like button; tapping it
   opens the roster. */
.group-hero__people {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    max-width: 100%;
    padding: 0.25rem 0.6rem 0.25rem 0.3rem;
    border-radius: 9999px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.group-hero__people:hover { background: var(--bg-hover); border-color: color-mix(in srgb, var(--accent-blue) 30%, var(--border-color)); }
.group-hero__avatars { display: inline-flex; align-items: center; }
.group-hero__avatars > * + * { margin-left: -0.45rem; }
.group-hero__avatar { --avatar-size: var(--avatar-sm); border-radius: 9999px; box-shadow: 0 0 0 2px var(--bg-secondary); }
/* The hero chip's speaking ring. The platform's .player-avatar--speaking (room.css) carries the ring for every
   surface, but the rule above sets box-shadow at the same (0,1,0) specificity and this sheet loads after
   room.css, so the chip's own separating ring would take the cascade. Compounding the two class names lifts
   the speaking state above it, the way .group-member__act--danger.app-btn-micro does below. */
.group-hero__avatar.player-avatar--speaking { box-shadow: 0 0 0 2px var(--accent-green); }
/* The roster row's speaking ring, drawn from the row because the disc it rings is rendered inside FriendRow,
   whose render gate deliberately holds no speaking flag — that flag toggles on every speech burst and would
   repaint an otherwise static row. The hero's chip has no such hop and carries .player-avatar--speaking
   (room.css) directly; the values here are that rule's. */
.group-member--speaking .player-avatar { box-shadow: 0 0 0 2px var(--accent-green); border-radius: var(--radius-pill); }
.group-hero__more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--avatar-sm);
    height: var(--avatar-sm);
    border-radius: 9999px;
    background: var(--bg-card);
    border: 2px solid var(--bg-secondary);
    color: var(--text-secondary);
    font-size: 0.72rem;
    font-weight: 600;
}
.group-hero__count { font-size: 0.8rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; }
/* The hero's primary action: a filled accent-blue treatment layered over the shared .gd__btn2 base, so the
   Invite button reads as the headline call-to-action while staying compact. Sits in source order after .gd__btn2
   so these overrides win at equal specificity. */
.group-hero__invite {
    background: var(--accent-blue);
    border-color: transparent;
    color: var(--text-on-accent);
    font-weight: 600;
    box-shadow: 0 8px 18px -14px color-mix(in srgb, var(--accent-blue) 80%, transparent);
}
.group-hero__invite:hover:not(:disabled) {
    background: var(--accent-blue);
    border-color: transparent;
    filter: brightness(1.06);
}
.group-hero__invite:active:not(:disabled) { filter: brightness(0.97); }

/* Secondary tab bar under the hero. */
.group-tabs { display: flex; flex-wrap: wrap; gap: 0.25rem; border-bottom: 1px solid var(--border-color); }
.group-tabs__tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.55rem 0.85rem;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.15s ease;
}
.group-tabs__tab:hover { color: var(--text-primary); }
.group-tabs__tab--active { color: var(--accent-blue-ink); }
.group-tabs__tab--active::after {
    content: "";
    position: absolute;
    left: 0.5rem;
    right: 0.5rem;
    bottom: -1px;
    height: 2px;
    border-radius: 2px;
    background: var(--accent-blue);
}

/* No horizontal overflow control here on purpose. Clipping — or hiding — an ancestor this close above the
   Live-games rail (a .shelf__row) swallows that rail's touch-pan, so it won't scroll under touch; the same
   clip/hidden at the shell (.app-main) does not, which is why page-level horizontal overflow is contained up
   there and this stays visible. So overflow-x:hidden is no substitute for the old clip (it would trap the pan
   just the same), and it carries a second problem: beside the default visible y axis it computes overflow-y to
   auto, making the tab body a nested scroll container — but the page is the single scroll context. */
.group-tab-body { min-height: 12rem; min-width: 0; }
.group-section { display: flex; flex-direction: column; gap: 0.75rem; }
.group-section__title { margin: 0.4rem 0 0; font-size: 1.2rem; font-weight: 700; color: var(--text-primary); }

/* The group page's shelves ride the catalog's shelf rail (.shelf / .shelf__row) with the shared MatchCard
   and GroupActiveMatchCard tiles, so they stay visually in step with the catalog shelves. */

/* A "coming soon" placeholder for the not-yet-built sections (recommendations, scheduling, permissions, …). */
.group-soon {
    padding: 0.85rem 1rem;
    border-radius: 0.8rem;
    background: var(--bg-secondary);
    border: 1px dashed var(--border-color);
    color: var(--text-secondary);
    font-size: 0.86rem;
}

/* An actionable empty state: a short, friendly line with a follow-up action, never a bare "nothing here" — the
   shared EmptyState, compacted so it sits inside a tab section rather than filling a page, and framed so it
   still reads as a slot in the group's stack of cards. */
.app-empty.group-empty {
    gap: 0.3rem;
    padding: 1.1rem 1rem;
    border-radius: 0.8rem;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
}
.group-empty .app-empty__art { font-size: 1.5rem; }
.group-empty .app-empty__title { font-size: 0.95rem; }

/* The Live games shelf's row (.shelf__row) carries the seated-match and spectate-table cards plus the trailing
   “+” start tile. */

/* The Open-seats rail: joinable lobbies from the player's other groups, riding the same shelf rail as Continue
   playing. Each card is a compact host·game·seats summary with a Join CTA. */
.group-openrail__card {
    flex: 0 0 auto;
    width: 11rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.7rem 0.8rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 0.9rem;
}
.group-openrail__host { font-size: 0.92rem; font-weight: 700; color: var(--text-primary); }
.group-openrail__game { font-size: 0.82rem; color: var(--text-secondary); }
.group-openrail__seats { font-size: 0.78rem; color: var(--text-muted); }

/* Settings: grouped rows of name/value + a quiet leave action. */
.group-settings { gap: 1.1rem; }
.group-settings__group { display: flex; flex-direction: column; gap: 0.35rem; }
.group-settings__value { margin: 0; font-size: 0.95rem; color: var(--text-primary); font-weight: 600; }
.group-settings__hint { margin: 0; font-size: 0.82rem; color: var(--text-muted); }

.group-panel__loading { margin: 0; font-size: 0.95rem; color: var(--text-secondary); }
/* A quiet text-link exit — leaving a shared group is a background action, not a primary one. */
.group-panel__leave {
    align-self: center;
    margin: 0.1rem 0 0;
    padding: 0.2rem 0.4rem;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 0.8rem;
    cursor: pointer;
}
.group-panel__leave:hover { color: var(--accent-red-ink); text-decoration: underline; }

.group-members { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.group-member { font-size: 0.9rem; color: var(--text-primary); display: flex; align-items: center; gap: 0.55rem; min-height: 0; padding: 0.45rem 0.55rem; border-radius: 0.7rem; background: var(--bg-card); }
/* The roster's inner content — the shared friend-row renders data-only; the member chrome (tags, admin
   controls, report) rides as a sibling, so the card background lives on the <li> and the friend-row is a
   transparent, flexible inner cluster that shares the row with the chrome. */
.group-members .friend-row { background: transparent; border: none; min-height: 0; padding: 0; gap: 0.55rem; flex: 1 1 auto; }
.group-members .friend-row__avatar { --avatar-size: var(--avatar-sm); }
/* Right-aligned cluster of member-specific chrome: admin tags, voice-issue glyph, roster controls, report. */
.group-member__chrome { display: inline-flex; align-items: center; gap: 0.3rem; flex: 0 0 auto; }
/* A warning glyph on a roster row whose peer connection can't be reached (a failed or sustained-disconnected
   pair — typically a no-TURN relay gap). */
.group-member__voice-issue { color: var(--accent-amber-ink); font-size: 0.8rem; }
.group-member__you { font-size: 0.75rem; color: var(--text-muted); }
/* Admin roster controls (promote/demote, remove), pushed to the row's end. */
.group-member__admin { margin-left: auto; display: inline-flex; gap: 0.3rem; flex: 0 0 auto; }
.group-member__report { display: inline-flex; gap: 0.3rem; align-items: center; flex: 0 0 auto; }
.group-member__report-input { width: 12rem; }
/* The button itself is .app-btn-micro (chrome.css); this is the destructive voice it has no state for.
   Compounded with the primitive, which loads later and would otherwise win the colour back at equal
   specificity. */
.group-member__act--danger.app-btn-micro { color: var(--accent-red-ink); }
.group-member__act--danger.app-btn-micro:hover { background: color-mix(in srgb, var(--accent-red) 12%, var(--bg-card)); color: var(--accent-red-ink); }

.group-rename { display: flex; align-items: center; gap: 0.4rem; margin: 0 0 0.2rem; }
.group-rename__input { flex: 1 1 auto; min-width: 0; }

/* Identity & posture: the cover image over the hero, a posture badge, and the Settings editors (a segmented
   posture control, inline description/tags/cover editors, and the pending join-request queue). */
.group-hero__posture { align-self: flex-start; margin: 0.1rem 0 0; }
.group-posture { display: flex; flex-direction: column; gap: 0.4rem; }
.group-posture__opt {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.45rem 0.6rem;
    border-radius: 0.6rem;
    border: 1px solid var(--border-color);
    background: var(--bg-card);
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.group-posture__opt:hover { background: var(--bg-hover); }
.group-posture__opt--active {
    border-color: var(--accent-blue);
    background: color-mix(in srgb, var(--accent-blue) 10%, var(--bg-card));
}
.group-posture__label { font-size: 0.9rem; font-weight: 700; color: var(--text-primary); }
.group-posture__desc { font-size: 0.78rem; color: var(--text-muted); }
.group-edit { display: flex; flex-direction: column; gap: 0.4rem; }
/* A multi-line editor stands taller than .app-input's tap floor; compounded with the primitive's class so
   the taller minimum wins the cascade (chrome.css loads later and would otherwise tie and win). */
.group-edit__area.app-input { width: 100%; min-height: 4rem; resize: vertical; }
.group-edit__input { flex: 1 1 auto; min-width: 0; }
.group-edit__actions { display: flex; align-items: center; gap: 0.4rem; }
.group-tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }
/* A tag a member typed, so it keeps its own casing and a readable size — larger than a status chip, and never
   shouted. See .app-chip in chrome.css; compounded with it because that sheet loads after this one and every
   property here is one the base recipe also sets. */
.group-tags__chip.app-chip { padding: 0.15rem 0.5rem; font-size: 0.78rem; color: var(--text-primary);
    background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-card)); }
.group-cover { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.group-cover__opt {
    padding: 0.35rem 0.7rem;
    border-radius: 0.6rem;
    border: 1px solid var(--border-color);
    background: var(--bg-card);
    color: var(--text-primary);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.group-cover__opt:hover { background: var(--bg-hover); }
.group-cover__opt--active {
    border-color: var(--accent-blue);
    background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-card));
}
.group-requests { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.group-requests__row { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.4rem; border-radius: 0.6rem; background: var(--bg-card); }
.group-requests__name { flex: 1 1 auto; min-width: 0; font-size: 0.88rem; color: var(--text-primary); font-weight: 600; }

/* Card cover + badges: the cover image over the banner, a posture label, and a pending-request count chip. */
.group-shelf-card__name-row { display: flex; align-items: center; gap: 0.35rem; min-width: 0; }
.group-shelf-card__pending {
    flex: 0 0 auto;
    min-width: 1.1rem;
    padding: 0.05rem 0.35rem;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--accent-amber) 20%, var(--bg-card));
    color: var(--accent-amber-ink);
    font-size: 0.68rem;
    font-weight: 700;
    text-align: center;
}

/* Played-together history: the games the group has finished, newest first. Each is a compact bordered card —
   the game's artwork, the result and when it was played, and a Replay button for a game the viewer can watch
   back (their own seated games; see docs/design/replay-playback.md). Kept short vertically so the list stays
   scannable. The accent tint comes from the game's catalog theme, like the ongoing-game cards. */
.group-history { display: flex; flex-direction: column; gap: 0.4rem; }
.group-history__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.group-history__row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: 0.7rem;
    background: color-mix(in srgb, var(--game-accent) 6%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--game-accent) 22%, var(--border-color));
    color: var(--text-primary);
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.group-history__row:hover {
    border-color: color-mix(in srgb, var(--game-accent) 42%, var(--border-color));
    background: color-mix(in srgb, var(--game-accent) 10%, var(--bg-card));
}
.group-history__art {
    flex: 0 0 auto;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 0.45rem;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-tile-bg);
}
.group-history__emoji { font-size: 1.05rem; line-height: 1; }
.group-history__body { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; flex: 1 1 auto; }
.group-history__game {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.group-history__meta {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    min-width: 0;
}
/* The winner name is the one variable-length part of the meta line, so it is the flex item that gives: min-width:0
   completes the truncation chain (body → meta → winner all shrink), so a long name ellipses instead of forcing the
   pill wider than its column. */
.group-history__winner { min-width: 0; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.group-history__dot { opacity: 0.5; flex: 0 0 auto; }
.group-history__time { flex: 0 0 auto; }

/* --- Group ongoing-games card ---
   One of the group's in-play tables the viewer is not seated in: a compact card on the same scrolling shelf rail
   as the continue/my-match cards. The game's catalog art fills the card as a backdrop (--art modifier) with a
   scrim so the overlaid name, seated-player avatar stack, live status, and Watch CTA read over it; a game with no
   art falls back to an accent-tinted tile led by the game's icon. The accent tint comes from the game's catalog
   theme (--game-accent / --game-accent-deep), like the group-history rows. */
.group-ongoing-card {
    position: relative;
    display: flex;
    align-items: stretch;
    flex: 0 0 auto;
    scroll-snap-align: start;
    width: 19rem;
    border-radius: 1.1rem;
    overflow: hidden;
    background: color-mix(in srgb, var(--game-accent) 8%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--game-accent) 25%, var(--border-color));
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
    text-align: left;
}
/* An art card gets a fixed height so the backdrop reads, with its content pinned to the bottom over the art. */
.group-ongoing-card--art { min-height: 7.5rem; }
.group-ongoing-card__art { z-index: 0; }
.group-ongoing-card__scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(
        to top,
        color-mix(in srgb, var(--game-accent-deep) 92%, #000) 0%,
        color-mix(in srgb, var(--game-accent-deep) 60%, transparent) 52%,
        transparent 100%);
}
.group-ongoing-card__content {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-width: 0;
    flex: 1 1 auto;
    padding: 0.85rem 1rem;
}
.group-ongoing-card--art .group-ongoing-card__content { justify-content: flex-end; }
/* Over the dark scrim the text goes light; the icon-tile fallback keeps the default dark-on-tint. */
.group-ongoing-card--art .group-ongoing-card__name,
.group-ongoing-card--art .group-ongoing-card__names,
.group-ongoing-card--art .group-ongoing-card__status { color: var(--text-on-accent); }
.group-ongoing-card__head { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.group-ongoing-card__icon { font-size: 1.9rem; line-height: 1; }
.group-ongoing-card__name { font-size: 1.02rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-ongoing-card__status {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.74rem;
    color: var(--text-secondary);
    white-space: nowrap;
}
/* A small pulsing dot marks the table as live. */
.group-ongoing-card__live {
    position: relative;
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 9999px;
    background: var(--accent-green);
}
/* The live ring is a ::after disc scaled + faded; transform + opacity
   composite, so it animates without repainting. */
.group-ongoing-card__live::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--accent-green);
    pointer-events: none;
    opacity: 0;
    animation: group-ongoing-pulse 2s ease-out infinite;
}
@keyframes group-ongoing-pulse {
    0%   { transform: scale(1); opacity: 0.65; }
    70%  { transform: scale(2.4); opacity: 0; }
    100% { transform: scale(2.4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .group-ongoing-card__live::after { animation: none; }
}
/* The Watch CTA sits at the trailing edge as a flex sibling of the content, centered over the art; its solid
   button surface reads over the backdrop. A flex sibling (not absolute) so it never underlaps the name. */
.group-ongoing-card__watch { position: relative; z-index: 3; flex: 0 0 auto; align-self: center; margin: 0 0.85rem 0 0.2rem; }
.group-ongoing-card__players { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.group-ongoing-card__avatars { display: inline-flex; align-items: center; flex: none; }
.group-ongoing-card__avatars > * + * { margin-left: -0.4rem; }
.group-ongoing-card__avatar { --avatar-size: var(--avatar-xs); border-radius: 9999px; box-shadow: 0 0 0 2px var(--bg-card); }
/* The "+N" overflow chip closing the avatar stack when more players are seated than the stack shows. */
.group-ongoing-card__more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    height: 1.5rem;
    padding: 0 0.3rem;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--game-accent-deep) 70%, #000);
    color: var(--text-on-accent);
    font-size: 0.66rem;
    font-weight: 700;
    box-shadow: 0 0 0 2px var(--bg-card);
}
.group-ongoing-card__names { font-size: 0.74rem; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Group play: the game-detail modal's group mode and the ready-up ---- */

/* The gathering proposal's invite roster (group mode of the Play card): a compact member checkbox list,
   styled like the modal's other controls. */
.gd-propose__invite { margin: 0; padding: 0; border: none; display: flex; flex-direction: column; gap: 0.25rem; }
.gd-propose__member { display: flex; align-items: center; gap: 0.45rem; font-size: 0.88rem; color: var(--text-primary); }
.gd-propose__member input { accent-color: var(--accent-blue); }
.gd-propose__member-avatar { --avatar-size: var(--avatar-xs); }
.gd-propose__member-name { min-width: 0; }

/* The joinable list of open gatherings — one compact card per gathering, packed into a wrapping grid so
   several rally side-by-side on a wide screen and stack on a narrow one. */
.group-gatherings {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: 0.7rem;
}

/* Ready-up card: the proposed game, its tempo/range/duration, a seat map, and the ready/cancel/start
   controls. Filled seats (ready participants) lead; open seats invite a claim or ready-up. */
.group-gathering {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.7rem;
    border-radius: 0.9rem;
    background: color-mix(in srgb, var(--accent-blue) 6%, var(--bg-secondary));
    border: 1px solid color-mix(in srgb, var(--accent-blue) 35%, var(--border-color));
    transition: transform 0.18s var(--ease-spring), box-shadow 0.18s ease;
}
.group-gathering:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.group-gathering__head { display: flex; align-items: center; gap: 0.5rem; }
.group-gathering__icon { font-size: 1.4rem; line-height: 1; }
.group-gathering__title { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.group-gathering__game { font-size: 0.98rem; color: var(--text-primary); }
.group-gathering__params { font-size: 0.74rem; color: var(--text-muted); }
.group-gathering__seats { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.group-gathering__seat {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
    padding: 0.3rem 0.45rem;
    border-radius: 0.6rem;
    border: 1px solid var(--border-color);
}
.group-gathering__seat--ready {
    border-color: color-mix(in srgb, var(--accent-green) 45%, transparent);
    background: color-mix(in srgb, var(--accent-green) 10%, var(--bg-card));
}
.group-gathering__seat--open { color: var(--text-muted); border-style: dashed; }
.group-gathering__seat-avatar { --avatar-size: var(--avatar-xs); }
.group-gathering__seat-name { font-size: 0.78rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-gathering__waiting { margin: 0; font-size: 0.74rem; color: var(--text-muted); display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }
.group-gathering__waiting-name { font-weight: 600; }
.group-gathering__status { margin: 0; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); }
/* The actions wrap rather than squeeze: in a narrow card the buttons drop to a second line instead of the primary
   "Start table" breaking its label across two lines. One filled primary (Start), one outline secondary (Claim),
   and a quiet Cancel — a single clear hierarchy. */
.group-gathering__actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 0.5rem; }
.group-gathering__actions .gd__play { flex: 0 0 auto; white-space: nowrap; }
.group-gathering__actions .gd__btn2 { white-space: nowrap; }
/* Disabled Start reads as clearly unavailable — a muted, bordered chip — rather than a faded-blue primary, so
   "can't start yet" is unmistakable while seats fill. */
.group-gathering__actions .gd__play:disabled {
    opacity: 1;
    background: var(--bg-secondary);
    color: var(--text-muted);
    border: 1px solid var(--border-color);
    box-shadow: none;
}
/* Cancel: quiet at rest (a ghost button), reddening on hover to own its destructive intent without shouting.
   Selector leads with .app-btn so it out-specifies the shared .app-btn--ghost:hover (which sets text-primary and
   loads after this file); without that lead the two tie and the ghost hover would win the cascade. */
.app-btn.group-gathering__cancel:hover:not(:disabled) { color: var(--accent-red-ink); }

/* ---- The schedule tab: planned sessions + an admin create form ---- */
.group-schedule { display: flex; flex-direction: column; gap: 0.8rem; }
.group-schedule__form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.8rem;
    border-radius: 0.9rem;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
}
.group-schedule__field { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.group-schedule__label { font-size: 0.72rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.group-schedule__when { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.group-schedule__when .group-schedule__field { flex: 1 1 8rem; }
.group-schedule__hint { margin: 0; font-size: 0.76rem; color: var(--text-muted); }
.group-schedule__list { display: flex; flex-direction: column; gap: 0.6rem; }

.group-session {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.7rem;
    border-radius: 0.9rem;
    background: color-mix(in srgb, var(--accent-blue) 5%, var(--bg-secondary));
    border: 1px solid color-mix(in srgb, var(--accent-blue) 30%, var(--border-color));
}
.group-session__head { display: flex; align-items: center; gap: 0.5rem; }
.group-session__icon { font-size: 1.4rem; line-height: 1; }
.group-session__title-wrap { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.group-session__title { font-size: 0.98rem; color: var(--text-primary); }
.group-session__game { font-size: 0.74rem; color: var(--text-muted); }
.group-session__time { font-size: 0.86rem; font-weight: 600; color: var(--text-secondary); }
.group-session__summary { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.group-session__counts { font-size: 0.78rem; color: var(--text-muted); }
.group-session__avatars { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.group-session__avatar { --avatar-size: var(--avatar-xs); }
.group-session__actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem; }

.group-rsvp { display: flex; gap: 0.35rem; }
.group-rsvp__btn {
    flex: 1 1 auto;
    padding: 0.35rem 0.5rem;
    font-size: 0.8rem;
    font-weight: 600;
    border-radius: 0.6rem;
    border: 1px solid var(--border-color);
    background: var(--bg-card);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.group-rsvp__btn:hover { background: var(--bg-hover); }
.group-rsvp__btn--active {
    border-color: color-mix(in srgb, var(--accent-blue) 55%, transparent);
    background: color-mix(in srgb, var(--accent-blue) 14%, var(--bg-card));
    color: var(--text-primary);
}

/* ---- The clubhouse: a play-first main column with a persistent chat/presence rail ---- */

/* Two-column clubhouse: the tab body is the main column, the floating conversation rail sits beside it.
   The page is the single scroll context; the rail is a floating panel whose conversation timeline scrolls
   internally (the only nested scroll). Mirrors the .game-room__body row→column collapse — desktop row, mobile
   column with the rail leading. */
.group-clubhouse { display: flex; gap: 0.9rem; align-items: flex-start; }
/* The main column grows with its content and never scrolls on its own: the shell's .app-main is the single
   scroll context here, so a drag over the play shelves or history chains out to it. */
.group-clubhouse__main { flex: 1 1 auto; min-width: 0; }
.group-rail {
    flex: 0 0 20rem;
    width: 20rem;
    max-width: 40vw;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-height: 0;          /* let the rail shrink so height passes to the timeline, the only scroller */
    overflow: visible;      /* the rail never scrolls — the conversation timeline is the single nested scroll */
    position: sticky;
    top: 0.5rem;   /* keep the conversation in view while the main column scrolls */
    max-height: calc(100dvh - 1rem);
}
/* The unified conversation panel fills the rail so its timeline scrolls internally while the composer + voice
   controls stay pinned at the bottom. */
.group-rail .group-convo { flex: 1 1 auto; min-height: 0; }
.group-rail .group-convo .group-chat__log { flex: 1 1 auto; max-height: none; }
/* The rail's title: a quiet heading that names the chat + activity stream as one surface. Sized just under the
   in-body section titles so it reads as a panel label, not a page heading. */
.group-rail__header { display: flex; align-items: center; gap: 0.4rem; padding: 0 0.15rem; flex: 0 0 auto; }
.group-rail__title { margin: 0; font-size: 0.98rem; font-weight: 700; color: var(--text-primary); }


/* Collapse: below a tablet width the rail can't spare the room — the conversation follows the main play
   column in document order, and the log takes a bounded mobile height so it doesn't push the whole page. The
   page is the single scroll context; the conversation panel scrolls its own timeline. */
@media (max-width: 56rem) {
    .group-clubhouse { flex-direction: column; }
    /* Column layout: give the main column a definite width. As a flex-row item its min-width:0 supplied the
       definite width its nested Live-games rail (a .shelf__row) resolves against; stacked in a column that no
       longer holds, so without this the column sizes to its content — the un-scrollable rail — and blows past
       the viewport (the rail then can't scroll, and Recently-played spills off-screen). width:100% pins it to
       the container so the rail scrolls internally again. Mirrors the rail below. */
    .group-clubhouse__main { width: 100%; }
    .group-rail { flex: 0 0 auto; width: 100%; max-width: none; position: static; max-height: none; }
    .group-rail .group-convo { min-height: 0; }
    .group-rail .group-convo .group-chat__log { flex: 0 0 auto; max-height: 18rem; }
}


/* The invite modal's QR canvas renders larger than the inline default so it scans cleanly without cropping. */
.invite-modal { display: flex; flex-direction: column; align-items: stretch; gap: 0.75rem; }
.invite-modal .invite-qr--inline .invite-qr__canvas { width: 180px; height: 180px; }
