/*
 * KLANS design system
 * Near-black charcoal + saturated green gaming accent, dense/compact cards.
 * See docs/KLANS_MASTER_PROMPT.md section 77 for the approved direction
 * and docs/design/*.png for reference mockups (landing, homepage, profile,
 * clan, game, event, messages, admin).
 *
 * Foundation (colors, buttons, forms, surfaces, topbar, footer, guest
 * landing, auth pages, home shell) was built first, page by page, and is
 * already close to the mockups. The "Design system: shared components"
 * section further down was added afterward as a dedicated pass: it
 * extracts the recurring pieces visible across ALL 8 mockups - stat
 * tiles, right-rail widgets, role/status pills, tabs, a profile/clan
 * banner header, post cards, a data table, date chips, progress bars -
 * into one reusable vocabulary so each page template can be rebuilt
 * against shared classes instead of inventing its own inline styles.
 * --accent was also retuned in this pass: the old #79d94c was a lighter,
 * more pastel green than what the mockups actually use - sampling the
 * REGISTER/JOIN buttons and the "K" logo letter across every mockup
 * converges on rgb(84-88, 144-149, 31-34), i.e. #58a121, a more
 * saturated, less washed-out green.
 */

:root {
    --bg: #07090b;
    --surface: #0e1317;
    --surface-hover: #151b20;
    --surface-alt: #0b0f13;
    /*
     * The raised panel one step above --surface: the hero card on a game and
     * event page, the active row in the conversation list, a received message
     * bubble, the admin stat tiles.
     *
     * Ten declarations reached for this token and none of them defined it. An
     * undefined var() with no fallback makes the whole declaration invalid at
     * computed-value time, so every one of those backgrounds computed to
     * transparent: the game hero was a bare outline on the page background, the
     * active conversation had no highlight, and a received message had no
     * bubble at all. It is a real level in the scale, not an alias - sitting
     * above --surface-hover so an active row still reads as more than a hover.
     */
    --surface-2: #161d24;
    --border: #252c31;
    --border-soft: #1b2126;
    --text: #ecf0f1;
    --muted: #8d969d;
    --muted-2: #778087;
    /*
     * Body copy that should sit back from --text without dropping all the way
     * to --muted: a clan or profile description, a definition-list value. Same
     * story as --surface-2 - seven uses, no definition. color is inherited, so
     * the invalid value fell back to --text and those paragraphs rendered at
     * full brightness instead of one step down.
     */
    --text-soft: #b9c1c7;
    /*
     * Sampled directly from docs/design/*.png (REGISTER/JOIN buttons and the
     * "K" logo letter converge on rgb(84-88, 144-149, 31-34) across every
     * mockup) - a more saturated, less pastel green than the old #79d94c.
     */
    --accent: #58a121;
    --accent-dim: #508f1d;
    --accent-fg: #07090b;
    --accent-soft: rgba(88, 161, 33, 0.14);
    --danger: #e35d5d;
    --danger-soft: rgba(227, 93, 93, 0.14);
    --warning: #e0b34d;
    --warning-soft: rgba(224, 179, 77, 0.14);
    --info: #5d9fe3;
    --info-soft: rgba(93, 159, 227, 0.14);
    /* Role/rank accent used for admin & moderator badges (distinct from the
       brand green, which is reserved for owner/primary actions). */
    --role-mod: #a56de0;
    --role-mod-soft: rgba(165, 109, 224, 0.14);
    /*
     * Tag tints (2026-09-19). Eight hues a hashtag is assigned by hash of its
     * slug - see tag_tint() in includes/helpers.php for why it is eight and
     * why it is a hash.
     *
     * Solved rather than picked: each one is its hue held at the same relative
     * luminance (0.30), which lands every tint at ~6.6:1 on --bg and ~6.2:1 on
     * --surface. Equal luminance is what keeps a paragraph ending in sixteen
     * tags from reading as confetti - the hues differ, the weight does not, so
     * no single tag shouts and the block still scans as one run of text.
     * --tag-1 is deliberately the brand green at that luminance, so the most
     * common case still looks like the site.
     *
     * Hues avoid --danger's red: a red hashtag reads as an error.
     */
    --tag-1: #5ca627;
    --tag-2: #38a870;
    --tag-3: #33a3b0;
    --tag-4: #6098da;
    --tag-5: #a186e2;
    --tag-6: #c977cf;
    --tag-7: #da74a1;
    --tag-8: #d28328;
    --radius: 6px;
    --radius-sm: 4px;
    --radius-lg: 12px;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-card: 0 8px 24px rgba(0, 0, 0, 0.35);
    /* One step above --shadow-card, for a popover that floats clear of the card
       it belongs to. Used by the repost form and, like the tokens above, never
       defined - so that popover had no shadow and no separation at all. */
    --shadow-lg: 0 18px 48px rgba(0, 0, 0, 0.5);
    --max-width: 1700px;
    --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --type-xs: 11px;
    --type-sm: 12px;
    --type-body: 14px;
    --type-title: 18px;
    --type-page: 26px;
}

* {
    box-sizing: border-box;
}

html {
    color-scheme: dark;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/*
 * The install offer and its dismiss, docked together.
 *
 * max() rather than a bare env(): the safe-area inset is 0 on nearly every
 * device, and a bare env() would collapse the 18px the design asked for. On a
 * notched iPhone in standalone mode this used to sit under the home indicator,
 * where a tap either does nothing or swipes the app away.
 */
.pwa-install-dock {
    position: fixed;
    right: max(18px, env(safe-area-inset-right));
    bottom: max(18px, calc(env(safe-area-inset-bottom) + 12px));
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* [hidden] is display:none from the UA sheet, which the rule above outranks. */
.pwa-install-dock[hidden] {
    display: none;
}

.pwa-install {
    box-shadow: var(--shadow-card);
}

.pwa-install-dismiss {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--surface);
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    box-shadow: var(--shadow-card);
}

.pwa-install-dismiss:hover {
    border-color: var(--border);
    color: var(--text);
    background: var(--surface-hover);
}

/*
 * The button is fixed and has no dismiss, so for as long as it is on screen it
 * sits on top of whatever the page happens to put in that corner: the Privacy
 * Policy link on the register form, the last clan card on the directory, the
 * footer links everywhere else. app.js marks the document while the floating
 * button is showing, and the page reserves the height it occupies.
 */
.pwa-install-visible body {
    padding-bottom: 76px;
}

/* The dock as well as the button: hiding only the button would have left the
   dismiss circle floating on its own in the corner of the installed app. */
.pwa-standalone .pwa-install,
.pwa-standalone .pwa-install-dock {
    display: none;
}

/* ---- Shared confirmation dialog ---------------------------------- */

.confirm-dialog {
    width: min(440px, calc(100vw - 28px));
    max-width: none;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 24px 80px rgba(0, 0, 0, .68);
}

.confirm-dialog::backdrop {
    background: rgba(0, 0, 0, .72);
    backdrop-filter: blur(3px);
}

.confirm-dialog-panel {
    display: grid;
    grid-template-columns: 42px minmax(0, 1fr);
    gap: 14px;
    padding: 22px;
}

.confirm-dialog-icon {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    border: 1px solid var(--danger);
    border-radius: 50%;
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 20px;
    font-weight: 800;
}

.confirm-dialog-copy h2 { margin: 1px 0 6px; font-size: 17px; }
.confirm-dialog-copy p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.55; }
.confirm-dialog-actions { grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }

@media (max-width: 460px) {
    .confirm-dialog-panel { padding: 18px; }
    .confirm-dialog-actions .btn { flex: 1 1 0; }
}

.report-control {
    color: var(--muted);
    font-size: 12px;
}

.report-control summary:hover {
    color: var(--text);
}

.report-form {
    display: grid;
    gap: 7px;
    width: 230px;
}

.report-form label {
    display: grid;
    gap: 4px;
    color: var(--muted);
    font-size: 11px;
}

.report-form select,
.report-form textarea {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text);
    font: inherit;
    padding: 6px 8px;
}

.comment-edit-control { position:relative; display:inline-block; }.comment-edit-control > summary { cursor:pointer; list-style:none; }.comment-edit-control > summary::-webkit-details-marker { display:none; }.comment-edit-form { position:absolute; left:0; z-index:24; display:grid; gap:7px; width:min(320px,calc(100vw - 40px)); margin-top:6px; padding:10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface); box-shadow:var(--shadow-card); }.comment-edit-form textarea { width:100%; resize:vertical; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface-alt); color:var(--text); font:inherit; padding:7px 9px; }.comment-edit-form .btn { justify-self:end; }
.post-comments { margin-top:12px; padding-top:12px; border-top:1px solid var(--border-soft); scroll-margin-top:88px; }
.comment-item { min-width:0; margin-bottom:7px; }
.comment-row { display:flex; align-items:flex-start; gap:8px; }
.comment-row > .avatar-img, .comment-row > .avatar-fallback { flex:0 0 32px; width:32px; height:32px; font-size:11px; }
.comment-body { flex:1; min-width:0; }
.comment-bubble { padding:8px 11px; border:1px solid transparent; border-radius:0 var(--radius-sm) var(--radius-sm) var(--radius-sm); background:var(--surface-alt); }
.comment-bubble.has-attachment { width:fit-content; max-width:100%; }
.comment-bubble:hover { border-color:var(--border-soft); }
.comment-meta { display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; font-size:13px; }
.comment-meta a, .comment-meta strong { color:var(--text); font-weight:700; }
.comment-meta time { color:var(--muted); font-size:11px; }
.comment-copy { margin-top:3px; overflow-wrap:anywhere; white-space:pre-wrap; font-size:14px; line-height:1.45; }
.comment-actions { display:flex; align-items:center; gap:10px; flex-wrap:wrap; min-height:24px; margin:2px 0 0 10px; color:var(--muted); font-size:12px; }
.comment-actions form { margin:0; }
.comment-action, .comment-edit-control > summary { display:inline-flex; align-items:center; gap:4px; height:auto; padding:2px 0; border:0; background:transparent; color:var(--muted); font:inherit; cursor:pointer; }
.comment-action:hover, .comment-edit-control > summary:hover { color:var(--text); text-decoration:none; }
.comment-action.is-active { color:var(--accent); }
.comment-composer { display:flex; align-items:flex-end; flex-wrap:wrap; gap:8px; margin:10px 0 0; }
.comment-composer textarea { flex:1; min-width:0; min-height:38px; max-height:150px; padding:8px 10px; resize:vertical; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface-alt); color:var(--text); font:inherit; font-size:13px; }
.composer-reply-context { display:flex; flex:1 0 100%; align-items:center; gap:8px; margin:0; padding:5px 10px; border-radius:var(--radius-sm); background:var(--surface-alt); color:var(--muted); font-size:12px; }
.composer-reply-context[hidden] { display:none; }
.composer-reply-context strong { color:var(--text); }
.composer-reply-context button { margin-left:auto; }
.comment-media-button { flex:0 0 auto; }
.comment-media-preview { order:10; flex:1 0 100%; width:100%; margin:0; }
.comment-video-poster { order:11; flex:1 0 100%; width:100%; margin:0; }
/* max-width as well as width, and it is not a duplicate of it.
   The bubble above is width:fit-content, so it is sized by what its children
   say they would like to be - and an image asks for its natural width, not the
   520 it is painted at. A 1920-wide screenshot therefore stretched the bubble
   to ~780px around a 520px picture and left a quarter of the bubble as bare
   background: the "huge block border" behind a comment's media. A percentage
   in width does not clamp that intrinsic contribution; an explicit max-width
   does, so the bubble now ends where the picture ends. */
.comment-media { display:block; overflow:hidden; width:min(100%,520px); max-width:520px; margin-top:8px; border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:#060708; }
.comment-media img, .comment-media video { display:block; width:100%; max-height:440px; object-fit:contain; }
.comment-rich-attachment { width:min(100%,520px); max-width:520px; }
.comment-embed { position:relative; overflow:hidden; width:min(100%,520px); max-width:520px; margin-top:8px; aspect-ratio:16 / 9; border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:#060708; }
.comment-embed iframe { display:block; width:100%; height:100%; border:0; }
.comment-link-card { display:grid; grid-template-columns:112px minmax(0,1fr); overflow:hidden; width:min(100%,520px); min-height:82px; margin-top:8px; border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:var(--surface); color:var(--text); text-decoration:none; }
.comment-link-card:hover { border-color:var(--border); text-decoration:none; }
.comment-link-card > img { width:112px; height:100%; min-height:82px; object-fit:cover; background:#060708; }
.comment-link-card > span { display:grid; align-content:center; min-width:0; gap:3px; padding:9px 11px; }
.comment-link-card small { overflow:hidden; color:var(--muted); font-size:10px; font-weight:700; letter-spacing:.04em; text-overflow:ellipsis; text-transform:uppercase; white-space:nowrap; }
.comment-link-card strong { overflow:hidden; font-size:13px; text-overflow:ellipsis; white-space:nowrap; }
.comment-link-card > span > span { display:-webkit-box; overflow:hidden; color:var(--muted); font-size:11px; line-height:1.35; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
.comment-replies { margin:5px 0 6px 16px; padding-left:15px; border-left:1px solid color-mix(in srgb,var(--accent) 28%,var(--border-soft)); }
.comment-replies > summary { width:max-content; max-width:100%; margin-left:-8px; padding:2px 7px; cursor:pointer; color:var(--muted); font-size:11px; font-weight:600; list-style:none; border-radius:999px; background:var(--surface); }
.comment-replies > summary::-webkit-details-marker { display:none; }.comment-replies > summary::before { content:'▾'; display:inline-block; width:14px; color:var(--accent); }.comment-replies:not([open]) > summary::before { content:'▸'; }.comment-replies-list { margin-top:5px; }.comment-load-more { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin:0 0 10px; color:var(--muted); font-size:12px; }
@media (max-width:640px) { .comment-replies { margin-left:8px; padding-left:9px; }.comment-row { gap:6px; }.comment-composer { align-items:stretch; flex-direction:column; margin-left:0; }.comment-composer .btn { width:100%; }.comment-link-card { grid-template-columns:82px minmax(0,1fr); }.comment-link-card > img { width:82px; } }

a {
    color: var(--accent);
    text-decoration: none;
}

/*
 * :where() so the pseudo-class adds no specificity.
 *
 * "a:hover" is a type plus a pseudo-class, which outranks a single class - so
 * every component that turns the underline off, from the event cards on the
 * landing page to the footer and settings navigation, had it forced back on the
 * moment a pointer touched it. A card is one <a> wrapping a date, a title, a
 * meta line and a count, and text-decoration draws through all of them: the
 * whole card underlined at once, four lines of it.
 *
 * Written this way the rule still reaches every ordinary inline link, which
 * sets no text-decoration of its own, while any component that says
 * "text-decoration: none" keeps it in every state without having to repeat
 * itself in a :hover rule.
 */
a:where(:hover) {
    text-decoration: underline;
}

img {
    max-width: 100%;
}

.ui-icon {
    display: inline-block;
    flex: 0 0 auto;
    overflow: visible;
    vertical-align: -0.16em;
}

h1, h2, h3, h4 {
    margin: 0 0 12px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

h1, .type-page { font-size: var(--type-page); line-height: 1.18; }
h2, .type-title { font-size: var(--type-title); line-height: 1.25; }
h3 { font-size: 15px; line-height: 1.3; }
.type-eyebrow { font-size: var(--type-xs); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.type-meta { font-size: var(--type-sm); color: var(--muted); }

p {
    margin: 0 0 12px;
}

/*
 * The gutter is floored at the notch inset rather than replaced by it, so a
 * phone held on its side does not put the first word of every line under the
 * camera cutout. max() keeps the 20px the design asked for on the hardware -
 * almost all of it - where the inset is 0.
 */
.container {
    max-width: var(--max-width);
    margin: 0 auto;
    padding-top: 0;
    padding-bottom: 0;
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
}

/* ---- Buttons ---------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 18px;
    font-size: 13px;
    font-weight: 600;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    cursor: pointer;
    line-height: 1.2;
    white-space: nowrap;
}

.btn-primary {
    background: var(--accent);
    color: var(--accent-fg);
}

.btn-primary:hover {
    background: var(--accent-dim);
    text-decoration: none;
}

.btn-outline {
    background: transparent;
    border-color: var(--border);
    color: var(--text);
}

.btn-outline:hover {
    background: var(--surface-hover);
    text-decoration: none;
}

.btn-ghost {
    background: transparent;
    color: var(--muted);
}

.btn-ghost:hover {
    color: var(--text);
    text-decoration: none;
}

.btn-danger {
    background: transparent;
    border-color: var(--danger);
    color: var(--danger);
}

.btn-block {
    width: 100%;
}

.btn-sm {
    padding: 6px 12px;
    font-size: 12px;
}

.btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn.is-loading { cursor:wait; pointer-events:none; }
.btn.is-loading::after { content:""; width:12px; height:12px; flex:0 0 auto; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; animation:ui-spin .7s linear infinite; }
form[aria-busy="true"] { cursor:wait; }
@keyframes ui-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion:reduce) { .btn.is-loading::after { animation-duration:1.8s; } }

.btn:focus-visible,
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ---- Forms -------------------------------------------------------- */

.field {
    margin-bottom: 16px;
}

.field label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 6px;
}

/*
 * Every text-entry control, not the four types this list used to name.
 *
 * A type missing from here got no styling at all: inside a .field an
 * input[type="url"] measured 21px tall and 177px wide against the 43px,
 * full-width box beside it, on the browser's own grey, because none of the
 * declarations below reached it. That was true of url, number, tel, date,
 * datetime-local, time, month and week - which is most of event editing, the
 * clan website and voice fields, and much of admin.
 *
 * The workaround that grew up around it is a $fieldStyle string re-typed
 * inline in five templates. Those can now go back to the class.
 *
 * Spelled out type by type rather than as input:not(...): a :not() chain takes
 * the specificity of its most specific argument, which would put this above
 * every single-class override in the file. This project has been bitten three
 * times by a rule that quietly outranked the one meant to win, and a form
 * control is the last place to invite it back.
 */
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="search"],
.field input[type="url"],
.field input[type="tel"],
.field input[type="number"],
.field input[type="date"],
.field input[type="datetime-local"],
.field input[type="time"],
.field input[type="month"],
.field input[type="week"],
.field textarea,
.field select {
    width: 100%;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    padding: 10px 12px;
    font-size: 14px;
    font-family: inherit;
}

.field input:focus,
.field textarea:focus,
.field select:focus {
    outline: none;
    border-color: var(--accent);
}

textarea,
select,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"],
input[type="week"] {
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
}

textarea:focus,
select:focus,
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="url"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="datetime-local"]:focus,
input[type="time"]:focus,
input[type="month"]:focus,
input[type="week"]:focus {
    border-color: var(--accent);
    outline: none;
}

/*
 * The two types that cannot take the box above. A colour well is a swatch, not
 * a text field, and a file control is a button plus a filename - padding and a
 * full-width background on either one looks broken rather than styled. They get
 * the surface and the border so they belong to the same form, and nothing else.
 */
input[type="file"] {
    max-width: 100%;
    padding: 9px 11px;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--muted);
    font: inherit;
    font-size: 13px;
}

.field input[type="file"] {
    width: 100%;
}

input[type="file"]::file-selector-button {
    margin-right: 10px;
    padding: 6px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-hover);
    color: var(--text);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

input[type="file"]::file-selector-button:hover {
    border-color: var(--accent-dim);
    color: var(--accent);
}

input[type="color"] {
    width: 44px;
    height: 40px;
    padding: 3px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    cursor: pointer;
}

.field .hint {
    margin-top: 6px;
    font-size: 12px;
    color: var(--muted-2);
}

.field .error {
    margin-top: 6px;
    font-size: 12px;
    color: var(--danger);
}

.field.has-error input,
.field.has-error textarea {
    border-color: var(--danger);
}

.checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--muted);
}

/* ---- Surfaces ------------------------------------------------------ */

.card {
    background: var(--surface);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
}

.card-pad {
    padding: 20px;
}

.divider {
    border: none;
    border-top: 1px solid var(--border-soft);
    margin: 16px 0;
}

/* ---- Flash messages ------------------------------------------------ */

.flash-stack {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 16px;
}

.flash {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    border: 1px solid var(--border);
}

.flash-icon { display:inline-grid; place-items:center; flex:0 0 18px; width:18px; height:18px; border:1px solid currentColor; border-radius:50%; font-size:11px; font-weight:800; line-height:1; }.flash-message { flex:1; padding-top:1px; }.flash-dismiss { flex:0 0 auto; width:22px; height:22px; margin:-2px -4px -2px 0; border:0; border-radius:5px; background:transparent; color:currentColor; cursor:pointer; font:inherit; font-size:19px; line-height:1; opacity:.72; }.flash-dismiss:hover, .flash-dismiss:focus-visible { opacity:1; background:rgba(255,255,255,.1); }

.flash-success { border-color: var(--accent-dim); color: var(--accent); background: var(--accent-soft); }
.flash-error { border-color: var(--danger); color: var(--danger); background: rgba(227, 93, 93, 0.08); }
.flash-info { border-color: var(--info); color: var(--info); background: rgba(93, 159, 227, 0.08); }
.flash-warning { border-color: var(--warning); color: var(--warning); background: rgba(224, 179, 77, 0.08); }

/* ---- Top navigation -------------------------------------------------- */

/*
 * The layouts declare apple-mobile-web-app-status-bar-style black-translucent,
 * which asks iOS to draw the app under the status bar - and then nothing
 * anywhere accounted for it, so an installed PWA put this bar behind the clock
 * and the battery. The viewport meta now carries viewport-fit=cover, without
 * which every env() in this file resolves to zero and none of it does anything.
 */
.topbar {
    background: var(--bg);
    border-bottom: 1px solid var(--border-soft);
    position: sticky;
    top: 0;
    z-index: 40;
    padding-top: env(safe-area-inset-top);
}

.topbar-inner {
    display: flex;
    align-items: center;
    gap: 24px;
    height: 56px;
}

.brand {
    display: flex;
    align-items: center;
    gap: 2px;
    font-size: 18px;
    font-weight: 800;
    letter-spacing: 0.02em;
    color: var(--text);
    flex-shrink: 0;
}

.brand:hover {
    text-decoration: none;
}

.brand .k { color: var(--accent); }

.brand-logo { width: auto; max-width: 180px; max-height: 28px; object-fit: contain; }

.topnav {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 1;
    /* A flex item defaults to min-width:auto, which means it refuses to shrink
       below its content and pushes the whole topbar wider than the page instead.
       That is the real cause of the horizontal scrollbar signed-in members saw
       between 900px and 1200px; it was about 25px before Artwork joined the nav
       and 101px after. min-width:0 lets it shrink, and the overflow goes inside
       the nav where it can be scrolled rather than onto the document. */
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
}

.topnav::-webkit-scrollbar { display: none; }

/* The nav runs out of room before the rest of the layout does - eight sections
   plus a search field plus the account menu - so it takes a full row of its own
   at 1150px rather than waiting for the 900px tablet block below. Its own
   breakpoint, because it is the nav's problem and not the page's. */
@media (max-width: 1150px) {
    .topbar-inner { height: auto; min-height: 56px; flex-wrap: wrap; gap: 8px 12px; padding-top: 6px; padding-bottom: 6px; }
    /*
     * Once the nav takes a row of its own it scrolls sideways with its
     * scrollbar hidden, and a signed-in member had no way to know that Messages
     * and Friends were off the right-hand edge.
     *
     * Four background layers rather than a mask, so the fade appears only when
     * there is somewhere to scroll to. The first two are attached local and
     * travel with the content, covering the fade at whichever end the content
     * already reaches; the last two are attached to the box and stay put. At
     * rest both fades are covered, scrolling right uncovers the left one, and a
     * nav that fits is never faded at all - which a plain mask could not do, and
     * it would have dimmed "Artwork" for every guest, who has no overflow.
     */
    .topnav {
        order: 3;
        flex: 0 0 100%;
        gap: 0;
        background:
            linear-gradient(90deg, var(--bg) 45%, transparent) left center / 26px 100% no-repeat local,
            linear-gradient(90deg, transparent, var(--bg) 55%) right center / 26px 100% no-repeat local,
            linear-gradient(90deg, var(--bg), transparent) left center / 24px 100% no-repeat scroll,
            linear-gradient(90deg, transparent, var(--bg)) right center / 24px 100% no-repeat scroll;
    }
    .topbar-actions { margin-left: auto; gap: 6px; }
}

.topnav a {
    color: var(--muted);
    font-weight: 600;
    font-size: 13px;
    padding: 8px 12px;
    border-radius: var(--radius-sm);
}

.topnav a:hover {
    color: var(--text);
    background: var(--surface-hover);
    text-decoration: none;
}

.topnav a.active {
    color: var(--accent);
}

.topbar-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}

.search-box {
    display: none;
}

/* Below the inline search breakpoint the topbar keeps a compact link to the
   search page, so search is never unreachable on a phone or tablet. */
.search-compact {
    display: inline-flex;
    align-items: center;
}

@media (min-width: 900px) {
    .search-compact {
        display: none;
    }
    .search-box {
        display: block;
        width: 260px;
    }
    .search-box input {
        width: 100%;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        color: var(--text);
        padding: 7px 10px;
        font-size: 13px;
    }
}

.user-menu {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text);
    font-weight: 600;
    font-size: 13px;
}

/* ---- Shared dropdowns and action popovers ------------------------ */

.klans-disclosure { position: relative; }
.klans-disclosure > .klans-disclosure-trigger { cursor: pointer; list-style: none; }
.klans-disclosure > .klans-disclosure-trigger::-webkit-details-marker { display: none; }
.klans-popover { position: absolute; top: calc(100% + 7px); left: 0; z-index: 50; margin: 0; padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); box-shadow: var(--shadow-card); }
.klans-popover-right { right: 0; left: auto; }
.klans-popover-up { top: auto; bottom: calc(100% + 8px); }
.klans-menu { min-width: 168px; padding: 6px; }
.klans-menu a, .klans-menu button { display: block; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--text); font: inherit; font-size: var(--type-sm); text-align: left; cursor: pointer; }
.klans-menu a:hover, .klans-menu button:hover { background: var(--surface-hover); color: var(--accent); text-decoration: none; }

.account-menu > summary {
    padding: 4px 6px;
    border-radius: var(--radius-sm);
}

.account-menu > summary:hover, .account-menu[open] > summary { background: var(--surface-hover); }
.account-menu-chevron { color: var(--muted); transition: transform 120ms ease; }
.account-menu[open] .account-menu-chevron { transform: rotate(180deg); }

.account-menu-panel {
    top: calc(100% + 8px);
}

.skip-link { position:fixed; z-index:100; top:10px; left:10px; transform:translateY(-160%); padding:10px 14px; border-radius:var(--radius-sm); background:var(--accent); color:var(--accent-fg); font-size:13px; font-weight:750; text-decoration:none; box-shadow:var(--shadow-card); transition:transform 120ms ease; }.skip-link:focus { transform:translateY(0); }

.avatar-img, .avatar-fallback {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    object-fit: cover;
    background: var(--surface-hover);
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    flex-shrink: 0;
}

/* ---- Footer ---------------------------------------------------------- */

.site-footer {
    border-top: 1px solid var(--border-soft);
    padding-top: 34px;
    /* The last thing on the page, so it is what ends up behind the home
       indicator on a phone. Floored at the 16px it already had. */
    padding-bottom: max(16px, env(safe-area-inset-bottom));
    padding-left: 0;
    padding-right: 0;
    color: var(--muted-2);
    font-size: 12px;
    margin-top: 40px;
}

.site-footer a {
    color: var(--muted);
}
.site-footer-grid { display:grid; grid-template-columns:minmax(220px,1.25fr) repeat(3,minmax(120px,.62fr)) minmax(210px,1fr); gap:28px; }.site-footer-brand .brand { display:inline-flex; margin-bottom:10px; }.site-footer-brand p, .site-footer-cta p { max-width:260px; margin:0; color:var(--muted); line-height:1.55; }.site-footer nav { display:grid; align-content:start; gap:8px; }.site-footer h2 { margin:0 0 3px; color:var(--text); font-size:12px; text-transform:uppercase; letter-spacing:.05em; }.site-footer nav a { text-decoration:none; }.site-footer nav a:hover { color:var(--accent); }.site-footer-cta { display:grid; align-content:start; gap:9px; }.site-footer-cta .btn { justify-self:start; margin-top:3px; }.site-footer-bottom { display:flex; justify-content:space-between; gap:12px; margin-top:28px; padding-top:14px; border-top:1px solid var(--border-soft); color:var(--muted-2); }

/* ---- Guest landing ----------------------------------------------------- */

.guest-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 64px;
}

.guest-nav-actions {
    display: flex;
    gap: 10px;
}

.hero {
    position: relative;
    min-height: 460px;
    display: flex;
    align-items: center;
    background: linear-gradient(180deg, rgba(11, 14, 17, 0.4), rgba(11, 14, 17, 0.95)), radial-gradient(circle at 70% 30%, #1a1f24, var(--bg));
    border-bottom: 1px solid var(--border-soft);
}

.guest-topbar .topbar-inner { height:70px; max-width:1400px; }
.guest-topbar .brand { font-size:24px; }
.guest-topbar .topbar-actions .btn { min-width:126px; text-transform:uppercase; }
.guest-hero {
    min-height:430px;
    overflow:hidden;
    background:#0b0f13;
}
.guest-hero::before {
    content:"";
    position:absolute;
    inset:-2%;
    background-image:linear-gradient(90deg,rgba(7,11,16,.96) 0%,rgba(7,11,16,.79) 34%,rgba(7,11,16,.38) 65%,rgba(7,11,16,.2) 100%),var(--guest-hero,linear-gradient(135deg,#111820,#07090b));
    background-position:center;
    background-size:cover;
    background-repeat:no-repeat;
    animation:guest-hero-reveal 4.5s cubic-bezier(.22,.61,.36,1) both;
    will-change:opacity,filter;
    pointer-events:none;
}
@keyframes guest-hero-reveal {
    0% { opacity:.82; filter:blur(4px) saturate(.94); }
    35% { opacity:.94; filter:blur(1px) saturate(.98); }
    100% { opacity:1; filter:blur(0) saturate(1); }
}
@media (prefers-reduced-motion:reduce) { .guest-hero::before { animation:none; filter:none; transform:none; } }
.guest-hero .container { width:100%; max-width:1400px; }
.guest-hero > .container { position:relative; z-index:1; }
.guest-hero .hero-content { max-width:560px; padding:54px 0; }
.guest-hero h1 { margin:0 0 8px; font-size:clamp(58px,5.4vw,82px); font-weight:900; letter-spacing:-.045em; }
.guest-hero .tagline { font-size:15px; }
.guest-content { max-width:1680px; padding-top:28px; padding-bottom:14px; }
.guest-featured { margin-top:0; scroll-margin-top:92px; }
.guest-games-grid { grid-template-columns:repeat(8,minmax(0,1fr)); gap:14px; }
/*
 * The game poster, shared by the landing page and the games directory.
 *
 * These rules used to be scoped to .guest-featured, so the directory grew a
 * card of its own - a 110px landscape strip with the title underneath - and the
 * same game looked like two different things depending on how you arrived at
 * it. They are on the component now; each page brings only its own grid.
 */
/* Two classes, not one. .game-card is declared further down this file and sets
   aspect-ratio 3/4, a flat background, padding and a border radius; at one
   class each it would win on order alone and the poster would render 3:4. The
   landing page's old rule carried two classes for exactly this reason. */
.game-card.game-poster { isolation:isolate; position:relative; display:flex; flex-direction:column; align-items:stretch; justify-content:flex-end; min-width:0; overflow:hidden; padding:0; border-radius:12px; background:linear-gradient(145deg,var(--surface-hover),var(--surface)); aspect-ratio:9 / 16; text-decoration:none; font-size:15px; transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease; }
.game-card.game-poster:hover { transform:translateY(-5px); border-color:var(--accent-dim); box-shadow:0 18px 36px rgba(0,0,0,.32); }
/* The scrim is what keeps the title legible on a bright cover as well as a
   dark one, so it is part of the card rather than a landing-page flourish. */
.game-poster::after { content:""; position:absolute; z-index:1; inset:0; background:linear-gradient(180deg,rgba(6,8,10,.04) 34%,rgba(6,8,10,.54) 62%,rgba(6,8,10,.98) 100%); pointer-events:none; }
.game-poster-art { position:absolute; inset:0; background:linear-gradient(145deg,var(--surface-hover),var(--surface)); }
.game-poster-art img { width:100%; height:100%; object-fit:cover; transition:transform .35s ease; }
.game-poster:hover .game-poster-art img { transform:scale(1.035); }
.game-poster-body { position:relative; z-index:2; display:grid; gap:7px; min-width:0; padding:18px 16px 16px; }
.game-poster strong { display:-webkit-box; overflow:hidden; color:#fff; font-size:clamp(15px,1.25vw,18px); line-height:1.22; text-shadow:0 1px 4px #000; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
.game-poster-meta { color:#cfd6db; font-size:11px; font-weight:600; text-shadow:0 1px 3px #000; }
.game-poster-platforms { display:flex; flex-wrap:wrap; gap:5px; min-width:0; }
.game-poster-platforms > span { padding:3px 6px; border:1px solid rgba(255,255,255,.27); border-radius:4px; background:rgba(0,0,0,.5); color:#e4e8eb; font-size:10px; font-weight:700; }
.game-poster-players { position:absolute; z-index:2; top:12px; right:12px; display:inline-flex; align-items:center; gap:5px; padding:5px 8px; border:1px solid rgba(255,255,255,.2); border-radius:999px; background:rgba(5,7,9,.72); color:#fff; font-size:10px; font-weight:700; backdrop-filter:blur(6px); }
.game-poster-players .ui-icon { color:var(--accent); }

/* Reduced motion: the lift and the slow zoom are decoration. */
@media (prefers-reduced-motion:reduce) {
    .game-poster, .game-poster-art img { transition:none; }
    .game-poster:hover { transform:none; }
    .game-poster:hover .game-poster-art img { transform:none; }
}
.guest-events { margin-top:32px; }
.guest-events-empty { padding:18px; border:1px solid var(--border); border-radius:var(--radius-sm); color:var(--muted); font-size:13px; background:var(--surface); }
.guest-section-head { display:flex; align-items:end; justify-content:space-between; gap:16px; margin-bottom:14px; }
.guest-section-head .section-label { margin-bottom:3px; }
.guest-section-head h2 { margin:0; font-size:21px; }
/*
 * The community calendar cards (rebuilt 2026-09-20).
 *
 * They were a single flex row each - date chip, one ellipsed line, a count -
 * which is the shape of a list item, not a card. Three of them across a
 * 1400px container spent most of that width on empty space while truncating
 * the one thing a visitor reads, the title, and the row carried no answer to
 * the questions somebody deciding whether to join actually has: which game,
 * online or in person, who is running it.
 *
 * Now each card is a stack of three bands - head (date + state + title),
 * facts, footer (attendance + the affordance that says this is a link) - so
 * it grows downward with its content instead of sideways into an ellipsis,
 * and the same stack works unchanged at one column on a phone. The lift,
 * radius and shadow are the game poster's and the gallery card's, so the
 * three rows of this page read as one set of cards rather than three.
 */
.guest-events-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px; margin:0; padding:0; list-style:none; }
.guest-events-grid > li { display:grid; min-width:0; }
.guest-event-card { position:relative; display:grid; grid-template-rows:auto 1fr auto; gap:12px; min-width:0; padding:15px 16px 13px; border:1px solid var(--border); border-radius:var(--radius-lg); background:linear-gradient(162deg,var(--surface-hover),var(--surface) 64%); color:var(--text); text-decoration:none; transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease; }
/* The rail is the card's state at a glance, read before any word on it. */
.guest-event-card::before { content:""; position:absolute; inset:0 auto 0 0; width:3px; border-radius:var(--radius-lg) 0 0 var(--radius-lg); background:linear-gradient(180deg,var(--accent),color-mix(in srgb,var(--accent) 18%,transparent)); }
.guest-event-card:hover { transform:translateY(-3px); border-color:var(--accent-dim); box-shadow:0 16px 32px rgba(0,0,0,.3); }

.guest-event-head { display:grid; grid-template-columns:auto minmax(0,1fr); align-items:center; gap:12px; min-width:0; }
.guest-event-date { display:grid; place-items:center; align-content:center; width:50px; height:52px; border:1px solid var(--border-soft); border-radius:10px; background:var(--surface-alt); line-height:1; }
.guest-event-date b { color:var(--accent); font-size:10px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; }
.guest-event-date strong { margin-top:4px; font-size:21px; font-weight:700; letter-spacing:-.02em; }
.guest-event-headline { display:grid; gap:6px; min-width:0; }
.guest-event-pills { display:flex; flex-wrap:wrap; gap:5px; min-width:0; }
/* Two lines, then an ellipsis: a long title should wrap rather than be cut at
   four words, but it may not push the footer of one card out of line with the
   two beside it. */
.guest-event-title { display:-webkit-box; overflow:hidden; font-size:15px; font-weight:700; line-height:1.3; -webkit-box-orient:vertical; -webkit-line-clamp:2; }

.guest-event-meta { display:grid; align-content:start; gap:7px; min-width:0; color:var(--muted); font-size:12px; }
.guest-event-when { display:flex; align-items:center; gap:7px; min-width:0; }
.guest-event-when time { overflow:hidden; color:var(--text-soft); font-weight:650; text-overflow:ellipsis; white-space:nowrap; }
.guest-event-zone { flex:0 1 auto; overflow:hidden; color:var(--muted-2); font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
/* Game, place and organiser on one wrapped line rather than three stacked
   rows: they are one answer - what this is and who runs it - and three rows
   of a single word each made the card taller than the title it belongs to. */
.guest-event-facts { display:flex; flex-wrap:wrap; align-items:center; gap:4px 12px; min-width:0; }
.guest-event-facts > span { display:inline-flex; align-items:center; gap:6px; min-width:0; max-width:100%; }
.guest-event-facts > span > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.guest-event-meta .ui-icon { flex:0 0 auto; color:var(--muted-2); }

.guest-event-foot { display:flex; align-items:center; justify-content:space-between; gap:10px; min-width:0; padding-top:11px; border-top:1px solid var(--border-soft); }
.guest-event-count { display:inline-flex; align-items:baseline; gap:5px; min-width:0; color:var(--muted); font-size:11px; }
.guest-event-count strong { color:var(--text); font-size:15px; font-weight:700; letter-spacing:-.01em; }
.guest-event-cap { color:var(--muted-2); font-size:11px; font-weight:600; }
.guest-event-count.is-full strong { color:var(--warning); }
.guest-event-cta { display:inline-flex; align-items:center; gap:4px; color:var(--accent); font-size:12px; font-weight:700; white-space:nowrap; }
/* The set has no chevron-right; the down one turned a quarter is the same
   glyph at the same stroke weight rather than a second drawing of it. */
.guest-event-cta .ui-icon { transform:rotate(-90deg); transition:transform .18s ease; }
.guest-event-card:hover .guest-event-cta .ui-icon { transform:rotate(-90deg) translateY(3px); }

.guest-event-card.is-live { border-color:color-mix(in srgb,var(--accent) 38%,var(--border)); }
.guest-event-card.is-live .guest-event-date { border-color:color-mix(in srgb,var(--accent) 45%,var(--border)); background:var(--accent-soft); }
.guest-event-card.is-past { background:var(--surface); }
.guest-event-card.is-past::before { background:var(--border); }
.guest-event-card.is-past .guest-event-date { border-style:dashed; }
.guest-event-card.is-past .guest-event-date b { color:var(--muted-2); }
.guest-event-card.is-past .guest-event-cta { color:var(--muted); }

/* The lift and the nudging chevron are decoration. */
@media (prefers-reduced-motion:reduce) {
    .guest-event-card, .guest-event-cta .ui-icon { transition:none; }
    .guest-event-card:hover { transform:none; }
    .guest-event-card:hover .guest-event-cta .ui-icon { transform:rotate(-90deg); }
}
.guest-cta { display:grid; grid-template-columns:64px minmax(0,1fr) auto; margin:26px 0 40px; border-color:var(--accent-dim); background:linear-gradient(100deg,var(--surface),rgba(53,130,24,.12)); }
.guest-cta-mark { width:64px; height:64px; border:1px solid rgba(88,161,33,.45); border-radius:14px; }
.guest-cta-copy { min-width:0; }
main:has(.guest-hero) + .site-footer .container { max-width:1400px; }
main:has(.guest-hero) + .site-footer { margin-top:0; }

.hero-content {
    max-width: 560px;
    padding: 40px 0;
}

.hero-eyebrow {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--muted);
    font-weight: 700;
    font-size: 13px;
    letter-spacing: 0.08em;
    margin-bottom: 8px;
}

.hero-eyebrow::before {
    content: "";
    width: 24px;
    height: 2px;
    background: var(--accent);
    display: inline-block;
}

.hero h1 {
    font-size: 52px;
    line-height: 1.05;
    margin-bottom: 12px;
}

.hero h1 .accent {
    color: var(--accent);
}

.hero .tagline {
    color: var(--accent);
    font-weight: 700;
    font-size: 14px;
    letter-spacing: 0.04em;
    margin-bottom: 16px;
    text-transform: uppercase;
}

.hero p.lead {
    color: var(--muted);
    font-size: 15px;
    max-width: 440px;
    margin-bottom: 24px;
}

.hero-actions {
    display: flex;
    gap: 12px;
}

.section-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 700;
    font-size: 13px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text);
    margin-bottom: 16px;
}

.section-label::before {
    content: "";
    width: 20px;
    height: 2px;
    background: var(--accent);
    display: inline-block;
}

.games-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 14px;
}

.game-card {
    position: relative;
    border-radius: var(--radius);
    overflow: hidden;
    border: 1px solid var(--border-soft);
    aspect-ratio: 3 / 4;
    background: var(--surface);
    display: flex;
    align-items: flex-end;
    padding: 12px;
    color: var(--text);
    font-weight: 700;
}

.game-card .players {
    position: absolute;
    top: 10px;
    right: 10px;
    font-size: 11px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--accent);
}

.cta-strip {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 24px;
    margin: 40px 0;
    flex-wrap: wrap;
}

/* ---- Auth pages ----------------------------------------------------- */

.auth-shell {
    min-height: calc(100vh - 57px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 16px;
}

.auth-shell-illustrated {
    min-height: calc(100svh - 57px - env(safe-area-inset-top));
    background:
        linear-gradient(rgba(3, 8, 11, .42), rgba(3, 8, 11, .42)),
        url("../images/background-login-register-v3.png") center top / cover no-repeat;
}

.auth-shell-illustrated .auth-card {
    background: rgba(12, 16, 19, .94);
    box-shadow: 0 18px 55px rgba(0, 0, 0, .58);
    backdrop-filter: blur(8px);
}

main:has(.auth-shell-illustrated) + .site-footer {
    margin-top: 0;
}

@media (max-width: 700px) {
    .auth-shell-illustrated {
        background-position: 72% top;
    }
}

.auth-card {
    width: 100%;
    max-width: 400px;
}

.auth-card h1 {
    font-size: 22px;
    margin-bottom: 4px;
}

.auth-card .subtitle {
    color: var(--muted);
    font-size: 13px;
    margin-bottom: 24px;
}

.social-buttons {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
}

.social-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 10px 16px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-alt);
    color: var(--text);
    font-weight: 600;
    font-size: 13px;
}

.social-btn:hover {
    background: var(--surface-hover);
    text-decoration: none;
}

.social-btn .icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

/* The steps on the "you already have an account" card: a path, not a warning. */
.auth-steps { margin:0 0 20px; padding-left:20px; color:var(--text); font-size:13px; line-height:1.55; }
.auth-steps li { margin-bottom:10px; }
.auth-steps li::marker { color:var(--muted); font-weight:600; }

.auth-card-register { max-width:520px; }
.social-buttons-register { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); }
.social-btn-provider { display:grid; grid-template-columns:22px minmax(0,1fr) 22px; min-width:0; min-height:46px; padding:10px 14px; border-color:transparent; color:#fff; box-shadow:0 5px 14px rgba(0,0,0,.16); }
.social-btn-provider::after { content:""; width:22px; }
.social-btn-provider > span { overflow:hidden; text-align:center; text-overflow:ellipsis; white-space:nowrap; }
.social-provider-icon { display:block; width:22px; height:22px; object-fit:contain; }
.social-btn-discord { background:#5865f2; }
.social-btn-discord:hover { background:#6571f3; color:#fff; }
.social-btn-steam { background:linear-gradient(135deg,#171a21,#2a475e); border-color:#416887; }
.social-btn-steam:hover { background:linear-gradient(135deg,#202631,#335b77); color:#fff; }
.social-btn-twitch { background:#9146ff; }
.social-btn-twitch:hover { background:#9b5cff; color:#fff; }
.social-btn-google { border-color:#dadce0; background:#fff; color:#202124; }
.social-btn-google:hover { border-color:#c9d5eb; background:#f8faff; color:#202124; }

@media (max-width:560px) {
    .auth-card-register { max-width:400px; }
    .social-buttons-register { grid-template-columns:minmax(0,1fr); }
}

.auth-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--muted-2);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 18px 0;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-soft);
}

.auth-footer-link {
    text-align: center;
    font-size: 13px;
    color: var(--muted);
    margin-top: 18px;
}

/* "Forgot your password?" under the password box. It belongs to the field
 * above it rather than to the form, so it is right-aligned and tight against
 * the input instead of centred like the footer link. */
.auth-inline-link {
    text-align: right;
    font-size: 12px;
    margin: 6px 0 0;
}

/* Terms/privacy consent at the foot of the registration forms. `.field
 * label` is a small uppercase field caption - this one is a sentence, so
 * the caption styling is undone here. */
.field-consent {
    margin-bottom: 18px;
}

.field-consent label {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin-bottom: 0;
    font-size: 13px;
    font-weight: 400;
    line-height: 1.45;
    text-transform: none;
    letter-spacing: 0;
}

.field-consent input[type="checkbox"] {
    flex-shrink: 0;
    margin-top: 2px;
    accent-color: var(--accent);
}

.field-consent.has-error label {
    color: var(--danger);
}

/* Google renders the checkbox in a fixed 304x78 iframe it will not resize -
 * and it must not be scaled either: the image-challenge popup is a
 * position:fixed descendant of .g-recaptcha, so a transform on any ancestor
 * becomes its containing block and throws the challenge off-screen. Only the
 * people who actually get challenged would ever see that break.
 *
 * So the widget keeps its native size and is centred, and narrow phones win
 * the room back from the card's own padding instead. */
.field-recaptcha {
    margin-bottom: 18px;
    text-align: center;
    overflow-x: auto;
}

.field-recaptcha .g-recaptcha {
    display: inline-block;
}

/* 304px stops fitting the card's content box at about 378px of viewport.
 * Letting the field use the card's 20px padding carries it down to ~338px. */
@media (max-width: 400px) {
    .field-recaptcha {
        margin-left: -20px;
        margin-right: -20px;
    }
}

/* Narrower still: also lend it the page gutter. That is every pixel there is
 * short of the card border, and carries the widget down to ~306px - below
 * that the field scrolls rather than the page widening. */
@media (max-width: 337px) {
    .field-recaptcha {
        margin-left: -36px;
        margin-right: -36px;
    }
}

/* ---- Home shell ------------------------------------------------------
 * Left nav rail, center feed, right rail of widgets (docs/design/01-
 * homepage.png). The explicit grid only names 2 columns below 1100px, so
 * a third <aside> (the right rail) would otherwise get placed into a new
 * implicit row under the first column - the second rule below spans it
 * full-width instead until there's room for a real third column. */

.home-grid {
    display: grid;
    grid-template-columns: 220px 1fr;
    gap: 20px;
    padding: 20px 0;
}

@media (min-width: 900px) and (max-width: 1099px) {
    .home-grid > aside:last-of-type {
        grid-column: 1 / -1;
    }
}

@media (min-width: 1100px) {
    .home-grid {
        grid-template-columns: 240px 1fr 300px;
    }

    /* Clan pages have denser sidebars than the home feed: give leadership
       and events enough room, while keeping the wall near mockup scale. */
    .home-grid.clan-grid {
        grid-template-columns: 310px minmax(0, 1fr) 380px;
    }
}

/* Home feed proportions are deliberately capped within the wider app
   container, matching the mockup's balanced social rails at ultrawide sizes. */
@media (min-width: 1500px) {
    .home-grid.home-grid-feed {
        grid-template-columns: 270px minmax(620px, 830px) 480px;
        justify-content: center;
    }
}

.post-composer { margin-bottom: 14px; }

.post-composer-main {
    display: flex;
    gap: 10px;
    margin-bottom: 8px;
}

.post-composer textarea {
    flex: 1;
    width: 100%;
    min-height: 58px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text);
    font: inherit;
    resize: vertical;
}

.post-composer-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.post-composer-actions label { cursor: pointer; }

.post-composer-extras,
.post-composer-submit {
    display: flex;
    align-items: center;
    gap: 8px;
}

.post-composer-submit { margin-left: auto; }
.composer-media-button input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.composer-media-preview { position: relative; display: grid; grid-template-columns: 72px minmax(0, 1fr) auto; align-items: center; gap: 10px; margin: 0 0 10px 42px; padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); }
.composer-media-preview[hidden] { display: none; }
.composer-media-preview img, .composer-media-preview video { display: block; width: 72px; height: 54px; border-radius: 6px; background: #060708; object-fit: cover; }
.composer-media-preview [data-composer-media-name],
.composer-link-preview-copy span { overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.composer-video-poster { display:flex; align-items:center; gap:10px; margin:0 0 10px 42px; color:var(--muted); font-size:12px; }
.composer-video-poster[hidden] { display:none; }
.composer-video-poster > span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.composer-link-preview {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px 42px;
    padding: 9px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}
.composer-link-preview[hidden] { display: none; }
.composer-link-preview-icon {
    display: grid;
    place-items: center;
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 11px;
    font-weight: 800;
}
.composer-link-preview-copy { display: grid; min-width: 0; flex: 1; }
.composer-link-preview-copy strong { font-size: 13px; }
.composer-link-preview .btn { flex: 0 0 auto; font-size: 18px; }

.side-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.side-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-weight: 600;
    font-size: 13px;
}

.side-nav a:hover {
    background: var(--surface-hover);
    color: var(--text);
    text-decoration: none;
}

/*
 * The column had no gap, so the two buttons sat on top of each other with
 * nothing between them and read as one control. They are a row now, and the
 * panel gaps its own children rather than leaving each one to add a margin.
 */
.placeholder-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    text-align: center;
    padding: 44px 24px;
    color: var(--muted);
}
.placeholder-panel > p { margin: 0; max-width: 46ch; }
.placeholder-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }

.placeholder-panel h2 {
    color: var(--text);
    font-size: 18px;
}

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    background: var(--accent-soft);
    color: var(--accent);
}

/* ====================================================================
 * Design system: shared components (docs/design/*.png reference)
 *
 * Component library extracted from the 8 approved mockups so every page
 * template can be rebuilt against the same vocabulary instead of each
 * one inventing its own inline styles. This section adds components
 * only - it does not change how any existing template renders (nothing
 * above this point lost a class it depended on).
 * ==================================================================== */

/* ---- Verified checkmark, inline next to a name/title ---------------- */

.verified-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-fg);
    flex-shrink: 0;
}

.verified-badge svg {
    width: 10px;
    height: 10px;
}

/* ---- Avatar sizing + online-status dot ------------------------------
 * avatar_or_initials() in includes/helpers.php always renders
 * .avatar-img/.avatar-fallback at a fixed 30px; wrap it in one of these
 * to resize for a profile/clan header or a widget list row, and wrap
 * that in .avatar-wrap to pin an .avatar-status dot on it. */

/* Smaller than the 30px default: an avatar sitting inside a line of 12px copy,
   such as the poster's face on an LFP listing byline, where the 30px one is
   taller than the line it belongs to. */
.avatar-xs .avatar-img, .avatar-xs .avatar-fallback { width: 20px; height: 20px; font-size: 9px; }
.avatar-sm .avatar-img, .avatar-sm .avatar-fallback { width: 36px; height: 36px; font-size: 13px; }
.avatar-md .avatar-img, .avatar-md .avatar-fallback { width: 44px; height: 44px; font-size: 15px; }
.avatar-lg .avatar-img, .avatar-lg .avatar-fallback { width: 72px; height: 72px; font-size: 24px; border: 3px solid var(--bg); }
.avatar-xl .avatar-img, .avatar-xl .avatar-fallback { width: 96px; height: 96px; font-size: 32px; border: 4px solid var(--bg); }

.avatar-wrap {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

.avatar-status {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--muted-2);
    border: 2px solid var(--surface);
}

.avatar-status.is-online { background: var(--accent); }
.avatar-status.is-ingame { background: var(--warning); }

.status-text {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--muted);
}

.status-text::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--muted-2);
    flex-shrink: 0;
}

.status-text.is-online { color: var(--accent); }
.status-text.is-online::before { background: var(--accent); }
.status-text.is-ingame { color: var(--warning); }
.status-text.is-ingame::before { background: var(--warning); }

/* ---- Role / status pills ---------------------------------------------
 * One shared shape (.pill), colored by a semantic modifier. Used for
 * clan roles (owner/admin/moderator/member), user account status
 * (active/suspended/banned), and report/moderation severity. */

.pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.pill-accent { background: var(--accent-soft); color: var(--accent); }
.pill-mod { background: var(--role-mod-soft); color: var(--role-mod); }
.pill-info { background: var(--info-soft); color: var(--info); }
.pill-warning { background: var(--warning-soft); color: var(--warning); }
.pill-danger { background: var(--danger-soft); color: var(--danger); }
.pill-muted { background: var(--surface-hover); color: var(--muted); }

/* ---- Interest pickers -------------------------------------------------
 * Progressive enhancement keeps long genre/game catalogs compact while
 * preserving ordinary checkboxes when JavaScript is unavailable. */

.interest-picker { margin-bottom: 22px; }
.interest-picker:last-child { margin-bottom: 0; }
.interest-picker-heading { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:10px; }
.interest-picker-heading > div { min-width:0; }
.field-label { display:block; color:var(--text); font-size:12px; font-weight:700; letter-spacing:.035em; text-transform:uppercase; }
.interest-picker-hint { display:block; margin-top:3px; color:var(--muted); font-size:12px; }
.interest-picker-count { flex:0 0 auto; color:var(--accent); font-size:11px; font-weight:700; }
.interest-picker-tools { display:flex; gap:8px; margin-bottom:12px; }
.interest-picker-search { flex:1 1 220px; margin:0; }
.interest-picker-search input { width:100%; min-height:36px; padding:7px 11px; }
.interest-picker-toggle { flex:0 0 auto; }
.interest-picker-options { display:flex; flex-wrap:wrap; gap:8px; }
.interest-choice { cursor:pointer; margin:0; max-width:100%; }
.interest-picker [hidden] { display:none !important; }
.interest-choice input { flex:0 0 auto; margin:0 6px 0 0; }
.interest-choice span { min-width:0; overflow-wrap:anywhere; }
.interest-choice:has(input:checked) { background:var(--accent); color:var(--accent-fg); border-color:var(--accent); }
.interest-choice-negative:has(input:checked) { background:var(--danger); color:#fff; border-color:var(--danger); }
.interest-picker-empty, .interest-picker-no-results { margin:0; color:var(--muted); font-size:13px; }
.onboarding-step-card { padding:20px; }
.onboarding-step-heading { display:flex; align-items:flex-start; gap:12px; margin-bottom:12px; }
.onboarding-step-heading > span { display:grid; place-items:center; flex:0 0 28px; width:28px; height:28px; border-radius:50%; background:var(--accent-soft); color:var(--accent); font-size:12px; font-weight:800; }
.onboarding-step-heading h2 { margin:1px 0 3px; font-size:16px; }
.onboarding-step-heading h2 small { margin-left:5px; color:var(--muted); font-size:11px; font-weight:600; text-transform:uppercase; }
.onboarding-step-heading p, .onboarding-requirement { margin:0; color:var(--muted); font-size:12px; }
.onboarding-requirement { margin:0 0 12px 40px; }

/* ---- Stat tiles --------------------------------------------------------
 * A row of small metric cards: admin dashboard totals, profile/clan
 * header counts (Friends/Clans/Games/Posts, Members/Games/Created). */

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

.stat-tile {
    background: var(--surface);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    padding: 16px;
}

.stat-tile .stat-label {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 8px;
}

.stat-tile .stat-value {
    font-size: 26px;
    font-weight: 800;
    line-height: 1;
    color: var(--text);
}

.stat-tile .stat-delta {
    display: block;
    margin-top: 6px;
    font-size: 12px;
    color: var(--muted-2);
}

.stat-tile .stat-delta.is-up { color: var(--accent); }
.stat-tile .stat-delta.is-down { color: var(--danger); }

/* Inline counts, e.g. "56 Friends · 7 Clans · 12 Games · 128 Posts" in a
   profile/clan banner header. */
.stat-row {
    display: flex;
    flex-wrap: nowrap;
    gap: 10px 22px;
}

.stat-row .stat {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-width: 0;
}

.stat-row .stat .stat-value {
    font-size: 22px;
    font-weight: 800;
    color: var(--text);
    line-height: 1.2;
}

.stat-row .stat .stat-label {
    font-size: 11px;
    color: var(--muted);
}

/* ---- Widgets (right-rail panels) --------------------------------------
 * "MY GAMES", "UPCOMING EVENTS", "MESSAGES", "RECENT REPORTS", etc. A
 * .card that gets a header row (eyebrow-style title + "View all" link)
 * and a list of compact rows underneath. */

.widget-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.widget-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text);
}

.widget-link {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent);
}

.widget-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.widget-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 4px;
    border-radius: var(--radius-sm);
}

.widget-row:hover {
    background: var(--surface-hover);
}

.widget-row .widget-row-media {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    flex-shrink: 0;
    background: var(--surface-alt);
}

.widget-row .widget-row-body {
    flex: 1;
    min-width: 0;
}

.widget-row .widget-row-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.widget-row .widget-row-meta {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.widget-row .widget-row-aside {
    font-size: 11px;
    color: var(--muted-2);
    flex-shrink: 0;
    text-align: right;
}

.widget-divider {
    border: none;
    border-top: 1px solid var(--border-soft);
    margin: 14px 0;
}

/* ---- Game entry card (home feed "My Games" widget) --------------------
 * A cover thumbnail + title header, then a 2-column "Clan Activity" /
 * "Official News" body - docs/design/01-homepage.png's per-game layout. */

/* 01-homepage.png uses three compact horizontal cards: cover art at left,
 * title and the activity/news split at right. The desktop home grid reserves
 * enough rail width for that hierarchy; smaller widths retain the same card
 * without allowing either text column to force document overflow. */
/*
 * The poster is 9:16, the ratio the featured cards on the landing page use.
 *
 * It used to be whatever the content left over: the column was a fixed 112px
 * and the cover stretched to the card, so a game with a video row got a
 * narrower poster than one without and no two cards matched.
 *
 * The width follows the rail rather than being enlarged everywhere. This rail
 * is 300px until 1500px, where it becomes 480px; at 300px a 128px poster
 * leaves 128px for two columns of headlines and the card overflows. So the
 * poster grows where there is room for it to grow into.
 */
.widget-game {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    min-height: 199px;
    overflow: hidden;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}

.widget-game + .widget-game {
    margin-top: 10px;
}

.widget-game-cover {
    display: block;
    width: 100%;
    /* Held to its own ratio rather than stretched to the row: a taller card
       leaves the card's own surface below the poster, which is the same colour
       and therefore no seam. */
    align-self: start;
    aspect-ratio: 9 / 16;
    overflow: hidden;
    background: var(--surface-alt);
}

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

.widget-game-body {
    min-width: 0;
    padding: 10px 12px;
}

.widget-game-title {
    display: block;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
    margin-bottom: 8px;
}

.widget-game-title:hover {
    color: var(--accent);
}

.widget-game-cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.widget-game-col-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--accent);
    margin-bottom: 6px;
}

.widget-game-item {
    display: block;
    font-size: 12px;
    color: var(--text);
    line-height: 1.4;
    margin-bottom: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.widget-game-item:hover {
    color: var(--accent);
}

.widget-game-item-meta {
    display: block;
    font-size: 11px;
    color: var(--muted-2);
    margin-top: -3px;
    margin-bottom: 6px;
}

.widget-game-empty {
    font-size: 12px;
    color: var(--muted-2);
}

.widget-game-video {
    display: grid;
    grid-template-columns: 72px minmax(0, 1fr);
    gap: 8px;
    align-items: center;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-soft);
    color: var(--text);
    font-size: var(--type-xs);
    line-height: 1.35;
}

.widget-game-video:hover { color: var(--accent); text-decoration: none; }
.widget-game-video img { width: 72px; height: 42px; object-fit: cover; border-radius: 2px; background: var(--surface-alt); }
.widget-game-video strong { display: block; margin-bottom: 2px; color: var(--accent); font-size: 10px; letter-spacing: .03em; text-transform: uppercase; }

/* ---- Date tile (event calendar chip: "MAY / 25") --------------------- */

.date-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    border: 1px solid var(--border-soft);
    flex-shrink: 0;
    line-height: 1.1;
}

.date-tile .date-month {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--accent);
}

.date-tile .date-day {
    font-size: 15px;
    font-weight: 800;
    color: var(--text);
}

/* ---- Progress bar (event roles/slots, participant capacity) --------- */

.progress-track {
    height: 6px;
    border-radius: 999px;
    background: var(--surface-alt);
    overflow: hidden;
}

.progress-fill {
    height: 100%;
    background: var(--accent);
    border-radius: 999px;
}

/* ---- Tabs (Wall / About / Games / Media / Clans / Friends) ---------- */

.tabs {
    display: flex;
    align-items: center;
    gap: 22px;
    border-bottom: 1px solid var(--border-soft);
}

.tabs a {
    padding: 12px 2px;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.tabs a:hover {
    color: var(--text);
    text-decoration: none;
}

.tabs a.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* ---- Profile / clan banner header -------------------------------------
 * Cover image with a large avatar overlapping its bottom-left corner,
 * name/title/meta to the right, actions (Edit Profile / Invite / Joined)
 * on the far right. */

/*
 * The header does not clip its own overflow. It used to, to keep the cover
 * inside the rounded corners, but the action popovers on this header open
 * downwards from triggers that sit on its bottom edge - the clan Invite form
 * lost 94 of its 111px that way, leaving a sliver that read as a dead button.
 * The cover rounds its own top corners instead, which is all the clipping the
 * header ever needed.
 */
.banner-header {
    border-radius: var(--radius);
    border: 1px solid var(--border-soft);
    background: var(--surface);
}

.banner-cover {
    height: 220px;
    border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
    background-size: cover;
    background-position: center;
    background-color: var(--surface-alt);
}

.banner-body {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    padding: 0 24px 20px;
    margin-top: -48px;
}

.banner-identity {
    display: flex;
    align-items: flex-end;
    gap: 16px;
}

.banner-identity h1 {
    font-size: 22px;
    margin-bottom: 2px;
}

.banner-identity .handle {
    color: var(--muted);
    font-size: 13px;
}

.banner-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 4px;
}

/* ---- Profile page ---------------------------------------------------
 * Keeps the wall as the primary reading surface when the mockup's three
 * desktop columns collapse, while retaining the compact identity/stat header. */

.profile-page {
    max-width: 1700px;
    padding-top: 20px;
    padding-bottom: 60px;
}

.profile-banner-identity,
.profile-banner-copy,
.profile-main,
.profile-left-rail,
.profile-right-rail { min-width: 0; }

/* --- Guides in the profile rail ---------------------------------------- */

.profile-guides-widget { margin-bottom: 16px; }
.profile-widget-empty { margin: 0; color: var(--muted); font-size: 13px; }

/*
 * A guide need not have a picture and most do not, so the tile is always the
 * same size whether it holds a cover or the book mark - rows keep one left
 * edge instead of going ragged wherever a cover happens to be missing.
 */
.profile-guide-thumb {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-sm);
    object-fit: cover;
}
.profile-guide-thumb-blank {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-soft);
    background: var(--surface-alt);
    color: var(--accent);
}
/* A long title must not push the rail wider, so the row may shrink. */
.profile-guide-row { min-width: 0; align-items: flex-start; }
.profile-guide-row .widget-row-body { min-width: 0; }
/*
 * Two lines, then an ellipsis. The other widgets cut their titles at one line
 * because a game or a friend is named in one or two words; a guide is named in
 * a sentence, and one line truncated every row in testing.
 */
/*
 * Scoped through .profile-guide-row on purpose: the base rule is
 * `.widget-row .widget-row-title`, two classes, so a single-class override
 * loses the cascade and the title silently keeps its one-line ellipsis.
 */
.profile-guide-row .profile-guide-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    line-height: 1.35;
}
/* The facts line stays on one line: it is short, and wrapping it would make
   the rows different heights for no gain. */
.profile-guide-row .profile-guide-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "Awaiting review" is the author's own status, so it reads as a marker
   rather than as part of the guide's description. */
.profile-guide-state { color: var(--warning); }

.profile-banner-copy h1,
.profile-banner-copy .handle { overflow-wrap: anywhere; }
/*
 * Four counts sitting loose beside the display name read as four unrelated
 * numbers. The same hairline divider the phone layout already uses groups them
 * into one block, which is what they are.
 */
.profile-banner-stats { margin-left: 24px; gap: 0; }
.profile-banner-stats .stat { flex: 0 1 auto; padding: 0 16px; }
.profile-banner-stats .stat:first-child { padding-left: 0; }
.profile-banner-stats .stat:last-child { padding-right: 0; }
.profile-banner-stats .stat + .stat { border-left: 1px solid var(--border-soft); }
.profile-banner-stats .stat .stat-value { white-space: nowrap; }
.profile-banner-actions { flex-wrap: wrap; justify-content: flex-end; }
.profile-banner-actions form { margin: 0; }
.profile-tabs { margin-top: 20px; }
.profile-grid { padding-top: 20px; }
.profile-left-rail,
.profile-right-rail { align-self: start; }
.profile-media-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:10px; }
.profile-friends-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
.profile-media-preview-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:6px; }
.profile-media-preview-grid > a { min-width:0; }
.profile-meta-list { display:grid; gap:7px; margin:10px 0 0; font-size:12px; }
.profile-meta-list > div { display:grid; grid-template-columns:88px minmax(0,1fr); gap:8px; }
.profile-meta-list dt { color:var(--muted); }
.profile-meta-list dd { min-width:0; margin:0; color:var(--text-soft); overflow-wrap:anywhere; }
.profile-meta-list-wide { max-width:620px; margin-top:16px; padding-top:14px; border-top:1px solid var(--border-soft); font-size:13px; }
.profile-meta-list-wide > div { grid-template-columns:130px minmax(0,1fr); }
.profile-interest-preview { margin-top:14px; padding-top:12px; border-top:1px solid var(--border-soft); }
.profile-interest-preview-title { margin-bottom:8px; color:var(--muted); font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; }
.profile-tag-list { display:flex; flex-wrap:wrap; gap:7px; }
.profile-tag-list .pill { white-space:normal; line-height:1.3; }
.profile-more-pill { text-decoration:none; }
.profile-overflow-details { margin-top:12px; }
.profile-overflow-details summary { width:max-content; max-width:100%; color:var(--accent); cursor:pointer; font-size:12px; font-weight:700; }
.profile-overflow-details[open] summary { margin-bottom:12px; }
.profile-game-section { margin-bottom:20px; }
.profile-game-tile { display:flex; align-items:center; gap:10px; min-width:0; padding:10px; color:var(--text); text-decoration:none; }
.profile-game-tile img, .profile-game-placeholder { flex:0 0 44px; width:44px; height:44px; border-radius:var(--radius-sm); object-fit:cover; }
.profile-game-placeholder { display:grid; place-items:center; background:var(--surface-hover); color:var(--muted); font-weight:800; }
.profile-game-tile > span:last-child { min-width:0; overflow-wrap:anywhere; font-size:13px; font-weight:600; }

/* ---- Clan page -------------------------------------------------------
 * The desktop composition follows 03-clan.png. Named regions also let the
 * wall remain the primary content when the three-column grid collapses. */

.clan-page {
    max-width: 1700px;
    padding-top: 20px;
    padding-bottom: 60px;
}

.clan-banner .banner-body,
.clan-banner .banner-identity,
.clan-main,
.clan-left-rail,
.clan-right-rail { min-width: 0; }
.clan-left-rail,
.clan-right-rail { align-self: start; }
.clan-banner-copy { min-width: 0; }
.clan-title-line, .clan-list-title { display: flex; align-items: center; gap: 7px; min-width: 0; }
.clan-title-line h1 { min-width: 0; }
.clan-list-title { flex-wrap: wrap; font-weight: 700; }
.clan-verified-badge { display: inline-grid; width: 20px; height: 20px; flex: 0 0 auto; place-items: center; color: var(--accent); filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 38%, transparent)); }
.clan-verified-badge svg { display: block; width: 100%; height: 100%; fill: currentColor; }
.clan-verified-badge-sm { width: 16px; height: 16px; }
.clan-invite-menu { position: relative; }
.clan-invite-menu > summary { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; list-style: none; }
.clan-invite-menu > summary svg { width: 15px; height: 15px; fill: currentColor; }
.clan-invite-menu > summary::-webkit-details-marker { display: none; }
.clan-invite-popover { position: absolute; z-index: 30; top: calc(100% + 8px); right: 0; display: grid; gap: 9px; width: min(300px, 78vw); box-shadow: var(--shadow-card); }
.clan-invite-popover label { color: var(--text-soft); font-size: 12px; font-weight: 700; }
.clan-invite-popover input { width: 100%; }
.clan-invite-banner { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 14px 0 0; border-color: color-mix(in srgb, var(--accent) 48%, var(--border)); background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
.clan-invite-banner > div:first-child { display: grid; gap: 3px; }
.clan-invite-banner span, .clan-invitations-card p { color: var(--muted); font-size: 12px; }
.clan-invitations-card { display: grid; gap: 12px; margin: 0 0 18px; border-color: color-mix(in srgb, var(--accent) 38%, var(--border)); }
.clan-invitations-card h2 { margin: 0; font-size: 16px; }
.clan-invitations-card p { margin: 3px 0 0; }
.clan-invitation-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding-top: 11px; border-top: 1px solid var(--border-soft); }
.clan-invitation-row .clan-manage-person span { display: grid; gap: 2px; }
.clan-invitation-row .clan-manage-person small { color: var(--muted); font-weight: 400; }
.clan-banner-copy h1,
.clan-banner-copy .handle { overflow-wrap: anywhere; }
.clan-banner-stats { flex-wrap:wrap; margin-top: 14px; }
/*
 * These five stats hold very different things - a member count, a date, and
 * free text a clan typed in. .stat-row sizes every column to flex:1 1 0, an
 * equal share whatever is in it, which suits the number-only rows it was built
 * for and not this one: "English, Lithuanian" needs ten times the room "1"
 * does. It was also nowrap with no overflow rule, so the text did not wrap,
 * did not clip, and simply drew over the Region column beside it.
 *
 * Sizing to content fixes the cause. The row already wraps, so a clan that
 * fills every field pushes onto a second line instead of onto its neighbour,
 * and a long value wraps within its own column rather than escaping it.
 */
.clan-banner-stats .stat { flex: 0 1 auto; max-width: 100%; }
/*
 * Three classes deep, to match .stat-row .stat .stat-value rather than lose to
 * it. The two-class form these overrides used to take never applied at all -
 * it is outranked, and a media query adds no specificity, which is why the
 * font-size below had been dead since it was written.
 *
 * break-word, not anywhere: anywhere also shrinks the min-content width, so a
 * comma-separated list broke mid-word ("Lithuani/an") while spaces sat unused.
 */
.clan-banner-stats .stat .stat-value { white-space: normal; overflow-wrap: break-word; }
.clan-banner .banner-actions { min-width: 0; flex-wrap: wrap; justify-content: flex-end; }
.clan-banner .banner-actions > * { min-width: 0; }
.clan-tabs { margin-top: 20px; scroll-padding-inline: 12px; scroll-snap-type: x proximity; }
.clan-tabs a { scroll-snap-align: start; }

/*
 * The clan tab row: sections on the left, the week's counts in the space that
 * was otherwise empty on the right. The underline belongs to the row so it
 * still runs the full width once the nav stops being a full-width block.
 */
.clan-tabs-row {
    display: flex;
    align-items: flex-end;
    gap: 24px;
    margin-top: 20px;
    border-bottom: 1px solid var(--border-soft);
}
.clan-tabs-row .clan-tabs {
    margin-top: 0;
    border-bottom: 0;
    flex: 1 1 auto;
    min-width: 0;
}

.clan-activity-strip {
    display: flex;
    align-items: baseline;
    gap: 16px;
    flex: 0 0 auto;
    padding-bottom: 9px;
}
.clan-activity-heading {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted-2);
}
.clan-activity-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
}
.clan-activity-stat b {
    font-size: 15px;
    font-weight: 800;
    line-height: 1;
    color: var(--text);
}
.clan-activity-stat span {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--muted);
}

/* Between the rail dropping away and the tabs starting to scroll, the counts
 * give up their heading before they start crowding the tab labels. */
@media (max-width: 1100px) {
    .clan-tabs-row { gap: 16px; }
    .clan-activity-strip { gap: 12px; }
    .clan-activity-heading { display: none; }
}
.clan-row-actions,
.clan-member-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ---- Management disclosure panel ----------------------------------
 *
 * The owner/admin surface on a clan page (and the event role templates
 * beside it). It is a <details> because it is machinery rather than
 * content, but everything below the summary is laid out as a settings
 * page would be: a sectioned body, one .field vocabulary, a two-column
 * grid that collapses to one, and destructive controls set apart in
 * their own bordered block instead of stacked flush with the rest.
 *
 * Named .manage-panel rather than .clan-manage-panel: the same chrome
 * is what the event manage panel wants, and nothing here is specific
 * to a clan.
 */
.clan-manage-panel { margin-top: 14px; }
.manage-panel { overflow: hidden; }
.manage-panel-summary {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    cursor: pointer;
    list-style: none;
    touch-action: manipulation;
    /* The summary is the panel's header when closed and its title bar when
       open, so it carries its own hover rather than borrowing the card's. */
    background: transparent;
}
.manage-panel-summary::-webkit-details-marker { display: none; }
.manage-panel-summary:hover { background: var(--surface-hover); }
.manage-panel[open] > .manage-panel-summary {
    border-bottom: 1px solid var(--border-soft);
    background: var(--surface-hover);
}
.manage-panel-icon {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--accent);
}
.manage-panel-heading { display: grid; gap: 2px; min-width: 0; flex: 1 1 auto; }
.manage-panel-heading strong { font-size: 14px; font-weight: 700; }
.manage-panel-heading small { color: var(--muted); font-size: 12px; }
.manage-panel-badge {
    flex: 0 0 auto;
    padding: 3px 9px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 11px;
    font-weight: 700;
}
.manage-panel-chevron {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--muted);
    transition: transform .15s ease;
}
.manage-panel[open] > .manage-panel-summary .manage-panel-chevron { transform: rotate(180deg); }

.manage-panel-body { padding: 4px 18px 18px; }
.manage-section { padding: 18px 0; border-bottom: 1px solid var(--border-soft); }
.manage-section:last-child { padding-bottom: 2px; border-bottom: 0; }
.manage-section-head { margin-bottom: 14px; }
.manage-section-head h3 { margin: 0; font-size: 14px; font-weight: 700; }
.manage-section-head p { margin: 3px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }

.manage-subhead { margin: 18px 0 12px; }
.manage-subhead h4 {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.manage-subhead p { margin: 3px 0 0; color: var(--muted-2); font-size: 12px; }
.manage-count {
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-soft);
    font-size: 11px;
    letter-spacing: 0;
}

/* Two columns that pair naturally (region/language, platform/link), one
   column for anything that needs the room. Below 720px both are one. */
.manage-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 16px; }
.manage-grid .field { min-width: 0; margin-bottom: 0; }
.manage-span-2 { grid-column: 1 / -1; }
.manage-grid textarea { resize: vertical; }

.manage-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}
.manage-narrow-form { max-width: 460px; }
.manage-narrow-form .field { margin-bottom: 14px; }
.manage-empty { margin: 0; color: var(--muted); font-size: 13px; }
.manage-panel-body .hint code {
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--surface-2);
    color: var(--text-soft);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
}

/* Branding tiles: the current image, what it is for, and the control that
   replaces it - so "change banner" is not a bare button with no subject. */
.manage-media-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.manage-media-tile {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
    padding: 12px 14px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}
.manage-media-banner {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 76px;
    height: 44px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background-color: var(--surface-2);
    background-position: center;
    background-size: cover;
    color: var(--muted);
}
.manage-media-copy { display: grid; gap: 2px; min-width: 0; flex: 1 1 auto; }
.manage-media-copy strong { font-size: 13px; }
.manage-media-copy small { color: var(--muted); font-size: 11px; line-height: 1.45; }
.manage-media-btn { flex: 0 0 auto; cursor: pointer; }

/* A tagged game is a chip you remove, not a full-size button that happens
   to end in a multiplication sign. */
.manage-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 14px; }
.manage-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 9px 5px 11px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-alt);
    color: var(--text-soft);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}
.manage-chip:hover { border-color: var(--danger); color: var(--danger); }
.manage-chip-x { color: var(--muted); font-size: 14px; line-height: 1; }
.manage-chip:hover .manage-chip-x { color: var(--danger); }

.manage-inline-form { display: flex; align-items: flex-end; gap: 8px; }
.manage-inline-form .field { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }

.manage-list .clan-manage-row:first-child { border-top: 1px solid var(--border-soft); }
.manage-list .clan-manage-row { padding: 10px 0; }
.manage-list .clan-manage-person { font-size: 13px; font-weight: 600; }
.manage-list .clan-manage-person > span:last-child { display: grid; gap: 2px; min-width: 0; }
.manage-list .clan-manage-person small { color: var(--muted); font-size: 11.5px; font-weight: 400; }
.manage-list .clan-manage-person:hover { color: var(--accent); }
.manage-row-copy { display: grid; gap: 2px; min-width: 0; }
.manage-row-copy strong { font-size: 13px; }
.manage-row-copy small { color: var(--muted); font-size: 11.5px; overflow-wrap: anywhere; }

.manage-danger {
    margin-top: 18px;
    padding: 14px 16px;
    border: 1px solid var(--danger);
    border-radius: var(--radius-sm);
    background: var(--danger-soft);
}
.manage-danger-head { margin-bottom: 12px; }
.manage-danger-head h4 { margin: 0; color: var(--danger); font-size: 13px; font-weight: 700; }
.manage-danger-head p { margin: 4px 0 0; color: var(--text-soft); font-size: 12px; line-height: 1.5; }
.manage-danger .field input { background: var(--surface); }

/* The clan page keeps a right rail down to 1100px, so the panel's own two
   columns get narrow well before the phone breakpoint - they go single at
   the width where a label and its value stop fitting side by side. */
@media (max-width: 860px) {
    .manage-grid,
    .manage-media-grid { grid-template-columns: minmax(0, 1fr); }
}
.clan-meta-list { display:grid; gap:7px; margin:12px 0 0; font-size:12px; }
.clan-meta-list > div { display:grid; grid-template-columns:94px minmax(0,1fr); gap:8px; }
.clan-meta-list dt { color:var(--muted); }
.clan-meta-list dd { min-width:0; margin:0; color:var(--text-soft); overflow-wrap:anywhere; }
.clan-meta-list dd a { color:var(--accent); text-decoration:none; }
.clan-meta-list dd a:hover { text-decoration:underline; }
.clan-meta-list-wide { max-width:640px; margin-top:16px; padding-top:14px; border-top:1px solid var(--border-soft); font-size:13px; }
.clan-meta-list-wide > div { grid-template-columns:130px minmax(0,1fr); }
.clan-recruiting-list { display:grid; gap:6px; margin:0 0 13px; padding:0; list-style:none; color:var(--text-soft); font-size:12px; }
.clan-recruiting-list li { position:relative; padding-left:18px; }
.clan-recruiting-list li::before { content:'✓'; position:absolute; left:0; color:var(--accent); font-weight:800; }
.clan-manage-row,
.clan-member-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 0;
    border-bottom: 1px solid var(--border-soft);
}
.clan-manage-person,
.clan-member-person {
    display: flex;
    align-items: center;
    min-width: 0;
    gap: 8px;
    color: inherit;
    text-decoration: none;
}
.clan-manage-person,
.clan-member-person > span:last-child { overflow-wrap: anywhere; }
.clan-event-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 10px;
    color: inherit;
    text-decoration: none;
}
.clan-event-filters {
    display: flex;
    gap: 6px;
    margin-bottom: 14px;
    overflow-x: auto;
    scrollbar-width: none;
}
.clan-event-filters::-webkit-scrollbar { display: none; }
.clan-event-filters a {
    flex: 0 0 auto;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    text-decoration: none;
}
.clan-event-filters a:hover { border-color: var(--accent-dim); color: var(--text); }
.clan-event-filters a.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.clan-events-empty {
    margin-bottom: 12px;
    padding: 18px;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-size: 13px;
    text-align: center;
}
.clan-event-footer { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* ---- Post / wall card -------------------------------------------------
 * Author row (avatar + name + meta + overflow menu), optional media,
 * body text, and an actions row (like/comment/share counts). Used on
 * the home feed, profile wall, clan wall, and game group-activity feed. */

.post-card + .post-card {
    margin-top: 10px;
}

.post-author {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
}

.post-author-left {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.post-author-name {
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    overflow-wrap: anywhere;
}

.post-game-tag {
    margin-left: 5px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
}

.post-game-tag:hover { color: var(--accent); }

.post-game-select select,
.post-game-picker {
    height: 32px;
    max-width: 180px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text);
    padding: 0 8px;
    font: inherit;
    font-size: 12px;
}

.post-author-meta {
    font-size: 12px;
    color: var(--muted);
}

.post-owner-controls {
    display: flex;
    align-items: flex-start;
    gap: 4px;
}

/*
 * Editing a post happens in the post, at the post's own width - not in a
 * 420px popover floating over the feed. The toggle is a visually hidden
 * checkbox at the top of the card so it can reach both the trigger in the
 * header and the editor down in the body; it keeps the card's own stacking
 * and scroll position, and clicking elsewhere no longer discards the draft
 * the way an outside-click-to-close popover did.
 */
.post-edit-toggle {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}
.post-edit-open { cursor: pointer; }
.post-edit-form { display: none; }
.post-edit-toggle:checked ~ .post-edit-form { display: block; margin: 10px 0 2px; }
.post-edit-toggle:checked ~ .post-copy { display: none; }
.post-edit-toggle:checked ~ .post-author .post-edit-open { display: none; }
/* The checkbox is invisible, so its focus ring is drawn on whichever label is
   currently standing in for it. */
.post-edit-toggle:focus-visible ~ .post-author .post-edit-open,
.post-edit-toggle:focus-visible ~ .post-edit-form .post-edit-cancel {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.post-edit-form textarea {
    display: block;
    width: 100%;
    min-height: 92px;
    resize: vertical;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    padding: 10px 12px;
    font: inherit;
    line-height: 1.5;
}
.post-edit-form textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
}
.post-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}
.post-edit-cancel { cursor: pointer; }

.post-audience {
    display: inline-block;
    margin-left: 3px;
    font-size: 11px;
    line-height: 1;
    vertical-align: 1px;
}

.post-media {
    display: block;
    border-radius: var(--radius-sm);
    overflow: hidden;
    margin: 10px auto;
}

.post-media img, .post-media video {
    display: block;
    width: 100%;
    max-height: 420px;
    object-fit: contain;
    background: #060708;
}

.post-media-centered, .post-attachment-centered { width: min(100%, 680px); margin-right: auto; margin-left: auto; }
.post-media-video { background: #060708; }

/* Homepage attachments follow the post copy in one natural reading column.
   They use the card width without becoming dedicated-page-height media. */
.home-feed-main .post-card-feed > .post-media img,
.home-feed-main .post-card-feed > .post-media video { max-height: 280px; }
.home-feed-main .post-card-feed > .post-external-gif img { max-height: 280px; }
.home-feed-main .post-card-feed > .post-attachment-centered,
.home-feed-main .post-card-feed > .post-media-centered { width: 100%; }
/* A post on a profile or a clan wall uses its whole card, the way a feed post
   does. The 680px cap above is for a post read on its own page, where the card
   spans the full container and unbounded media would tower over the copy. A
   clan main is already a column between two rails - 815px at a 1600px viewport
   - so capping it again left the attachment floating in 135px of its own
   gutters while the text beside it ran the full width. */
.profile-main .post-card > .post-attachment-centered,
.profile-main .post-card > .post-media-centered,
.clan-main .post-card > .post-attachment-centered,
.clan-main .post-card > .post-media-centered,
.game-main .post-card > .post-attachment-centered,
.game-main .post-card > .post-media-centered { width: 100%; }
.home-feed-main .post-card-feed { padding: 14px; }
.home-feed-main .post-card-feed > .post-author { margin-bottom: 8px; }
.home-feed-main .post-card-feed > .post-actions { margin-top: 8px; padding-top: 8px; }

@media (max-width: 640px) {
    .clan-invite-banner, .clan-invitation-row { align-items: stretch; flex-direction: column; }
    .clan-invite-banner .clan-row-actions, .clan-invitation-row .clan-row-actions { width: 100%; }
    .clan-invite-banner .clan-row-actions form, .clan-invitation-row .clan-row-actions form { flex: 1; }
    .clan-invite-banner .clan-row-actions .btn, .clan-invitation-row .clan-row-actions .btn { width: 100%; }
    .composer-link-preview, .composer-media-preview, .composer-video-poster { margin-left: 0; }
}

.post-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 14px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-soft);
}

.post-card input[type="text"] { min-width: 0; }

.post-action {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
}

.post-action:hover {
    color: var(--text);
    text-decoration: none;
}
.post-action.is-active { color: var(--accent); }
.post-action-status { margin:6px 0 0; color:var(--accent); font-size:12px; }
.post-action-status.is-error { color:var(--danger); }

.post-mention {
    color: var(--accent);
    font-weight: 700;
    text-decoration: none;
}

.post-mention:hover {
    text-decoration: underline;
}

.post-copy {
    margin: 0 0 10px;
    white-space: pre-wrap;
}

.post-embed,
.post-external-gif,
.post-link-card {
    display: block;
    overflow: hidden;
    margin-top: 10px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}

.post-embed {
    position: relative;
    aspect-ratio: 16 / 9;
    background: #060708;
}

.post-embed iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

.external-embed-consent {
    background: radial-gradient(circle at 50% 30%, rgba(255,255,255,.07), transparent 48%), #060708;
}
.external-embed-load {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
    height: 100%;
    min-height: 150px;
    padding: 18px;
    border: 0;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    text-align: left;
}
.external-embed-load:hover .external-embed-play,
.external-embed-load:focus-visible .external-embed-play { transform: scale(1.08); background: var(--accent); }
.external-embed-play {
    display: grid;
    place-items: center;
    flex: 0 0 48px;
    width: 48px;
    height: 48px;
    padding-left: 3px;
    border-radius: 50%;
    background: var(--accent-dim);
    color: var(--accent-fg);
    font-size: 18px;
    transition: transform 120ms ease, background 120ms ease;
}
.external-embed-load span:last-child { display: grid; gap: 3px; }
.external-embed-load strong { font-size: 14px; }
.external-embed-load small { color: var(--muted); font-size: 11px; }
.external-embed-fallback { position: absolute; inset: auto 10px 8px; color: var(--muted); font-size: 11px; text-align: center; }

.post-external-gif img {
    display: block;
    width: 100%;
    max-height: 640px;
    object-fit: contain;
    background: #060708;
}

.post-link-card {
    color: var(--text);
    text-decoration: none;
}

.post-link-card:hover {
    border-color: var(--border);
    text-decoration: none;
}

.post-link-card > img {
    display: block;
    width: 100%;
    max-height: 290px;
    object-fit: cover;
    background: var(--surface);
}

.post-link-card-copy {
    display: grid;
    gap: 4px;
    padding: 11px 13px;
}

.post-link-card-host {
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
}

.post-link-card-copy strong {
    overflow: hidden;
    font-size: 14px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.post-link-card-copy > span:last-child {
    display: -webkit-box;
    overflow: hidden;
    color: var(--muted);
    font-size: 13px;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

/* ---- Nav notification count bubble ("Messages 3") -------------------- */

.nav-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-fg);
    font-size: 10px;
    font-weight: 800;
    line-height: 16px;
}

/* ---- Data table (admin Users / Reports / Registrations) -------------- */

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

.data-table th {
    text-align: left;
    padding: 10px 12px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
    border-bottom: 1px solid var(--border-soft);
    white-space: nowrap;
}

.data-table td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--border-soft);
    vertical-align: middle;
}

.data-table tr:last-child td {
    border-bottom: none;
}

.data-table tr:hover td {
    background: var(--surface-hover);
}

/* ---- Section eyebrow reused at smaller scale (widget-title is the
 * compact form of .section-label for use inside a card rather than a
 * full page section). ------------------------------------------------- */

.hidden {
    display: none !important;
}

/* ---- Game hub -------------------------------------------------------- */

.game-page { padding-top: 20px; padding-bottom: 64px; }
.game-hero { position: relative; isolation: isolate; min-height: 290px; overflow: hidden; display: grid; grid-template-columns: 190px minmax(0, 1fr) auto; align-items: end; gap: 24px; padding: 32px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
.game-hero::before { content: ''; position: absolute; z-index: -2; inset: 0; background: var(--game-header) center / cover no-repeat; }
.game-hero::after { content: ''; position: absolute; z-index: -1; inset: 0; background: linear-gradient(90deg, rgba(10,15,23,.94) 0%, rgba(10,15,23,.8) 54%, rgba(10,15,23,.62) 100%); }
.game-hero.no-image::before { background: radial-gradient(circle at 78% 18%, rgba(48,137,255,.25), transparent 35%), var(--surface-2); }
.game-cover { width: 190px; height: 240px; overflow: hidden; border: 3px solid rgba(255,255,255,.18); border-radius: var(--radius-sm); background: var(--surface); box-shadow: 0 14px 30px rgba(0,0,0,.32); }
.game-cover .avatar-img, .game-cover .avatar-fallback { width: 100%; height: 100%; border-radius: 0; font-size: 40px; }
.game-hero-content { min-width: 0; max-width: 840px; }
.game-hero h1 { margin: 3px 0 4px; font-size: clamp(30px, 4vw, 48px); letter-spacing: -.03em; }
.game-hero h1, .game-hero p { overflow-wrap: anywhere; }
.game-hero p { max-width: 760px; margin: 12px 0; color: #d7dce7; white-space: pre-wrap; line-height: 1.55; }
.game-hero-stats { color: #b7c0d1; font-size: 13px; font-weight: 600; }
.game-hero-stats span { padding: 0 6px; color: var(--accent); }
.game-tags { display: flex; gap: 7px; flex-wrap: wrap; margin: 12px 0; }
.game-tags span { border: 1px solid rgba(255,255,255,.2); border-radius: 999px; padding: 4px 9px; color: #edf2fa; font-size: 12px; font-weight: 650; }
.game-meta { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: #bdc5d5; }
.game-meta span { display: grid; gap: 2px; }.game-meta b { color: #fff; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.game-hero-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }
.game-hero-actions form { display: flex; margin: 0; }
.game-layout { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 24px; align-items: start; margin-top: 26px; }
.game-main { min-width: 0; }.game-rail { display: grid; gap: 16px; }
.game-section-heading { display: flex; justify-content: space-between; align-items: end; gap: 16px; margin: 0 0 10px; }.game-section-heading h2 { margin: 1px 0 0; font-size: 20px; }.game-section-heading > span { color: var(--muted); font-size: 12px; }
.game-main .post-card { margin-bottom: 10px; }.game-community-section { margin-top: 30px; }
.game-rail-card { padding: 18px; }.game-news-item { display: grid; gap: 4px; padding: 10px 0; color: inherit; text-decoration: none; border-bottom: 1px solid var(--border-soft); font-size: 13px; line-height: 1.38; }.game-news-item:last-of-type { border-bottom: 0; }.game-news-item small, .game-lfp-row small, .game-video-list small, .game-event-row small { color: var(--muted); font-size: 11px; }
.game-lfp-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; color: inherit; text-decoration: none; border-bottom: 1px solid var(--border-soft); }.game-lfp-row:last-of-type { border-bottom: 0; }.game-lfp-row > span:last-child { display: grid; min-width: 0; gap: 2px; font-size: 13px; }.game-lfp-row b, .game-video-list b, .game-event-row b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.game-lfp-row small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.game-video-list { display: grid; gap: 10px; }.game-video-list > a { display: grid; grid-template-columns: 108px minmax(0,1fr); gap: 9px; color: inherit; text-decoration: none; font-size: 12px; line-height: 1.35; }.game-video-list > a > span:last-child { display: grid; align-content: center; gap: 4px; }.game-video-thumb { overflow: hidden; aspect-ratio: 16 / 9; border-radius: 6px; background: var(--surface-alt); }.game-video-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The live rail reuses the Official Videos row, plus a dot that says the thumbnail is not a recording. min-height:0 because a grid item's auto minimum lets a thumbnail that is not 16:9 push past the aspect-ratio. */
.game-live-thumb { position: relative; min-height: 0; }.game-live-dot { position: absolute; top: 5px; left: 5px; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px rgba(7, 9, 11, 0.65); }
.game-list-card { padding: 4px 18px; }.game-event-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; color: inherit; text-decoration: none; border-bottom: 1px solid var(--border-soft); }.game-event-row:last-of-type { border-bottom: 0; }.game-event-row > span:last-child { display: grid; gap: 3px; }
.game-empty { margin: 0; color: var(--muted); font-size: 13px; }.game-rail-card .game-empty { padding: 4px 0; }

/* ---- Event detail ---------------------------------------------------- */
.event-page { max-width: 1700px; padding-top: 20px; padding-bottom: 64px; }
/*
 * The hero is two heroes. An event with banner artwork gets the full-bleed
 * treatment - a tall card, the image behind a scrim, overlay type. An event
 * without one - which is most of them - gets a plain card that is exactly as
 * tall as its content.
 *
 * They used to be the same rule. `min-height: 310px` with the content bottom
 * aligned meant an event with no artwork opened on roughly 150px of empty
 * black with the title pinned under it, and the RSVP box floating in the
 * lower right of the same void. It was the first thing on the page and the
 * worst thing on it. Everything the banner needs now lives behind
 * `.has-banner`, so no banner costs nothing.
 */
.event-hero { position: relative; isolation: isolate; display: grid; grid-template-columns: minmax(0,1fr) 340px; gap: 26px; overflow: hidden; padding: 24px 26px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); }
/* Not decoration for its own sake: the plain card is otherwise the same flat
   near-black as the page behind it, and the hero has to read as the top of
   something. One soft accent wash off the top-left corner is enough. */
.event-hero:not(.has-banner)::after { content: ''; position: absolute; z-index: -1; inset: 0; background: radial-gradient(118% 150% at 0% 0%, rgba(88,161,33,.10), transparent 58%); }
.event-hero.has-banner { min-height: 300px; padding: 30px 32px; border-color: transparent; background: var(--surface-2); }
.event-hero.has-banner::before { content: ''; position: absolute; z-index: -2; inset: 0; background: var(--event-banner) center / cover no-repeat; }.event-hero.has-banner::after { content: ''; position: absolute; z-index: -1; inset: 0; background: linear-gradient(100deg, rgba(8,13,20,.95), rgba(8,13,20,.78) 56%, rgba(8,13,20,.7)); }
.event-hero.has-banner .event-hero-main { justify-content: end; }
.event-hero-main { display: flex; min-width: 0; flex-direction: column; align-items: stretch; }
.event-hero-top { display: flex; align-items: center; justify-content: space-between; gap: 12px 16px; flex-wrap: wrap; }
.event-host { display: inline-flex; align-items: center; min-width: 0; gap: 9px; color: var(--text); text-decoration: none; font-size: 13px; font-weight: 700; }.event-host .avatar-img, .event-host .avatar-fallback { width: 36px; height: 36px; }.event-host span { overflow-wrap: anywhere; }.event-host:hover span { color: var(--accent); }
.event-host.is-platform { color: var(--muted); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }.event-host.is-platform::before { content: ''; width: 20px; height: 2px; background: var(--accent); }
/* Status, format and recurrence as pills on the title line - three facts that
   used to be a sentence fragment tacked onto the end of the stats row. */
.event-hero-flags { display: flex; flex-wrap: wrap; gap: 6px; }
.event-flag { padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface-alt); color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.event-flag.is-cancelled { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.event-hero h1 { margin: 16px 0 0; font-size: clamp(27px, 3.4vw, 40px); line-height: 1.06; letter-spacing: -.03em; overflow-wrap: anywhere; }
/* Date chip + when/where, the two facts a visitor came for, on one line. */
.event-hero-when { display: flex; align-items: center; gap: 14px; margin-top: 18px; }
.event-date-chip { display: grid; place-items: center; align-content: center; flex: 0 0 58px; width: 58px; height: 58px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-alt); line-height: 1; }
.event-date-chip b { color: var(--accent); font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.event-date-chip strong { margin-top: 5px; font-size: 22px; font-weight: 750; letter-spacing: -.02em; }
.event-hero-meta { display: grid; min-width: 0; gap: 7px; }
.event-hero-datetime { color: var(--text); font-size: 15px; font-weight: 650; overflow-wrap: anywhere; }
.event-hero-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.event-hero-tags > * { min-width: 0; padding: 3px 9px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: var(--muted); font-size: 12px; overflow-wrap: anywhere; text-decoration: none; }
.event-hero-tags a { color: var(--accent); font-weight: 650; }.event-hero-tags a:hover { border-color: var(--accent-dim); background: var(--accent-soft); }
/* Going / Maybe / Waiting as tiles rather than a run of small grey text. They
   are the answer to the only question a visitor arrives with. */
.event-hero-stats { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.event-hero-stats span { display: grid; gap: 3px; min-width: 82px; padding: 9px 12px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--muted); font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
.event-hero-stats b { color: var(--text); font-size: 21px; font-weight: 750; letter-spacing: -.02em; }
.event-hero-stats i { color: var(--muted); font-size: 14px; font-style: normal; font-weight: 650; }
/* Over artwork the same pieces need translucent shells and lifted type. */
.event-hero.has-banner .event-host { color: #e8edf5; }
.event-hero.has-banner .event-hero-datetime { color: #f2f5f9; }
.event-hero.has-banner .event-hero-stats b { color: #fff; }
.event-hero.has-banner .event-date-chip, .event-hero.has-banner .event-hero-tags > *, .event-hero.has-banner .event-hero-stats span, .event-hero.has-banner .event-flag { border-color: rgba(255,255,255,.16); background: rgba(9,14,22,.6); color: #c3ccd8; }
.event-hero.has-banner .event-flag.is-cancelled { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
/* Top aligned, not bottom aligned: the panel is the first thing to act on, and
   `align-self: end` left it hanging off the lower right of the hero with the
   gap above it growing with every line of title. */
.event-rsvp-panel { align-self: start; display: grid; align-content: start; padding: 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-alt); }
.event-hero.has-banner .event-rsvp-panel { border-color: rgba(255,255,255,.14); background: rgba(9,14,22,.82); backdrop-filter: blur(8px); }
.event-rsvp-panel > .widget-title { margin-bottom: 10px; }
.event-rsvp-panel > .badge { display: inline-flex; justify-self: start; margin-bottom: 8px; }
.event-rsvp-note { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
.event-rsvp-panel form:first-of-type { margin-top: 8px !important; padding-top: 10px !important; }.event-rsvp-panel input:not([type="checkbox"]):not([type="radio"]), .event-rsvp-panel select, .event-rsvp-panel textarea { max-width: 100%; }.event-rsvp-form { border-top: 1px solid var(--border-soft); }.event-rsvp-actions, .event-inline-actions { display: flex; gap: 8px; flex-wrap: wrap; }.event-reminder-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; font-size: 13px; }.event-reminder-form select { min-width: 0; flex: 1; }.event-invite-banner { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }.event-page > .card, .event-page > details.card { max-width: 900px; margin-left: 0; }
/*
 * Section nav and the report control share one bar, and above 900px it sticks
 * under the topbar - which is what makes a page this long navigable without
 * scrolling back to the top. Only above 900px: below it the topbar is allowed
 * to wrap past its 56px, so a bar pinned to 56px would slide under it, and the
 * layout has stacked into one column where vertical space is worth more than a
 * pinned index anyway. z-index stays below the topbar's 40 so the two never
 * fight, and the anchors carry a matching scroll-margin so a jump does not land
 * the heading underneath the bar that sent you there.
 */
.event-toolbar { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding: 5px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
@media (min-width: 901px) { .event-toolbar { position: sticky; z-index: 20; top: 56px; } }
.event-section-nav { display: flex; align-items: center; flex: 1; gap: 4px; min-width: 0; overflow-x: auto; scrollbar-width: none; }.event-section-nav::-webkit-scrollbar { display: none; }.event-section-nav a { flex: 0 0 auto; padding: 8px 12px; border-radius: 7px; color: var(--muted); font-size: 13px; font-weight: 650; white-space: nowrap; text-decoration: none; }.event-section-nav a:hover { color: var(--text); background: var(--surface-hover); }
.event-section-nav a.is-active { background: var(--accent-soft); color: var(--accent); }
.event-toolbar-aside { flex: 0 0 auto; padding-left: 8px; border-left: 1px solid var(--border-soft); }
.event-main-content > section[id], .event-main-content > .discussion[id], .event-side-rail > section[id], #attachments { scroll-margin-top: 118px; }
.event-content-grid { display: grid; grid-template-columns: minmax(0, 1fr) 344px; gap: 20px; align-items: start; margin-top: 16px; }.event-main-content { display: grid; align-content: start; gap: 16px; min-width: 0; }.event-main-content > .card, .event-main-content > details.card { max-width: none; margin: 0; }.event-side-rail { display: grid; gap: 16px; }.event-rail-card, .event-overview-card { padding: 18px; }
/*
 * One head for every section in the main column. The page previously had
 * three: a lone eyebrow on Overview, eyebrow-over-heading on Announcements
 * and the waiting list, and a bare 15px <h2> floating above a card for
 * Attendees and Series occurrences - which is why those two read as
 * afterthoughts rather than as parts of the same page.
 */
.event-section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 12px; }
.event-section-head h2 { margin: 5px 0 0; font-size: 16px; letter-spacing: -.01em; }
.event-section-head > span { flex: 0 0 auto; color: var(--muted); font-size: 11px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; }
.event-overview-copy { max-width: 780px; margin: 0; color: var(--text); line-height: 1.6; white-space: pre-wrap; }.event-overview-empty { color: var(--muted); }.event-empty-note { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }.event-rules { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border-soft); }.event-rules h3 { margin: 0 0 8px; font-size: 13px; }.event-rules p { margin: 0; color: var(--muted); white-space: pre-wrap; line-height: 1.55; }
.event-details-list { display: grid; gap: 10px; margin: 12px 0 0; font-size: 13px; }.event-details-list > div { display: grid; grid-template-columns: 105px minmax(0,1fr); gap: 10px; }.event-details-list dt { color: var(--muted); }.event-details-list dd { margin: 0; overflow-wrap: anywhere; }
/* A capped role gets a bar; an uncapped one has nothing to be a fraction of
   and keeps the bare count. */
.event-role-row { display: grid; gap: 8px; padding: 11px 0; border-bottom: 1px solid var(--border-soft); font-size: 13px; }.event-role-row:last-of-type { border-bottom: 0; }
.event-role-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.event-role-head > span { display: grid; min-width: 0; gap: 3px; }.event-role-head small { color: var(--muted); font-size: 11px; }.event-role-head strong { color: var(--accent); white-space: nowrap; }
.event-role-bar { overflow: hidden; height: 5px; border-radius: 999px; background: var(--surface-2); }.event-role-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.event-role-row.is-full .event-role-head strong { color: var(--warning); }.event-role-row.is-full .event-role-bar > span { background: var(--warning); }
/* Quick links: voice, stream, map, external page - the only things on this
   page anyone clicks in a hurry, so they look clickable. */
.event-link-list { display: grid; gap: 7px; margin-top: 12px; }
.event-link-list a { display: grid; gap: 2px; padding: 10px 12px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--text); font-size: 13px; font-weight: 650; text-decoration: none; }
.event-link-list a:hover { border-color: var(--accent-dim); background: var(--surface-hover); color: var(--accent); }
.event-link-list small { color: var(--muted); font-size: 11px; font-weight: 400; }

.discussion { margin-top:20px; }.discussion-head { display:flex; align-items:end; justify-content:space-between; gap:12px; margin-bottom:16px; }.discussion-head h2 { margin:3px 0 0; font-size:18px; }.discussion-head > span { color:var(--muted); font-size:12px; }.discussion-list { display:grid; gap:14px; }.discussion-comment { display:flex; gap:9px; min-width:0; }.discussion-comment.is-reply { margin:10px 0 0 18px; padding-left:10px; border-left:1px solid var(--border-soft); }.discussion-comment .avatar-img, .discussion-comment .avatar-fallback { width:30px; height:30px; flex:0 0 30px; font-size:11px; }.discussion-comment-body { min-width:0; flex:1; }.discussion-comment-copy { padding:9px 11px; border:1px solid var(--border-soft); border-radius:0 var(--radius-sm) var(--radius-sm) var(--radius-sm); background:var(--surface-alt); }.discussion-comment-copy b { font-size:13px; }.discussion-comment-copy time { margin-left:7px; color:var(--muted); font-size:11px; }.discussion-comment-copy p { margin:4px 0 0; white-space:pre-wrap; font-size:13px; line-height:1.45; }.discussion-comment-actions { display:flex; align-items:center; gap:10px; padding:4px 0 0 6px; font-size:12px; }.discussion-comment-actions form { margin:0; }.discussion-comment-actions a { color:var(--muted); text-decoration:none; }.discussion-comment-actions a:hover { color:var(--text); }.discussion-comment-actions .btn { height:auto; padding:0; color:var(--muted); font-size:12px; }.discussion-comment-actions .btn.is-active { color:var(--accent); }.discussion-reply-form { display:flex; gap:6px; margin-top:7px; }.discussion-reply-form input, .discussion-composer textarea { width:100%; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface); color:var(--text); font:inherit; }.discussion-reply-form input { min-width:0; padding:7px 9px; font-size:12px; }.discussion-empty { margin:0; padding:16px; border:1px dashed var(--border); border-radius:var(--radius-sm); color:var(--muted); text-align:center; font-size:13px; }.discussion-composer { display:grid; gap:9px; margin-top:16px; padding-top:16px; border-top:1px solid var(--border-soft); }.discussion-composer textarea { min-height:82px; padding:10px 12px; resize:vertical; }.discussion-composer .btn { justify-self:end; }
.event-reminders-card p, .event-clan-card p { margin:12px 0 0; color:var(--muted); font-size:13px; line-height:1.5; }.event-reminder-list { display:grid; gap:5px; margin-top:12px; }.event-reminder-list span { color:var(--muted); font-size:11px; }.event-clan-link { display:flex; align-items:center; gap:9px; margin-top:12px; color:var(--text); text-decoration:none; }.event-clan-link > span { display:grid; gap:3px; font-size:13px; }.event-clan-link small { color:var(--muted); font-size:11px; }.event-clan-link:hover b { color:var(--accent); }
.event-attachments-card { max-width:none !important; margin-top:16px; padding:18px; }.event-attachment-list { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin-top:12px; }.event-attachment-card { display:grid; grid-template-columns:76px minmax(0,1fr) auto; align-items:center; min-width:0; min-height:74px; overflow:hidden; border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:var(--surface-alt); }.event-attachment-preview { display:flex; align-items:center; justify-content:center; align-self:stretch; min-width:0; overflow:hidden; background:var(--surface-2); color:var(--muted); text-decoration:none; }.event-attachment-preview img { width:100%; height:100%; min-height:72px; object-fit:cover; }.event-attachment-kind { font-size:10px; font-weight:800; letter-spacing:.08em; }.event-attachment-copy { display:grid; min-width:0; gap:4px; padding:10px; }.event-attachment-copy strong { overflow:hidden; color:var(--text); font-size:13px; text-overflow:ellipsis; white-space:nowrap; }.event-attachment-copy span { color:var(--muted); font-size:11px; }.event-attachment-open { padding:10px 12px; color:var(--accent); font-size:12px; font-weight:700; text-decoration:none; }.event-attachment-open:hover { color:var(--text); }
.event-manage-panel { margin-top: 14px; }.event-manage-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }.event-manage-form > input:not([type="checkbox"]):not([type="radio"]), .event-manage-form > select, .event-manage-form > textarea { min-width: 0; max-width: 100%; }.event-file-form input[type="file"] { max-width: 100%; }.event-question-options { display: flex; gap: 8px; }.event-question-options select { min-width: 0; }
.event-manage-summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; cursor: pointer; font-size: 14px; font-weight: 700; }.event-manage-summary > span { color: var(--muted); font-size: 11px; font-weight: 500; letter-spacing: .04em; text-transform: uppercase; }
/* Attendees and series occurrences: rows that respond to a pointer, with the
   status as a pill on the right instead of 12px grey text trailing the name. */
.event-attendee-list, .event-series-list { display: grid; }
.event-series-row, .event-attendee-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 10px; border-bottom: 1px solid var(--border-soft); border-radius: var(--radius-sm); color: inherit; font-size: 13px; text-decoration: none; }
.event-series-row > *, .event-attendee-row > * { min-width: 0; overflow-wrap: anywhere; }
.event-attendee-list > .event-attendee-row:last-child, .event-series-list > .event-series-row:last-child { border-bottom: 0; }
.event-series-row:hover, .event-attendee-row:hover { background: var(--surface-hover); }
.event-series-row.is-current { background: var(--accent-soft); font-weight: 700; }
.event-series-going { flex: 0 0 auto; color: var(--muted); font-size: 12px; white-space: nowrap; }
.event-attendee-person { display: flex; align-items: center; min-width: 0; gap: 10px; color: inherit; font-weight: 650; text-decoration: none; }.event-attendee-person:hover span { color: var(--accent); }.event-attendee-person .avatar-img, .event-attendee-person .avatar-fallback { width: 32px; height: 32px; flex: 0 0 32px; }
.event-attendee-status { flex: 0 0 auto; padding: 3px 10px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: var(--muted); font-size: 11px; font-weight: 650; white-space: nowrap; }
.event-attendee-status.is-maybe { border-color: var(--warning); background: var(--warning-soft); color: var(--warning); }
.event-attendee-answers { display: grid; gap: 4px; margin: 0 10px 8px 52px; padding: 8px 10px; border-left: 2px solid var(--border); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: var(--surface-alt); }
.event-attendee-answers > div { display: grid; grid-template-columns: minmax(100px,160px) minmax(0,1fr); gap: 10px; font-size: 12px; }
.event-attendee-answers dt { color: var(--muted); }.event-attendee-answers dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
.event-waitlist { margin: 20px 0; }.event-waitlist-list { display:grid; }.event-waitlist-entry { display:grid; grid-template-columns:32px minmax(0,1fr) auto; align-items:center; gap:10px; min-width:0; padding:9px 8px; border-bottom:1px solid var(--border-soft); border-radius:var(--radius-sm); }.event-waitlist-entry.is-me { background:var(--accent-soft); }.event-waitlist-position { color:var(--muted); font-size:12px; font-weight:750; text-align:center; }.event-waitlist-entry .event-attendee-person span { display:grid; min-width:0; }.event-waitlist-entry .event-attendee-person b { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }.event-waitlist-entry .event-attendee-person small { color:var(--accent); font-size:10px; font-weight:700; text-transform:uppercase; }.event-waitlist-role { color:var(--muted); font-size:12px; text-align:right; }.event-waitlist-answers { display:grid; gap:4px; margin:0 8px 8px 50px; padding:8px 10px; border-left:2px solid var(--border); background:var(--surface-alt); }.event-waitlist-answers > div { display:grid; grid-template-columns:minmax(100px,160px) minmax(0,1fr); gap:10px; font-size:12px; }.event-waitlist-answers dt { color:var(--muted); }.event-waitlist-answers dd { min-width:0; margin:0; overflow-wrap:anywhere; }.event-waitlist-note, .event-waitlist-empty { margin:10px 0 0; color:var(--muted); font-size:12px; line-height:1.5; }
@media (max-width: 560px) { .event-section-head { align-items:flex-start; flex-direction:column; gap:4px; }.event-waitlist-entry { grid-template-columns:28px minmax(0,1fr); }.event-waitlist-role { grid-column:2; text-align:left; }.event-waitlist-answers { margin-left:38px; }.event-waitlist-answers > div { grid-template-columns:1fr; gap:2px; } }
@media (max-width: 980px) { .event-attachment-list { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width: 560px) { .event-attachment-list { grid-template-columns:1fr; }.event-attachment-card { grid-template-columns:68px minmax(0,1fr) auto; } }

/* ---- Messages -------------------------------------------------------- */
.messages-page { max-width: 1700px; padding-top: 20px; padding-bottom: 64px; }
.messages-layout { display: grid; grid-template-columns: 300px minmax(0,1fr) 270px; gap: 16px; align-items: stretch; min-height: min(760px, calc(100vh - 130px)); }.messages-layout:has(.messages-empty-state) { grid-template-columns:350px minmax(0,1fr); }
.messages-sidebar { display: flex; min-width: 0; flex-direction: column; gap: 10px; }.messages-conversation-list { flex: 1; min-height: 0; padding: 6px; overflow-y: auto; }.messages-group-start { flex: 0 0 auto; }
.messages-sidebar-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 8px 4px; }.messages-sidebar-head > div { display: grid; gap: 3px; }.messages-sidebar-head span { color: var(--muted); font-size: 11px; }
.conversation-row { display: flex; align-items: center; gap: 10px; padding: 11px 10px; border-radius: var(--radius-sm); color: inherit; text-decoration: none; }.conversation-row:hover { background: var(--surface-hover); }.conversation-row.is-active { background: var(--surface-2); box-shadow: inset 3px 0 0 var(--accent); }
.messages-list-empty { display: grid; place-items: center; gap: 6px; min-height: 190px; padding: 20px; text-align: center; color: var(--muted); }.messages-list-empty > span { color: var(--accent); font-size: 23px; }.messages-list-empty p { margin: 0; color: var(--text); font-size: 13px; font-weight: 650; }.messages-list-empty small { max-width: 220px; line-height: 1.45; }
.messages-chat { min-width: 0; display: flex; flex-direction: column; }.messages-chat-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 0 0 14px; border-bottom: 1px solid var(--border-soft); }.messages-chat-title { min-width: 0; overflow-wrap: anywhere; font-weight: 750; font-size: 16px; }.messages-back-link { display: none; color: var(--muted); text-decoration: none; font-size: 18px; line-height: 1; }.messages-empty-state { display: grid; place-items: center; align-content: center; flex: 1; min-height: 420px; padding: 36px; text-align: center; }.messages-empty-icon { display: grid; place-items: center; width: 52px; height: 52px; margin-bottom: 6px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 50%; color: var(--accent); font-size: 23px; background: color-mix(in srgb, var(--accent) 10%, transparent); }.messages-empty-state h1 { margin: 6px 0 4px; font-size: 21px; }.messages-empty-state p { max-width: 330px; margin: 0 0 16px; color: var(--muted); line-height: 1.55; }
.message-thread { flex: 1; display: flex; flex-direction: column; gap: 12px; min-height: 380px; max-height: 590px; overflow-y: auto; padding: 18px 3px; margin-bottom: 10px; }.message-row { align-self: flex-start; max-width: min(75%, 680px); min-width: 0; }.message-row.has-preview { width:min(82%,560px); max-width:560px; }.message-row.is-mine { align-self: flex-end; }.message-row.is-mine > .message-bubble { background: var(--accent) !important; color: var(--bg) !important; }.message-bubble { border-radius: var(--radius-sm); padding: 8px 12px; overflow-wrap: anywhere; white-space: pre-wrap; font-size: 14px; }.message-media-link { display: block; margin-bottom: 4px; }.message-media-image, .message-media-video { display: block; width: auto; max-width: min(100%, 260px); max-height: 260px; margin-bottom: 4px; border-radius: var(--radius-sm); }.message-native-embed, .message-gif-preview, .message-link-card { display:block; overflow:hidden; margin-top:6px; border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:var(--surface-alt); color:var(--text); text-align:left; text-decoration:none; }.message-preview-label { display:block; overflow:hidden; color:var(--muted); font-size:10px; font-weight:750; letter-spacing:.05em; line-height:1.3; text-overflow:ellipsis; text-transform:uppercase; white-space:nowrap; }.message-native-embed > .message-preview-label, .message-gif-preview > .message-preview-label { padding:8px 10px; border-bottom:1px solid var(--border-soft); }.message-native-embed-frame { position:relative; aspect-ratio:16 / 9; background:#060708; }.message-native-embed-frame iframe { display:block; width:100%; height:100%; border:0; }.message-gif-preview img { display:block; width:100%; max-height:420px; object-fit:contain; background:#060708; }.message-link-card { display:grid; grid-template-columns:120px minmax(0,1fr); }.message-link-card:hover { border-color:var(--border); text-decoration:none; }.message-link-card > img { width:120px; height:100%; min-height:86px; object-fit:cover; background:#060708; }.message-link-card-copy { display:grid; align-content:center; min-width:0; gap:4px; padding:10px 12px; }.message-link-card-copy strong { overflow:hidden; color:var(--text); font-size:13px; line-height:1.35; text-overflow:ellipsis; white-space:nowrap; }.message-link-card-copy > span:last-child:not(.message-preview-label) { display:-webkit-box; overflow:hidden; color:var(--muted); font-size:11px; line-height:1.4; -webkit-box-orient:vertical; -webkit-line-clamp:2; }.message-meta { color: var(--muted); font-size: 11px; margin-top: 3px; text-align: left; }.message-row.is-mine .message-meta { text-align: right; }.message-edit-form, .messages-member-form { display: flex; gap: 6px; margin-top: 6px; }.message-edit-form input, .messages-member-form input { min-width: 0; }
.message-poll-status { min-height:14px; margin:-5px 0 5px; color:var(--muted); font-size:11px; text-align:center; }
.message-composer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-top: 12px; border-top: 1px solid var(--border-soft); }.message-composer-input { flex: 1; min-width: 160px; padding: 10px 12px; }.message-composer-attachment { cursor: pointer; }.message-composer-file-hint { color:var(--muted); font-size:11px; white-space:nowrap; }
.messages-context { display:grid; align-content:start; gap:12px; min-width:0; }.messages-context-card { display:grid; gap:12px; }.messages-context-identity { display:flex; align-items:center; gap:9px; min-width:0; }.messages-context-identity > span, .messages-members-list a > span { display:grid; min-width:0; gap:3px; font-size:13px; }.messages-context-identity small, .messages-members-list small, .messages-context-empty { color:var(--muted); font-size:11px; }.messages-context-count { color:var(--muted); font-size:12px; }.messages-media-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:5px; }.messages-media-grid a { display:grid; aspect-ratio:1; overflow:hidden; border:1px solid var(--border-soft); border-radius:6px; background:var(--surface-alt); color:var(--muted); place-items:center; text-decoration:none; font-size:10px; }.messages-media-grid img { width:100%; height:100%; object-fit:cover; }.messages-context-empty { margin:0; line-height:1.5; }.messages-members-list { display:grid; gap:7px; }.messages-members-list a { display:flex; align-items:center; gap:8px; color:var(--text); text-decoration:none; }.messages-members-list a:hover b { color:var(--accent); }.messages-members-list .avatar-img, .messages-members-list .avatar-fallback { width:27px; height:27px; font-size:10px; }

/* ---- Looking For Play -------------------------------------------------
 * The listing index and a single request's detail page.
 *
 * Both templates predate this component library and were built entirely
 * from inline styles on bare <div>s, so they inherited none of it. Three
 * things were actually broken rather than merely plain:
 *
 * 1. The detail page listed six one-word facts down a plain <dl>, which
 *    spent roughly 300px of scroll saying "8 players, PC, EU, ENG, TvT,
 *    medium". .lfp-facts says the same thing in one hairline grid.
 * 2. Its reply composer put the <label> *beside* the textarea, because an
 *    inline label next to an inline-block textarea with no width is
 *    exactly what a browser does when nobody says otherwise. The textarea
 *    was left at its default cols, about a third of the column.
 * 3. Nothing said how long a post had left without reading a UTC
 *    timestamp. .lfp-countdown is the one piece of colour on the page and
 *    it is spent on that, because an LFP post is only worth answering
 *    while it is open - see LfpService::expiry() for where the thresholds
 *    live and why they are shared with the listing.
 *
 * Everything else here is the shared vocabulary this feature never got:
 * .card, .pill, the avatar sizes, .field, .btn.
 */

.lfp-page { max-width: 900px; padding-top: 20px; padding-bottom: 64px; }
.lfp-head { margin-bottom: 20px; }
.lfp-head h1 { margin: 0 0 6px; font-size: var(--type-page); }
.lfp-head p { max-width: 62ch; margin: 0; color: var(--muted); font-size: 13px; line-height: 1.55; }

.lfp-eyebrow { display: block; color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }

/* A post's remaining life. The dot carries the same state as the text, so
   the chip still reads as urgent in a screenshot without colour. */
.lfp-countdown { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; background: var(--surface-hover); color: var(--muted); font-size: 11px; font-weight: 700; white-space: nowrap; }
.lfp-countdown::before { content: ""; width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: currentColor; }
.lfp-countdown.is-soon { background: var(--warning-soft); color: var(--warning); }
.lfp-countdown.is-critical { background: var(--danger-soft); color: var(--danger); }

/* ---- LFP: composer ---------------------------------------------------- */

.lfp-composer { margin-bottom: 18px; }
.lfp-composer-cta { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border: 1px dashed var(--border); border-radius: var(--radius); background: var(--surface-alt); cursor: pointer; list-style: none; }
.lfp-composer-cta::-webkit-details-marker { display: none; }
.lfp-composer-cta:hover { border-color: var(--accent-dim); background: var(--surface); }
.lfp-composer-mark { display: grid; flex: 0 0 32px; place-items: center; width: 32px; height: 32px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); font-size: 19px; font-weight: 700; line-height: 1; }
.lfp-composer-cta-copy { display: grid; gap: 2px; min-width: 0; }
.lfp-composer-cta-copy strong { color: var(--text); font-size: 14px; }
.lfp-composer-cta-copy small { color: var(--muted); font-size: 12px; }
/* Open, the summary stops being a button and becomes the form's own header. */
.lfp-composer[open] .lfp-composer-cta { border-style: solid; border-bottom-color: transparent; border-bottom-left-radius: 0; border-bottom-right-radius: 0; background: var(--surface); }
.lfp-composer[open] .lfp-composer-mark { transform: rotate(45deg); }
.lfp-composer[open] .lfp-composer-form { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; }
.lfp-form-alert { margin: 0 0 14px; padding: 9px 12px; border: 1px solid var(--danger); border-radius: var(--radius-sm); background: var(--danger-soft); color: var(--danger); font-size: 13px; }
.lfp-form-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 14px; }
.lfp-form-grid .field { min-width: 0; }
.lfp-form-grid .lfp-span-all { grid-column: 1 / -1; }
/* "optional", "hours" - the qualifier belongs to the label but not to its
   weight, or every second field shouts a word nobody needs to read twice. */
.lfp-form-grid label span { color: var(--muted-2); font-weight: 500; text-transform: none; letter-spacing: 0; }
.lfp-composer-foot { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; padding-top: 14px; border-top: 1px solid var(--border-soft); }

/* ---- LFP: your own posts --------------------------------------------- */

.lfp-mine { margin-bottom: 18px; }
.lfp-mine > summary { color: var(--muted); cursor: pointer; font-size: 13px; }
.lfp-mine > summary:hover { color: var(--text); }
.lfp-mine-list { display: grid; gap: 8px; margin-top: 10px; }
.lfp-mine-row { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.lfp-mine-row > div { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.lfp-mine-row form { margin: 0; }
.lfp-mine-status { color: var(--muted); font-size: 12px; }
.lfp-mine-status::before { content: "· "; }

/* ---- LFP: filters and listing ----------------------------------------- */

.lfp-filter-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; padding: 10px; }
.lfp-filter-bar select, .lfp-filter-bar input[type="text"] { min-width: 0; padding: 8px 10px; font-size: 13px; }
.lfp-filter-bar select { flex: 1 1 160px; max-width: 220px; }
.lfp-filter-bar input[type="text"] { flex: 1 1 130px; max-width: 170px; }

.lfp-list { display: grid; gap: 10px; }
.lfp-card { display: flex; align-items: flex-start; gap: 14px; padding: 16px; }
.lfp-card:hover { border-color: var(--border); }
/* 3:4, the ratio box art is drawn in - see components/game-poster.php for why
   the catalog is treated as portrait everywhere rather than cropped wide. */
.lfp-card-cover { display: block; flex: 0 0 auto; width: 54px; height: 72px; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); }
.lfp-card-cover .avatar-img, .lfp-card-cover .avatar-fallback { width: 100%; height: 100%; border-radius: 0; font-size: 18px; }
.lfp-card-body { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.lfp-card-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lfp-card-game { color: var(--text); font-size: 15px; font-weight: 750; text-decoration: none; }
.lfp-card-game:hover { color: var(--accent); }
.lfp-card-byline { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 5px 0 0; color: var(--muted); font-size: 12px; }
/* Two lines in the list, the whole thing on the listing's own page. A
   description is free text with its own line breaks in it, and one post that
   writes out a Sunday schedule should not make every row below it scroll.
   The breaks are collapsed here rather than preserved: with pre-wrap the
   clamp counted the author's blank line as its second line and printed an
   ellipsis on its own. Presentation only - the stored text still has them,
   and the listing's own page shows them. */
.lfp-card-copy { display: -webkit-box; overflow: hidden; margin: 9px 0 0; color: var(--text-soft); font-size: 13px; line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.lfp-card-actions { display: flex; flex: 0 0 auto; flex-direction: column; gap: 8px; }
.lfp-card-actions .btn { width: 100%; }

/* The facts a listing is scanned for. One shape, and the count of players
   still wanted gets the accent because that is the number being scanned. */
.lfp-chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0 0; padding: 0; list-style: none; }
.lfp-chips li { padding: 3px 9px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: var(--text-soft); font-size: 12px; }
.lfp-chips li.is-key { border-color: transparent; background: var(--accent-soft); color: var(--accent); font-weight: 700; }

.lfp-pagination { display: flex; gap: 8px; margin-top: 16px; }

/* ---- LFP: a single request -------------------------------------------- */

.lfp-back { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 14px; color: var(--muted); font-size: 13px; text-decoration: none; }
.lfp-back:hover { color: var(--text); }
.lfp-detail { padding: 0; overflow: hidden; overflow-wrap: anywhere; }
.lfp-detail-body { padding: 22px; }

/* The header runs to the card's own edges so the game's banner is a banner
   rather than a picture pasted inside a panel - the same construction the game
   hub and the event page use, at the smaller scale a single post deserves.
   isolation + negative z-index keep the artwork and its scrim behind the text
   without either one needing a stacking context of its own. */
.lfp-hero { position: relative; isolation: isolate; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: end; gap: 18px; overflow: hidden; padding: 22px; }
.lfp-hero::before { content: ""; position: absolute; z-index: -2; inset: 0; background: var(--lfp-header) center / cover no-repeat; }
.lfp-hero::after { content: ""; position: absolute; z-index: -1; inset: 0; background: linear-gradient(90deg, rgba(8, 12, 16, 0.95), rgba(8, 12, 16, 0.82) 58%, rgba(8, 12, 16, 0.7)); }
/* A game nobody has uploaded a banner for still gets a header, not a hole. */
.lfp-hero.no-image::before { background: radial-gradient(circle at 82% 12%, rgba(88, 161, 33, 0.22), transparent 42%), var(--surface-2); }
.lfp-hero-cover { display: block; flex: 0 0 auto; width: 88px; height: 118px; overflow: hidden; border: 2px solid rgba(255, 255, 255, 0.18); border-radius: var(--radius-sm); background: var(--surface); box-shadow: 0 10px 22px rgba(0, 0, 0, 0.42); }
.lfp-hero-cover .avatar-img, .lfp-hero-cover .avatar-fallback { width: 100%; height: 100%; border-radius: 0; font-size: 28px; }
.lfp-hero-content { min-width: 0; }
.lfp-hero-content h1 { margin: 4px 0 0; font-size: 30px; letter-spacing: -0.025em; line-height: 1.12; }
.lfp-hero-content h1 a { color: #fff; text-decoration: none; }
.lfp-hero-content h1 a:hover { color: var(--accent); }
.lfp-hero-flags { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }

.lfp-byline { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin: 10px 0 0; font-size: 13px; }
.lfp-byline span { color: var(--muted); font-size: 12px; }
.lfp-byline-person { display: inline-flex; align-items: center; gap: 7px; min-width: 0; color: var(--text); text-decoration: none; }
.lfp-byline-person strong { overflow-wrap: anywhere; font-weight: 700; }
.lfp-byline-person:hover strong { color: var(--accent); }

/*
 * Flex, not grid, and the column count comes from the template.
 *
 * As a six-item auto-fit grid this wrapped 5 + 1 and left four empty cells
 * sitting in the frame beside "Skill / rank". The template counts the facts and
 * sets --lfp-fact-cols so the row divides them (see the comment there); flex is
 * what lets a short last row - five facts as 3 + 2 - share the width between
 * the tiles that are on it instead of leaving a hole.
 *
 * The basis is a hair under 100%/n so a rounded-up sub-pixel cannot push the
 * last tile of a row onto the next one.
 */
.lfp-facts { display: flex; flex-wrap: wrap; gap: 1px; margin: 20px 0 0; overflow: hidden; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--border-soft); }
.lfp-facts > div { flex: 1 1 calc(100% / var(--lfp-fact-cols, 3) - 2px); min-width: 0; padding: 11px 13px; background: var(--surface-alt); }
.lfp-facts dt { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }
.lfp-facts dd { margin: 5px 0 0; overflow-wrap: break-word; color: var(--text); font-size: 14px; font-weight: 650; }
.lfp-facts .is-key dd { color: var(--accent); font-size: 21px; font-weight: 800; line-height: 1.05; }

.lfp-detail-copy { margin: 18px 0 0; padding-left: 13px; border-left: 2px solid var(--accent-dim); color: var(--text); font-size: 14px; line-height: 1.6; white-space: pre-wrap; }
.lfp-detail-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
.lfp-detail-closes { margin-left: auto; color: var(--muted-2); font-size: 12px; }

/* ---- LFP: replies ------------------------------------------------------ */

.lfp-replies { margin-top: 26px; scroll-margin-top: 88px; }
.lfp-replies-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.lfp-replies-head h2 { margin: 0; font-size: 18px; }
.lfp-replies-empty { margin: 0 0 12px; padding: 18px; border: 1px dashed var(--border); border-radius: var(--radius-sm); color: var(--muted); font-size: 13px; text-align: center; }
.lfp-reply-list { display: grid; gap: 10px; margin-bottom: 14px; }
.lfp-reply { display: flex; gap: 12px; padding: 14px 16px; overflow-wrap: anywhere; scroll-margin-top: 88px; }
/* The person who opened the request, answering in their own thread: the one
   row a reader needs to find first. */
.lfp-reply.is-op { border-color: var(--accent-dim); background: var(--surface-2); }
.lfp-reply-body { flex: 1; min-width: 0; }
.lfp-reply-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lfp-reply-head a { color: var(--text); text-decoration: none; }
.lfp-reply-head a:hover { color: var(--accent); }
.lfp-reply-head time { margin-left: auto; color: var(--muted); font-size: 12px; }
.lfp-reply-copy { margin: 7px 0 0; color: var(--text-soft); font-size: 14px; line-height: 1.55; white-space: pre-wrap; }
.lfp-reply-actions { display: flex; align-items: center; gap: 14px; margin-top: 9px; font-size: 12px; }
.lfp-reply-actions form { margin: 0; }

.lfp-reply-form { display: grid; gap: 10px; }
.lfp-reply-form label { color: var(--text); font-size: 13px; font-weight: 700; }
.lfp-reply-form-row { display: flex; align-items: flex-start; gap: 12px; }
.lfp-reply-form-row > .avatar-sm { flex: 0 0 auto; }
.lfp-reply-form textarea { flex: 1; min-width: 0; padding: 11px 13px; resize: vertical; }
/* The hint lines up under the textarea, not under the avatar beside it. */
.lfp-reply-form-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-left: 48px; }
.lfp-reply-form-foot small { color: var(--muted); font-size: 12px; }

@media (max-width: 720px) {
    .lfp-page { padding-top: 14px; padding-bottom: 44px; }
    .lfp-head h1 { font-size: 23px; }
    .lfp-form-grid { grid-template-columns: minmax(0, 1fr); }
    .lfp-composer-foot .btn { width: 100%; }
    .lfp-filter-bar select, .lfp-filter-bar input[type="text"] { flex: 1 1 100%; max-width: none; }
    .lfp-filter-bar .btn { flex: 1 1 auto; }
    .lfp-card { flex-wrap: wrap; padding: 14px; }
    .lfp-card-cover { width: 46px; height: 61px; }
    .lfp-card-actions { flex-direction: row; flex: 1 1 100%; }
    .lfp-card-actions .btn { flex: 1; }
    .lfp-detail-body { padding: 16px; }
    /* Three columns will not fit, and the flags are the one part that can move
       without the cover and the game's name coming apart. */
    .lfp-hero { grid-template-columns: auto minmax(0, 1fr); gap: 14px; padding: 16px; }
    .lfp-hero-cover { width: 66px; height: 88px; }
    .lfp-hero-flags { grid-column: 1 / -1; justify-content: flex-start; }
    .lfp-hero-content h1 { font-size: 22px; }
    /* Two per row whatever the template asked for; three 11px labels do not fit
       a phone, and "Players needed" would wrap in every one of them.
       The basis is overridden on the tile rather than --lfp-fact-cols on the
       list, because the template sets that one inline and an inline declaration
       beats a stylesheet at any width, media query or not. */
    .lfp-facts > div { flex-basis: calc(50% - 2px); }
    .lfp-detail-actions .btn { flex: 1 1 auto; }
    .lfp-detail-closes { flex: 1 1 100%; margin-left: 0; }
    /* The avatar column is worth less than the width it costs here. */
    .lfp-reply-form-row > .avatar-sm { display: none; }
    .lfp-reply-form-foot { margin-left: 0; }
    .lfp-reply-form-foot .btn { width: 100%; }
}

/* ---- Settings -------------------------------------------------------- */
.settings-page { max-width: 1200px; padding-top: 28px; padding-bottom: 64px; }.settings-page-head { display:flex; align-items:end; justify-content:space-between; gap:20px; margin-bottom:22px; }.settings-page-head h1 { margin:0; font-size:30px; }.settings-page-head .section-label { margin-bottom:4px; }.settings-page-head p { margin:0; color:var(--muted); font-size:13px; }.settings-layout { display:grid; grid-template-columns:210px minmax(0,760px); gap:24px; align-items:start; }.settings-nav { position:sticky; top:78px; display:grid; gap:3px; padding:8px; }.settings-nav a { padding:9px 10px; border-radius:var(--radius-sm); color:var(--muted); text-decoration:none; font-size:13px; font-weight:650; }.settings-nav a:hover { color:var(--text); background:var(--surface-hover); }.settings-main { min-width:0; }.settings-profile-fields { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }.settings-profile-fields .field { min-width:0; }

@media (max-width: 1500px) {
    .guest-games-grid { grid-template-columns:repeat(6,minmax(0,1fr)); }
}

@media (max-width: 1200px) {
    .guest-games-grid { grid-template-columns:repeat(5,minmax(0,1fr)); }
}

@media (max-width: 900px) {
    .site-footer-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px 18px; }
    .site-footer-brand, .site-footer-cta { grid-column:1 / -1; }
    .site-footer-bottom { align-items:flex-start; flex-direction:column; }
    .guest-events-grid { grid-template-columns: 1fr; }
    .guest-section-head { align-items:flex-start; flex-direction:column; }
    .guest-hero { min-height:460px; }
    .guest-hero::before { background-position:58% center; }
    .guest-hero .hero-content { max-width:500px; }
    .guest-hero h1 { font-size:56px; }
    .guest-content { padding-top:28px; }
    .guest-games-grid { grid-template-columns:repeat(4,minmax(0,1fr)); gap:14px; }
    .settings-page-head { align-items: flex-start; flex-direction: column; }
    .settings-layout { grid-template-columns: minmax(0, 1fr); gap: 16px; }
    .settings-nav { position: static; display: flex; overflow-x: auto; gap: 4px; padding: 7px; scrollbar-width: none; }
    .settings-nav::-webkit-scrollbar { display: none; }
    .settings-nav a { flex: 0 0 auto; white-space: nowrap; }
    .container { padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right)); }
    .topbar-inner { height: auto; min-height: 56px; flex-wrap: wrap; gap: 8px 12px; padding-top: 6px; padding-bottom: 6px; }
    /* The guest bar's fixed 70px height is set outside any media query by a
       two-class selector, so it outranks the height:auto above - a media query
       adds no specificity. Left alone, the nav that wraps onto a second row has
       no room to be in and is squeezed back onto the first. */
    .guest-topbar .topbar-inner { height: auto; }
    /* flex, not width: the base rule sets flex:1, whose flex-basis:0 governs a
       flex item's main size and quietly beat the width:100% that used to be
       here - so the nav was squeezed onto the first row at about 62px and left
       to scroll inside itself, rather than taking the full second row the
       overflow-x below assumes. Visible once guests were given a nav on
       2026-09-16; members had it too. */
    .topnav a { padding: 7px 10px; }
    .topbar-actions { margin-left: auto; gap: 6px; }
    .user-menu > span { display: none; }
    .account-menu > summary { padding: 3px; }
    .hero h1 { font-size: 36px; }
    .home-grid { grid-template-columns: 1fr; }
    .home-grid.home-grid-feed { justify-content: stretch; }
    .home-grid-feed .home-feed-main { grid-row: 1; min-width: 0; }
    .home-grid-feed .home-right-rail { grid-row: 2; min-width: 0; }
    .home-grid-feed .home-left-rail { grid-row: 3; min-width: 0; }
    .profile-grid .profile-main { grid-row: 1; }
    .profile-grid .profile-left-rail { grid-row: 2; }
    .profile-grid .profile-right-rail { grid-row: 3; }
    .profile-tabs { overflow-x:auto; gap:20px; scrollbar-width:none; }
    .profile-tabs::-webkit-scrollbar { display:none; }
    .profile-tabs a { flex:0 0 auto; white-space:nowrap; }
    .clan-grid .clan-main { grid-row: 1; }
    .clan-grid .clan-left-rail { grid-row: 2; }
    .clan-grid .clan-right-rail { grid-row: 3; }
    .clan-grid { gap: 14px; padding-top: 14px; }
    .clan-grid > * { min-width: 0; }
    .clan-tabs { overflow-x: auto; gap: 20px; scrollbar-width: none; }
    .clan-tabs::-webkit-scrollbar { display: none; }
    .clan-tabs a { flex: 0 0 auto; white-space: nowrap; }
    /* The tabs need the whole width to scroll in, so the counts drop below
     * them and scroll sideways themselves rather than squeezing the labels. */
    .clan-tabs-row { flex-direction: column; align-items: stretch; gap: 0; border-bottom: 0; }
    .clan-tabs-row .clan-tabs { border-bottom: 1px solid var(--border-soft); }
    .clan-activity-strip { overflow-x: auto; padding: 10px 0 0; gap: 14px; scrollbar-width: none; }
    .clan-activity-strip::-webkit-scrollbar { display: none; }
    .clan-activity-heading { display: inline; flex: 0 0 auto; }
    .post-composer-actions { gap: 10px; }
    .banner-body { margin-top: -32px; }
    .banner-identity { align-items: center; }
    .avatar-lg .avatar-img, .avatar-lg .avatar-fallback { width: 56px; height: 56px; font-size: 18px; }
    .avatar-xl .avatar-img, .avatar-xl .avatar-fallback { width: 64px; height: 64px; font-size: 20px; }
    .data-table { display: block; overflow-x: auto; white-space: nowrap; }
    .event-hero, .event-hero.has-banner { grid-template-columns: 1fr; gap: 18px; min-height: 0; padding: 20px; }.event-rsvp-panel { width: 100%; }.event-page > .card, .event-page > details.card { max-width: none; }.event-content-grid { grid-template-columns: 1fr; }.event-side-rail { grid-row: 2; grid-template-columns: repeat(2, minmax(0, 1fr)); }.event-clan-card:last-child { grid-column: 1 / -1; }.event-main-content { grid-row: 1; }
    .game-hero { grid-template-columns: 112px minmax(0, 1fr); gap: 16px; padding: 20px; min-height: 0; }.game-cover { width: 112px; height: 142px; }.game-hero-actions { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); justify-content: stretch; }.game-hero-actions .btn, .game-hero-actions form { width: 100%; }.game-layout { grid-template-columns: 1fr; }.game-rail { grid-row: 2; grid-template-columns: repeat(2, minmax(0, 1fr)); }.game-main { grid-row: 1; }
    .messages-layout, .messages-layout:has(.messages-empty-state) { grid-template-columns:minmax(0,1fr); min-height:0; }.messages-sidebar { max-height:360px; }.messages-conversation-list { flex:0 1 auto; }.messages-chat { min-height:480px; }.messages-context { grid-template-columns:repeat(2,minmax(0,1fr)); }.messages-context-card:last-child { grid-column:1 / -1; }
}

@media (min-width: 701px) and (max-width: 1100px) {
    .messages-layout.has-active { grid-template-columns: 260px minmax(0, 1fr); }
    .messages-layout.has-active .messages-sidebar { grid-column: 1; grid-row: 1; max-height: 720px; }
    .messages-layout.has-active .messages-chat { grid-column: 2; grid-row: 1; min-height: 620px; }
    .messages-layout.has-active .messages-context { grid-column: 1 / -1; grid-row: 2; grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .messages-layout.has-active .messages-context-card:last-child { grid-column: auto; }
}

@media (max-width: 700px) {
    .guest-games-grid { grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; }
    .game-rail { grid-template-columns: minmax(0, 1fr); }
    .game-hero-actions { grid-template-columns: minmax(0, 1fr); }
    .event-side-rail { grid-template-columns: minmax(0, 1fr); }
    .event-clan-card:last-child { grid-column: auto; }
    .messages-layout.has-active .messages-chat { grid-row: 1; }
    .messages-layout.has-active .messages-sidebar { grid-row: 2; }
    .messages-layout.has-active .messages-context { grid-row: 3; }
    .messages-back-link { display: inline-flex; flex: 0 0 auto; }
}

@media (max-width: 560px) {
    .settings-profile-fields { grid-template-columns:minmax(0,1fr); gap:0; }
    .profile-meta-list-wide > div { grid-template-columns:100px minmax(0,1fr); }
    .interest-picker-tools { align-items:stretch; flex-direction:column; }
    .interest-picker-search { flex-basis:auto; }
    .interest-picker-toggle { width:100%; }
    .interest-choice { flex:1 1 calc(50% - 4px); justify-content:flex-start; }
    .onboarding-step-card { padding:16px; }
    .onboarding-requirement { margin-left:0; }
    .profile-game-grid { grid-template-columns:minmax(0,1fr); }
    /* Wrapping is what lets .topnav drop to its own full-width row at this
       width (see the 900px block). nowrap was written when a guest topbar held
       nothing but the brand and two buttons and so had nothing to wrap; once
       guests were given the Clans/Games/Events nav on 2026-09-16 it pushed that
       nav off the side of the screen instead. */
    .guest-topbar .topbar-inner { min-height:58px; gap:8px; }
    .guest-topbar .brand { flex:0 0 auto; }
    .guest-topbar .topbar-actions { gap:6px; }
    .guest-topbar .topbar-actions .btn { min-width:0; min-height:40px; padding:9px 12px; font-size:12px; }
    .guest-hero { min-height:500px; align-items:flex-end; }
    .guest-hero::before { background-position:62% center; background-image:linear-gradient(90deg,rgba(7,11,16,.96),rgba(7,11,16,.66)),linear-gradient(0deg,rgba(7,11,16,.98),transparent 52%),var(--guest-hero,linear-gradient(135deg,#111820,#07090b)); }
    .guest-hero .hero-content { width:100%; padding:54px 0 38px; }
    .guest-hero .hero-eyebrow { font-size:12px; }
    .guest-hero h1 { font-size:clamp(42px,16vw,58px); }
    .guest-hero .tagline { font-size:12px; line-height:1.45; }
    .guest-hero p.lead { max-width:34ch; font-size:14px; line-height:1.6; }
    .guest-hero .hero-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
    .guest-hero .hero-actions .btn { width:100%; min-width:0; padding-inline:10px; text-align:center; }
    .guest-content { padding-top:24px; padding-bottom:8px; }
    .guest-featured .section-label { margin-bottom:12px; }
    .guest-games-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:9px; }
    .guest-featured .game-card { min-width:0; font-size:12px; overflow-wrap:anywhere; }
    .game-poster-body { gap:6px; padding:14px 12px 12px; }
    .game-poster-players { top:9px; right:9px; padding:4px 6px; }
    .game-poster-platforms > span { padding:2px 5px; font-size:9px; }
    .guest-events { margin-top:30px; }
    .guest-section-head { gap:10px; }
    .guest-section-head .btn { width:100%; text-align:center; }
    /*
     * The card is already a stack, so the phone needs no reflow - only less
     * of everything, and an escape hatch for the one string on it nobody
     * controls the length of. A title is a member's own text and a phone is
     * 343px wide, so it wraps and breaks rather than overflowing the card;
     * the clamp above still stops it at two lines.
     */
    .guest-events-grid { gap:10px; }
    .guest-event-card { gap:11px; padding:13px 14px 12px; }
    .guest-event-head { gap:11px; }
    .guest-event-date { width:46px; height:48px; }
    .guest-event-date strong { font-size:19px; }
    .guest-event-title { font-size:14px; overflow-wrap:anywhere; }
    .guest-event-facts { gap:3px 10px; }
    .guest-events-empty { padding:16px; font-size:13px; }
    .guest-cta { grid-template-columns:54px minmax(0,1fr); align-items:center; margin:24px 0 32px; padding:18px; }
    .guest-cta-mark { width:54px; height:54px; border-radius:12px; }
    .guest-cta h2 { font-size:18px; }
    .guest-cta .btn { grid-column:1 / -1; width:100%; text-align:center; }
    main:has(.guest-hero) + .site-footer { padding-top:28px; }
    main:has(.guest-hero) + .site-footer .site-footer-grid { grid-template-columns:minmax(0,1fr); gap:24px; }
    main:has(.guest-hero) + .site-footer .site-footer-brand,
    main:has(.guest-hero) + .site-footer .site-footer-cta { grid-column:auto; }
    .home-grid { gap: 14px; padding-top: 12px; }
    .home-grid > * { min-width: 0; }
    .profile-page { padding-top:12px; padding-bottom:40px; }
    .profile-banner .banner-cover { height:150px; }
    .profile-banner .banner-body { align-items:stretch; gap:14px; padding:0 14px 16px; margin-top:-30px; }
    .profile-banner-identity { display:grid; grid-template-columns:64px minmax(0,1fr); width:100%; align-items:end; gap:12px; }
    .profile-banner-copy { flex:1; padding-top:34px; }
    .profile-banner-copy h1 { margin-top:0; font-size:20px; }
    .profile-banner-stats { grid-column:1 / -1; width:100%; margin:4px 0 0; gap:0; }
    .profile-banner-stats .stat { flex:1 1 0; min-width:0; padding:0 7px 0 0; }
    .profile-banner-stats .stat + .stat { padding-left:7px; border-left:1px solid var(--border-soft); }
    /* Three classes, to beat .stat-row .stat .stat-value. As written before -
       two classes inside a media query - neither of these ever applied, so the
       phone still rendered these at the desktop 22px. */
    .profile-banner-stats .stat .stat-value { font-size:14px; }
    .profile-banner-stats .stat .stat-label { font-size:9px; }
    .profile-banner-actions { width:100%; justify-content:flex-start; padding-bottom:0; }
    .profile-banner-actions > a,
    .profile-banner-actions > form,
    .profile-banner-actions > form .btn { flex:1 1 auto; }
    .profile-banner-actions > a { text-align:center; }
    .profile-tabs { margin-top:12px; }
    .profile-grid { gap:14px; padding-top:12px; }
    .profile-media-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
    .profile-friends-grid { grid-template-columns:minmax(0,1fr); }
    .profile-main .stat-grid { grid-template-columns:minmax(0,1fr); }
    .clan-page { padding-top: 12px; padding-bottom: 40px; }
    .clan-banner .banner-cover { height: 124px; }
    .clan-banner .banner-body { align-items: stretch; gap: 12px; padding: 0 12px 14px; margin-top: -30px; }
    .clan-banner .banner-identity { width: 100%; align-items: flex-start; gap: 12px; }
    .clan-banner-copy { flex: 1; padding-top: 34px; }
    .clan-banner-copy h1 { font-size: 20px; }
    .clan-banner-copy .handle { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
    /* Two metrics now, not five: Language, Region and Created moved out into
       chips of their own, so the three-column grid this was built for left an
       empty track. */
    .clan-banner-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: calc(100% + 76px); margin: 0 0 0 -76px; gap: 0; }
    .clan-banner-stats .stat { min-width: 0; padding: 7px 8px; border-left: 1px solid var(--border-soft); }
    .clan-banner-stats .stat:first-child { padding-left: 0; border-left: 0; }
    /* Three classes, for the reason given where the wrapping is set: the
       two-class form never won against the base rule. Free text a clan typed
       in does not belong at 22px in a third of a phone screen. */
    .clan-banner-stats .stat .stat-value { font-size: 14px; line-height: 1.3; }
    .clan-banner-stats .stat-label { font-size: 10px; }
    .clan-banner .banner-actions { width: 100%; justify-content: flex-start; padding-bottom: 0; }
    .clan-banner .banner-actions > form:first-child,
    .clan-banner .banner-actions > form:first-child .btn { flex: 1; }
    .clan-banner .banner-actions .btn { min-height: 40px; }
    .clan-invite-menu[open] { order: 20; flex: 1 1 100%; }
    .clan-invite-menu[open] > summary { width: 100%; justify-content: center; }
    .clan-invite-menu[open] .clan-invite-popover { position: static; width: 100%; margin-top: 8px; }
    .clan-tabs { margin-top: 12px; }
    .clan-tabs a { min-height: 42px; display: inline-flex; align-items: center; }
    .clan-meta-list-wide > div { grid-template-columns:100px minmax(0,1fr); }
    .manage-panel-summary { gap: 10px; padding: 12px 14px; }
    .manage-panel-heading small { display: none; }
    .manage-panel-body { padding: 2px 14px 14px; }
    .manage-section { padding: 16px 0; }
    /* A tile that is icon + copy + button in one row runs out of width first:
       the button drops under the copy rather than squeezing it. */
    .manage-media-tile { flex-wrap: wrap; }
    .manage-media-btn { width: 100%; }
    .manage-inline-form { align-items: stretch; flex-direction: column; }
    .manage-inline-form .btn { width: 100%; }
    /* A chip is a tap target here, not just a label with an x on it. */
    .manage-chip { min-height: 38px; padding: 6px 12px 6px 14px; }
    .manage-actions .btn { width: 100%; }
    .manage-narrow-form { max-width: none; }
    .clan-manage-row,
    .clan-member-row { align-items: flex-start; flex-direction: column; }
    .clan-row-actions,
    .clan-member-actions { width: 100%; flex-wrap: wrap; }
    .clan-row-actions > form { flex: 1 1 auto; }
    .clan-row-actions > form .btn { width: 100%; }
    .clan-member-actions .pill { margin-right: auto; }
    .clan-event-row { align-items: flex-start; flex-wrap: wrap; }
    .clan-event-row .widget-row-body { flex: 1 1 calc(100% - 58px); }
    .clan-event-row .widget-row-aside { margin-left: 58px; }
    .post-composer-main { align-items: flex-start; }
    .post-composer textarea { min-width: 0; }
    .post-composer-actions { align-items: stretch; }
    .post-composer-actions > .post-game-select { flex: 1 1 140px; margin-left: auto; }
    .post-game-select select { width: 100%; max-width: none; }
    .post-composer-submit { flex: 1 1 100%; width: 100%; margin-left: 0; }
    .post-composer-submit .post-game-picker { flex: 1 1 auto; min-width: 0; max-width: none; }
    .post-composer-submit .btn { flex: 0 0 auto; }
    .post-composer-extras { flex-wrap: wrap; }
    .post-actions { gap: 6px 8px; }
    .post-actions .btn-sm { padding-left: 6px; padding-right: 6px; }
    .post-author { align-items: flex-start; }
    .post-author-left > div { min-width: 0; }
    .post-game-tag { display: inline; margin-left: 3px; }
    .event-page { padding-top: 12px; padding-bottom: 40px; }.event-hero, .event-hero.has-banner { gap: 14px; padding: 16px; }.event-hero h1 { margin-top: 12px; font-size: 26px; line-height: 1.08; }
    .event-hero-top { gap: 8px 12px; }
    .event-hero-when { gap: 11px; margin-top: 14px; }.event-date-chip { flex: 0 0 50px; width: 50px; height: 50px; }.event-date-chip strong { font-size: 19px; }.event-hero-datetime { font-size: 14px; }.event-hero-tags > * { font-size: 11px; }
    /* A grid rather than the desktop flex row: on a 360px phone a row of stat
       tiles either overflows or squeezes each tile narrower than its own
       number. auto-fit puts the usual three across and wraps a fourth. */
    .event-hero-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; margin-top: 16px; }.event-hero-stats span { min-width: 0; padding: 8px 10px; }.event-hero-stats b { font-size: 19px; }
    .event-toolbar { margin-top: 10px; padding: 4px; }.event-toolbar-aside { padding-left: 6px; }.event-section-nav a { padding: 7px 10px; }
    .event-attendee-answers { margin-left: 40px; }.event-attendee-answers > div { grid-template-columns: 1fr; gap: 2px; }
    .event-rsvp-panel { padding: 14px; }.event-rsvp-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }.event-rsvp-actions .btn { width: 100%; }.event-reminder-form { align-items: stretch; flex-direction: column; }.event-reminder-form label { width: 100%; }.event-reminder-form select, .event-reminder-form .btn { width: 100%; }.event-content-grid { gap: 14px; margin-top: 14px; }.event-overview-card, .event-rail-card, .discussion, .event-manage-panel { padding: 14px; }.event-details-list > div { grid-template-columns: 88px minmax(0, 1fr); }.event-manage-form { align-items: stretch; display: grid; grid-template-columns: minmax(0, 1fr); }.event-manage-form > *, .event-manage-form .btn { width: 100% !important; min-width: 0 !important; }.event-question-options { align-items: stretch; flex-direction: column; }.event-file-form input[type="file"] { padding: 7px 0; }.event-series-row, .event-attendee-row { align-items: flex-start; flex-direction: column; gap: 8px; padding: 10px 8px; }.discussion-comment.is-reply { margin-left: 8px; padding-left: 8px; }.discussion-head { align-items: flex-start; }.discussion-composer .btn { width: 100%; justify-self: stretch; }.discussion-reply-form { align-items: stretch; flex-direction: column; }.discussion-reply-form .btn { width: 100%; }
    .messages-page { padding-top: 12px; padding-bottom: 40px; }.messages-layout { gap: 12px; }.messages-chat { min-height: 430px; padding: 14px; }.messages-sidebar { max-height: none; }.messages-conversation-list { max-height: 300px; }.messages-context { grid-template-columns: minmax(0, 1fr); gap: 10px; }.messages-context-card:last-child { grid-column: auto; }.message-thread { min-height: 290px; max-height: 58vh; padding: 14px 1px; }.message-row { max-width: 88%; }.message-row.has-preview { width:94%; max-width:94%; }.message-link-card { grid-template-columns:92px minmax(0,1fr); }.message-link-card > img { width:92px; min-height:82px; }.message-composer { display: grid; grid-template-columns: auto minmax(92px, 1fr) auto; align-items: stretch; }.message-composer-input { grid-column: 1 / -1; width: 100%; min-width: 0; }.message-composer-attachment, .message-composer-send { width: 100%; }.message-composer-file-hint { align-self:center; text-align:center; }.message-media-image, .message-media-video { max-width: 100%; height: auto; }.message-edit-form, .messages-member-form { align-items: stretch; flex-direction: column; }.message-edit-form .btn, .messages-member-form .btn { width: 100%; }.messages-empty-state { min-height: 320px; padding: 24px 14px; }
    .game-page { padding-top: 12px; padding-bottom: 40px; }.game-hero { grid-template-columns: 82px minmax(0, 1fr); align-items: start; gap: 12px; padding: 15px; }.game-cover { width: 82px; height: 108px; }.game-hero h1 { font-size: 27px; line-height: 1.05; }.game-hero-stats { font-size: 11px; }.game-hero p { font-size: 13px; margin: 8px 0; }.game-meta { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px 12px; }.game-meta span { min-width: 0; overflow-wrap: anywhere; }.game-tags { margin: 8px 0; gap: 5px; }.game-tags span { padding: 3px 7px; font-size: 11px; }.game-hero-actions { display: grid; grid-template-columns: 1fr; }.game-hero-actions .btn, .game-hero-actions form, .game-hero-actions button { width: 100%; }.game-layout { margin-top: 18px; gap: 18px; }.game-section-heading { align-items: flex-start; }.game-section-heading h2 { font-size: 18px; }.game-rail-card { padding: 14px; }.game-list-card { padding: 3px 14px; }.game-event-row { align-items: flex-start; }.game-event-row > span:last-child { min-width: 0; }.game-event-row b { white-space: normal; }.game-video-list > a { grid-template-columns: 94px minmax(0, 1fr); }.game-news-item b { overflow-wrap: anywhere; }
}

@media (max-width: 400px) {
    .game-hero { grid-template-columns: 66px minmax(0, 1fr); padding: 12px; }
    .game-cover { width: 66px; height: 88px; }
    .game-hero h1 { font-size: 23px; }
    .game-meta { grid-template-columns: minmax(0, 1fr); }
    .game-video-list > a { grid-template-columns: 82px minmax(0, 1fr); }
}
.repost-card{margin:12px 0;padding:12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-alt);min-width:0}
.repost-label{display:flex;align-items:center;gap:6px;margin-bottom:8px;color:var(--muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
.repost-author{display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:13px}.repost-author a{color:var(--text);font-weight:700;text-decoration:none}.repost-author span{color:var(--muted)}
.repost-copy{margin:8px 0;white-space:pre-wrap;overflow-wrap:anywhere}.repost-media{display:block;margin-top:8px}.repost-media img{display:block;width:100%;max-height:260px;object-fit:cover;border-radius:var(--radius-sm)}
.repost-open{display:inline-block;margin-top:9px;color:var(--accent);font-size:12px;font-weight:700}.repost-unavailable{color:var(--muted);font-size:13px}
.post-repost-control{position:relative}.post-repost-control>summary{list-style:none;cursor:pointer}.post-repost-control>summary::-webkit-details-marker{display:none}
.post-repost-form{position:absolute;z-index:20;left:0;bottom:calc(100% + 8px);width:min(320px,calc(100vw - 40px));padding:10px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg)}
.post-repost-form textarea{display:block;width:100%;resize:vertical;margin-bottom:8px;background:var(--surface-alt);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:8px;font:inherit}
@media(max-width:640px){.post-repost-form{position:fixed;left:16px;right:16px;bottom:16px;width:auto}.repost-media img{max-height:220px}}

/* Shared public empty/result state */
.empty-state{display:grid;justify-items:center;gap:8px;margin:18px 0;padding:30px 24px;text-align:center;border:1px dashed color-mix(in srgb,var(--accent) 34%,var(--border));border-radius:var(--radius);background:linear-gradient(145deg,color-mix(in srgb,var(--accent) 6%,var(--surface)),var(--surface));color:var(--text)}
.empty-state-icon{display:grid;place-items:center;width:48px;height:48px;margin-bottom:2px;border:1px solid color-mix(in srgb,var(--accent) 42%,var(--border));border-radius:50%;background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
.empty-state h2{margin:0;font-size:18px;line-height:1.25}.empty-state p{max-width:470px;margin:0 0 6px;color:var(--muted);font-size:13px;line-height:1.55}.empty-state .btn{margin-top:2px}
.empty-state-compact{padding:22px 18px}.empty-state-compact .empty-state-icon{width:40px;height:40px}.empty-state-compact h2{font-size:16px}
@media(max-width:640px){.empty-state{padding:24px 16px}.empty-state p{max-width:34ch}}

/* Shared HTTP error state */
.error-state{display:grid;grid-template-columns:72px minmax(0,1fr);gap:20px;width:min(620px,100%);padding:26px;border:1px solid var(--border);border-radius:var(--radius-lg);background:linear-gradient(145deg,var(--surface),var(--surface-alt));box-shadow:var(--shadow-card)}
.error-state-code{display:grid;place-items:center;width:72px;height:72px;border:1px solid currentColor;border-radius:50%;background:var(--danger-soft);color:var(--danger);font-size:18px;font-weight:800;letter-spacing:.04em}.error-state-warning .error-state-code{background:var(--warning-soft);color:var(--warning)}.error-state-muted .error-state-code{background:var(--surface-hover);color:var(--muted)}
.error-state-copy .type-eyebrow{margin:0 0 3px}.error-state-copy h1{margin:0 0 7px}.error-state-copy>p:not(.type-eyebrow){max-width:470px;margin:0 0 16px;color:var(--muted)}
.error-debug{width:min(620px,100%);margin-top:14px;padding:12px 14px;border:1px solid var(--border);border-radius:var(--radius);color:var(--muted);background:var(--surface)}.error-debug summary{cursor:pointer}.error-debug pre{overflow:auto;white-space:pre-wrap;font-size:12px}
@media(max-width:520px){.error-state{grid-template-columns:minmax(0,1fr);padding:22px}.error-state-code{width:54px;height:54px}.error-state-copy .btn{width:100%}}

/* Event waiting-list manager controls */
.event-waitlist-actions{grid-column:2/-1;display:flex;justify-content:flex-end;flex-wrap:wrap;gap:6px}.event-waitlist-actions form{margin:0}.event-waitlist-actions .btn{min-height:30px}
@media(max-width:560px){.event-waitlist-actions{grid-column:2;justify-content:flex-start}}

/* Event attendee role assignment */
.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.event-attendee-role-form{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-wrap:wrap}.event-attendee-role-form select{max-width:170px}
@media(max-width:560px){.event-attendee-role-form{width:100%;justify-content:flex-start}.event-attendee-role-form select{flex:1;max-width:none}}

/* Event organizer announcements */
.event-announcements{margin-top:14px}.event-announcement-composer{display:grid;gap:7px;margin-top:12px;padding:10px;border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--surface-alt)}.event-announcement-composer label{font-size:12px;font-weight:700}.event-announcement-composer textarea{width:100%;resize:vertical;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 10px;font:inherit}.event-announcement-composer>div{display:flex;align-items:center;justify-content:space-between;gap:10px}.event-announcement-composer>div>span{color:var(--muted);font-size:11px}.event-announcement-list{display:grid;margin-top:12px}.event-announcement{padding:12px 0;border-top:1px solid var(--border-soft);scroll-margin-top:76px}.event-announcement header{display:flex;align-items:center;gap:8px}.event-announcement header>div{display:grid}.event-announcement header a{color:var(--text);font-size:13px;font-weight:700;text-decoration:none}.event-announcement header span{color:var(--muted);font-size:11px}.event-announcement p{margin:9px 0 0;white-space:normal;overflow-wrap:anywhere;font-size:13px;line-height:1.55}
@media(max-width:560px){.event-announcement-composer>div{align-items:stretch;flex-direction:column}.event-announcement-composer .btn{width:100%}}

/* Organizer-only RSVP history */
.event-rsvp-history{margin:0 0 20px}.event-rsvp-history>summary{display:flex;align-items:center;justify-content:space-between;gap:10px;cursor:pointer;font-size:13px;font-weight:700}.event-rsvp-history>summary>span{color:var(--muted);font-size:11px;font-weight:500}.event-rsvp-history-list{display:grid;margin-top:10px}.event-rsvp-history-row{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start;gap:9px;padding:9px 0;border-top:1px solid var(--border-soft)}.event-rsvp-history-row>div{min-width:0}.event-rsvp-history-row a{color:var(--text);font-size:13px;font-weight:700;text-decoration:none}.event-rsvp-history-row p{margin:2px 0;color:var(--text);font-size:12px;overflow-wrap:anywhere}.event-rsvp-history-row small{color:var(--muted);font-size:11px}

.pagination{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:16px}
.pagination .is-disabled{opacity:.45;cursor:default;pointer-events:none}
.pagination-gap{padding:0 2px;color:var(--muted);font-size:12px}
.list-truncated{margin:10px 18px 0;color:var(--muted);font-size:12px}
.message-history-note{margin:0 0 10px;padding:8px 12px;border:1px solid var(--border-soft);border-radius:var(--radius-sm);color:var(--muted);font-size:12px}
.message-thread-older{margin:0 0 12px;text-align:center}

/*
 * Public-surface components (2026-09-16)
 *
 * Games, clans, events and the help centre answer an anonymous visitor now, so
 * three things needed a look they never had: a breadcrumb trail, the sign-in
 * offer that stands where a member sees an action, and the lede paragraph each
 * page uses to say what it is. They are styled from the existing tokens rather
 * than new ones - a visitor arriving from search should land on the same site a
 * member signs in to.
 */

/* Breadcrumbs. Visible trail and its BreadcrumbList markup come from one array. */
.breadcrumbs{margin:0 0 14px}
.breadcrumbs ol{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0;padding:0;list-style:none;color:var(--muted);font-size:12px}
.breadcrumbs li{display:flex;align-items:center;gap:6px;min-width:0}
.breadcrumbs li+li::before{content:"›";color:var(--border)}
.breadcrumbs a{color:var(--muted);text-decoration:none}
.breadcrumbs a:hover{color:var(--text)}
.breadcrumbs [aria-current="page"]{color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:min(420px,60vw)}

/* The sign-in offer. Named login-cta, not guest-cta: .guest-cta is already the
   landing page's closing strip and the two are different components. */
.login-cta{margin:0}
.login-cta-inline{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0;color:var(--muted);font-size:13px}
.login-cta-inline a:not(.btn){color:var(--accent);text-decoration:none}
.login-cta-inline a:not(.btn):hover{text-decoration:underline}
.login-cta-card{display:grid;gap:10px;padding:14px 16px;margin:0 0 16px;border-color:var(--accent-dim);background:linear-gradient(100deg,var(--surface),rgba(53,130,24,.10))}
.login-cta-card p{margin:0;color:var(--text);font-size:14px;font-weight:600}
.login-cta-actions{display:flex;flex-wrap:wrap;gap:8px}

/*
 * The public profile preview's ending (2026-09-18).
 *
 * A stranger reads two posts and then the wall stops. Stopping at a hard edge
 * reads as "that is everything he has"; fading out reads as "there is more",
 * which is the true thing and the one that earns the sign-up the sheet asks
 * for when they reach the bottom.
 *
 * The blur is built the way a phone app builds it - progressively, so the top
 * of the veil is barely touched and the bottom is properly out of focus.
 * backdrop-filter cannot ramp on its own, so two layers each blur the same
 * backdrop by a different amount and a mask fades each one in over the one
 * above. Neither layer is load-bearing: where backdrop-filter is unsupported
 * both are invisible and the colour gradient underneath still carries the
 * fade to the page background.
 *
 * Only the veil is decorative. The posts stay in the document, selectable and
 * readable to anything that does not paint - a crawler reads what a member
 * reads, which is the point of previewing them at all.
 */
.preview-wall-heading{margin:0 0 12px}
.preview-wall{position:relative}
/* The fade ends where the last card does, not in the gap after it. */
.preview-wall.has-more > .post-card:last-of-type{margin-bottom:0}
/* Sized against the wall, not the viewport: the fade is meant to take the tail
   of the last post, and a fixed height swallowed a short one whole - two posts
   promised, one and a bit readable. A proportion keeps the author row and the
   first lines crisp whether the post is three words or thirty. */
.preview-veil{position:absolute;inset:auto 0 0;z-index:1;display:block;height:clamp(84px,30%,180px);border-radius:0 0 var(--radius) var(--radius);pointer-events:none;background:linear-gradient(to bottom,rgba(7,9,11,0) 0%,rgba(7,9,11,.35) 45%,rgba(7,9,11,.8) 72%,var(--bg) 100%)}
.preview-veil::before,
.preview-veil::after{content:"";position:absolute;inset:0;border-radius:inherit}
.preview-veil::before{backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);mask-image:linear-gradient(to bottom,transparent,#000 55%);-webkit-mask-image:linear-gradient(to bottom,transparent,#000 55%)}
.preview-veil::after{backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);mask-image:linear-gradient(to bottom,transparent 40%,#000 92%);-webkit-mask-image:linear-gradient(to bottom,transparent 40%,#000 92%)}
/* The marker app.js measures for "they reached the end". Block so its position
   is the end of the page rather than wherever an empty inline box lands. */
.preview-end{display:block}

/*
 * The same offer as a sheet, opened when a visitor reaches the end of the
 * preview. Built on <dialog> like the confirm, video, news and lightbox
 * sheets, so the backdrop, the focus trap and Escape are the browser's job
 * rather than this stylesheet's.
 *
 * The backdrop blurs as well as dims: what is behind it is the page the
 * visitor was reading, and blurring it is the same sentence as the fade above
 * - there is more here - rather than a grey sheet thrown over the window.
 */
.preview-gate-dialog{width:min(400px,calc(100vw - 28px));max-width:none;padding:0;border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);color:var(--text);box-shadow:0 24px 80px rgba(0,0,0,.74)}
.preview-gate-dialog::backdrop{background:rgba(3,5,7,.7);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.preview-gate-panel{position:relative;display:grid;justify-items:center;gap:12px;padding:30px 26px 26px;text-align:center}
.preview-gate-close{position:absolute;top:8px;right:8px;padding:2px 9px;font-size:21px;line-height:1.2}
.preview-gate-panel h2{margin:0;font-size:19px;letter-spacing:-.01em}
.preview-gate-panel p{margin:0;color:var(--muted);font-size:13px;line-height:1.6}
.preview-gate-panel strong{color:var(--text)}
.preview-gate-join{width:100%;margin-top:2px}
.preview-gate-signin{font-size:13px;font-weight:600}
/* The avatar is the profile's, at the size the banner uses; the phone rule for
   .avatar-xl shrinks it there too, which is the right size in a sheet. */
.preview-gate-panel .avatar-xl .avatar-img,
.preview-gate-panel .avatar-xl .avatar-fallback{border-width:0}
@media (prefers-reduced-motion:no-preference){
    .preview-gate-dialog[open]{animation:preview-gate-pop .18s ease-out}
}
@keyframes preview-gate-pop{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}

/* The one- or two-sentence summary each public page opens with. */
.page-lede{max-width:64ch;margin:-2px 0 16px;color:var(--muted);font-size:13px;line-height:1.6}
.page-lede a{color:var(--accent);text-decoration:none}
.page-lede a:hover{text-decoration:underline}

/* Said when a roster is showing fewer people than the group actually has. */
.clan-roster-note{margin:0 0 12px;padding:8px 12px;border:1px solid var(--border-soft);border-radius:var(--radius-sm);color:var(--muted);font-size:12px}

/* Help centre */
.help-topic-list{display:grid;gap:8px;margin:16px 0 26px}
.help-topic-link{display:grid;gap:3px;padding:12px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-alt);color:inherit;text-decoration:none}
.help-topic-link:hover{border-color:var(--accent-dim);background:var(--surface-hover)}
.help-topic-link b{font-size:14px}
.help-topic-link small{color:var(--muted);font-size:12px;line-height:1.5}
.help-article h2{margin:24px 0 8px;font-size:16px}
.help-article p,.help-article li{color:var(--muted);font-size:14px;line-height:1.65}
.help-article ul{margin:0 0 12px;padding-left:20px}
.help-article li{margin-bottom:6px}
.help-article a{color:var(--accent);text-decoration:none}
.help-article a:hover{text-decoration:underline}
.help-faq{margin-top:28px;padding-top:18px;border-top:1px solid var(--border-soft)}
.help-faq h2{margin:0 0 12px;font-size:16px}
.help-faq dl{margin:0}
.help-faq dt{margin-top:14px;font-size:14px;font-weight:700}
.help-faq dt:first-of-type{margin-top:0}
.help-faq dd{margin:5px 0 0;color:var(--muted);font-size:13px;line-height:1.65}

/* ---------------------------------------------------------------------------
   Long-form documents: About, Terms, Privacy, Community Guidelines.

   The help articles above are a screen or two each, so they needed nothing but
   readable prose. These four are not: the Terms run twenty numbered sections,
   the Guidelines fifteen, and both are read the way a reference is read -
   somebody arrives looking for one clause, usually from a link that named it.
   So they get a contents list, an id on every section, and a heading style that
   makes the section number scannable, none of which .help-article has.
   --------------------------------------------------------------------------- */
/* The page column. Wider than the help articles' 760px because these documents
   are mostly tables, two-column contents lists and numbered clauses rather than
   running prose - at 760px the enforcement ladder and the legal-basis matrix
   were scrolling sideways inside a card with empty space either side of it.
   Width lives here rather than inline on each of the four templates so the
   padding below can grow with it. */
.legal-page{max-width:1000px;padding-top:32px;padding-bottom:48px}
.legal-doc h1{margin:0 0 6px;font-size:var(--type-page);letter-spacing:-.02em}
.legal-meta{margin:0 0 18px;color:var(--muted-2);font-size:var(--type-sm)}
.legal-meta b{color:var(--text-soft);font-weight:600}
/* A step up from the 14px body copy: a longer line needs a larger glyph and
   more leading to stay readable, and these are documents people read rather
   than scan. */
.legal-doc>p,.legal-doc li,.legal-doc dd{color:var(--text-soft);font-size:15px;line-height:1.75}
.legal-doc h2{margin:30px 0 10px;padding-top:18px;border-top:1px solid var(--border-soft);font-size:17px;letter-spacing:-.01em;scroll-margin-top:76px}
.legal-doc h2:first-of-type{margin-top:22px}
.legal-doc h3{margin:20px 0 6px;font-size:14px;color:var(--text)}
.legal-doc h2+p,.legal-doc h3+p{margin-top:0}
.legal-doc ul,.legal-doc ol{margin:0 0 14px;padding-left:20px}
.legal-doc li{margin-bottom:7px}
.legal-doc li>ul{margin-top:7px}
.legal-doc a{color:var(--accent);text-decoration:none}
.legal-doc a:hover{text-decoration:underline}
.legal-doc strong,.legal-doc b{color:var(--text);font-weight:700}
.legal-doc hr{margin:26px 0;border:0;border-top:1px solid var(--border-soft)}

/* The contents list. Two columns where there is room, because twenty entries in
   one column pushes the first section below the fold on a laptop. */
.legal-toc{margin:0 0 8px;padding:14px 16px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-alt)}
.legal-toc h2{margin:0 0 10px;padding:0;border:0;font-size:var(--type-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.legal-toc p{margin:0 0 7px;font-size:13px;line-height:1.65}
.legal-toc ol{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px 20px;margin:0;padding-left:20px}
.legal-toc li{margin:0;color:var(--muted-2);font-size:13px;line-height:1.65}

/* The plain-language summary each document opens with - "In short" on the
   Privacy Policy, "The short version" on the Guidelines. */
.legal-summary{margin:0 0 4px;padding:14px 16px;border:1px solid var(--accent-dim);border-radius:var(--radius);background:var(--accent-soft)}
.legal-summary h2{margin:0 0 8px;padding:0;border:0;font-size:var(--type-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--text)}
.legal-summary p,.legal-summary li{color:var(--text-soft);font-size:13px;line-height:1.65}
.legal-summary>*:last-child{margin-bottom:0}

/* An aside inside a section: the enforcement level a rule normally carries, a
   warning that a rule also applies in private messages, a crisis-line note. */
.legal-callout{margin:0 0 14px;padding:11px 14px;border-left:3px solid var(--border);border-radius:0 var(--radius-sm) var(--radius-sm) 0;background:var(--surface-alt);color:var(--text-soft);font-size:13px;line-height:1.65}
.legal-callout>*:last-child{margin-bottom:0}
.legal-callout-warn{border-left-color:var(--warning);background:var(--warning-soft)}
.legal-callout-danger{border-left-color:var(--danger);background:var(--danger-soft)}
.legal-callout-info{border-left-color:var(--info);background:var(--info-soft)}

/* Allowed / not allowed, which both the Guidelines and the Terms lean on. The
   colour carries the meaning at a glance; the label carries it for everyone
   else, so neither is doing the job alone. */
.legal-verdict{margin:14px 0 6px;font-size:var(--type-sm);font-weight:700;text-transform:uppercase;letter-spacing:.06em}
.legal-verdict-yes{color:var(--accent)}
.legal-verdict-no{color:var(--danger)}

/* Tables: the enforcement ladder, the legal-basis matrix, retention periods,
   the processor list. Each is wider than a phone, so the wrapper scrolls rather
   than letting the page scroll sideways. */
.legal-table{margin:0 0 16px;overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius)}
.legal-table table{width:100%;min-width:440px;border-collapse:collapse;font-size:13px}
.legal-table th,.legal-table td{padding:9px 12px;text-align:left;vertical-align:top;border-bottom:1px solid var(--border-soft);line-height:1.6}
.legal-table thead th{background:var(--surface-alt);color:var(--text);font-size:var(--type-xs);text-transform:uppercase;letter-spacing:.06em}
.legal-table tbody td{color:var(--text-soft)}
.legal-table tbody tr:last-child th,.legal-table tbody tr:last-child td{border-bottom:0}

/* The About page's two operators. */
.legal-people{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;margin:0 0 16px}
.legal-person{padding:14px 16px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-alt)}
.legal-person h3{margin:0 0 2px;font-size:15px}
.legal-person small{display:block;margin-bottom:8px;color:var(--muted);font-size:var(--type-sm)}
.legal-person p{margin:0;color:var(--text-soft);font-size:13px;line-height:1.65}

/* Where to write to us, at the end of every one of these pages. */
.legal-contact{display:grid;gap:8px;margin:0 0 4px;padding:14px 16px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-alt)}
.legal-contact div{font-size:13px;line-height:1.6}
.legal-contact b{display:block;color:var(--text);font-size:var(--type-sm)}
.legal-contact span{color:var(--text-soft)}

/* Wide enough for the card to carry more inner breathing room than the shared
   20px, and for the contents list to take a third column instead of running
   sixteen entries down two. */
@media(min-width:900px){
    .legal-doc.card-pad{padding:32px 36px}
    .legal-doc h1{font-size:30px}
    .legal-toc ol{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media(max-width:700px){
    .legal-toc ol{grid-template-columns:minmax(0,1fr)}
    .legal-doc>p,.legal-doc li,.legal-doc dd{font-size:var(--type-body);line-height:1.7}
}

@media(max-width:560px){
    .login-cta-actions .btn{flex:1;text-align:center}
    .breadcrumbs [aria-current="page"]{max-width:100%}
}

/*
 * On-site video player (2026-09-16)
 *
 * Built on <dialog>, like the confirm dialog above, so the backdrop, the focus
 * trap and Escape-to-close come from the browser. The frame holds its shape
 * with aspect-ratio rather than any JavaScript measuring: the dialog is sized
 * by width alone and the 16:9 box follows.
 *
 * On a phone it becomes a bottom sheet. dvh, not vh - vh is the viewport with
 * the browser's own chrome ignored, so a 90vh sheet is taller than the screen
 * on mobile Safari and its controls end up under the address bar.
 */
.video-dialog { width: min(960px, calc(100vw - 28px)); max-width: none; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); color: var(--text); box-shadow: 0 24px 80px rgba(0, 0, 0, .74); }
.video-dialog::backdrop { background: rgba(0, 0, 0, .82); backdrop-filter: blur(4px); }
.video-dialog-panel { display: grid; gap: 0; }
.video-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 14px 13px 16px; border-bottom: 1px solid var(--border-soft); }
/* Shared with the stream player below: the two dialogs clamp their heading the same way. */
.video-dialog-head h2, .stream-dialog-head h2 { display: -webkit-box; overflow: hidden; margin: 0; font-size: 15px; line-height: 1.35; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.video-dialog-close { flex: 0 0 auto; padding: 4px 10px; font-size: 22px; line-height: 1; }
.video-dialog-frame { position: relative; aspect-ratio: 16 / 9; width: 100%; background: #000; }
/* A Short is portrait. The dialog narrows rather than the frame growing tall:
   9/16 of a 960px dialog would be 1700px of video. */
.video-dialog.is-portrait { width: min(420px, calc(100vw - 28px)); }
.video-dialog.is-portrait .video-dialog-frame { aspect-ratio: 9 / 16; max-height: 72dvh; }
.video-dialog-frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.video-dialog-consent { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; height: 100%; padding: 18px; border: 0; color: var(--text); text-align: left; background: var(--surface-alt); cursor: pointer; font: inherit; }
.video-dialog-consent:hover { background: var(--surface-hover); }
.video-dialog-consent span { display: grid; gap: 3px; }
.video-dialog-consent small { color: var(--muted); font-size: 11px; }
.video-dialog-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; }
.video-dialog-foot small, .stream-dialog-foot small { min-width: 0; overflow: hidden; color: var(--muted); text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }

@media (max-width: 700px) {
    /* Both forms, because .video-dialog.is-portrait is two classes and this is
       one: a portrait video kept its 420px desktop width while taking the bottom
       sheet's square lower corners and its missing side borders, so it drew as a
       narrow panel with two cut-off edges floating above the bottom of the
       screen. On a phone a sheet is a sheet whichever way the video is turned. */
    .video-dialog,
    .video-dialog.is-portrait { width: 100%; max-height: 92dvh; margin: auto auto 0; border-width: 1px 0 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
    .video-dialog-head { padding: 12px 12px 12px 14px; }
    .video-dialog-head h2 { font-size: 14px; }
    .video-dialog-foot { flex-direction: column; align-items: stretch; gap: 8px; }
    .video-dialog-foot .btn { text-align: center; }
}

/*
 * News reader sheet (2026-09-16)
 *
 * The video player's twin: same <dialog>, same bottom-sheet behaviour on a
 * phone. It scrolls rather than stretching, because a 600-character summary
 * with an image is taller than a short viewport - and a dialog that cannot
 * scroll simply hides its own footer, which is where the way out lives.
 */
.article-dialog { width: min(640px, calc(100vw - 28px)); max-width: none; max-height: 88dvh; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); color: var(--text); box-shadow: 0 24px 80px rgba(0, 0, 0, .74); }
.article-dialog::backdrop { background: rgba(0, 0, 0, .78); backdrop-filter: blur(4px); }
.article-dialog-panel { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; max-height: inherit; }
.article-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 12px 12px 18px; border-bottom: 1px solid var(--border-soft); }
.article-dialog-source { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--muted); font-size: 12px; }
.article-dialog-source span { overflow: hidden; color: var(--accent); text-overflow: ellipsis; white-space: nowrap; font-weight: 700; }
.article-dialog-source time::before { content: "·"; margin-right: 8px; color: var(--border); }
.article-dialog-close { flex: 0 0 auto; padding: 4px 10px; font-size: 22px; line-height: 1; }
.article-dialog-body { overflow-y: auto; padding: 18px; }
.article-dialog-media { margin: -18px -18px 16px; background: var(--surface-alt); }
.article-dialog-media img { display: block; aspect-ratio: 16 / 9; width: 100%; object-fit: cover; }
.article-dialog-body h2 { margin: 0 0 10px; font-size: 19px; line-height: 1.32; }
.article-dialog-excerpt { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.65; }
.article-dialog-note { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--border-soft); color: var(--muted-2); font-size: 11px; }
.article-dialog-foot { padding: 14px 18px; border-top: 1px solid var(--border-soft); }
.article-dialog-foot .btn { width: 100%; text-align: center; }

@media (max-width: 700px) {
    .article-dialog { width: 100%; max-height: 92dvh; margin: auto auto 0; border-width: 1px 0 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
    .article-dialog-body { padding: 16px; }
    .article-dialog-media { margin: -16px -16px 14px; }
    .article-dialog-body h2 { font-size: 17px; }
}

/*
 * Banner metrics and banner facts (2026-09-16)
 *
 * A clan banner carries two kinds of information and used to give them one
 * voice: Members and Games are counts a visitor scans for, while Language,
 * Region and Created are attributes somebody typed in. At the same 22px bold
 * the attributes shouted over the counts, and a language list drew over the
 * column beside it.
 *
 * Counts keep the big number. Attributes become a quiet chip - a small
 * uppercase label and its value on one line - which is both easier to read and
 * honest about which of the two matters at a glance.
 */
.banner-facts-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 22px; margin-top: 14px; }
.banner-facts-row .clan-banner-stats { margin-top: 0; flex: 0 0 auto; gap: 10px 20px; }
.banner-facts { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
.banner-fact { display: flex; align-items: baseline; gap: 7px; min-width: 0; padding: 5px 11px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); }
.banner-fact dt { flex: 0 0 auto; color: var(--muted-2); font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.banner-fact dd { min-width: 0; margin: 0; overflow-wrap: break-word; color: var(--text); font-size: 13px; font-weight: 600; }

@media (max-width: 560px) {
    /*
     * On a phone the metrics are pulled left into the avatar's gutter so two
     * columns get the banner's full width. That pull was written on the stats
     * alone, back when they were the only thing on this row; the chips stayed
     * behind and sat indented under the clan name while the counts above them
     * started at the banner edge. It belongs to the row, so both groups share
     * one left edge - and the stats, no longer breaking out on their own, are
     * put back to the row's width at two classes, which is what the rule they
     * are overriding is written at.
     */
    .banner-facts-row { align-items: stretch; gap: 12px; width: calc(100% + 76px); margin-left: -76px; }
    /* gap belongs on this line for the same reason width and margin-left do.
       The phone rule turns the stats into a two-column grid whose columns are
       separated by a border, not by space, so it asks for gap:0 - but it asks
       at one class, and .banner-facts-row .clan-banner-stats sets gap:10px 20px
       at two, which won everywhere and left a gap the border then sat inside
       of. Restating it here is what makes the phone rule's gap:0 real. */
    .banner-facts-row .clan-banner-stats { width: 100%; margin-left: 0; gap: 0; }
    .banner-facts { gap: 5px; }
    .banner-fact { padding: 5px 10px; }
    .banner-fact dd { font-size: 12px; }
}

/*
 * Media lightbox (2026-09-16)
 *
 * Two panes on a desktop: the image takes the room it can and the thread sits
 * beside it at a fixed reading width, the way a photo and its comments sit on
 * any social site. On a phone there is no room for two columns, so it becomes
 * a sheet - image on top at a bounded height, thread scrolling underneath -
 * rather than an image squeezed next to a 40-character column.
 *
 * dvh, not vh: on mobile browsers vh is the viewport with the address bar
 * hidden, so a dialog sized in it is taller than the screen until the reader
 * scrolls.
 */
.media-dialog { width: min(1180px, calc(100vw - 28px)); max-width: none; max-height: 90dvh; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); color: var(--text); box-shadow: 0 24px 80px rgba(0, 0, 0, .74); }
.media-dialog::backdrop { background: rgba(0, 0, 0, .82); backdrop-filter: blur(4px); }
.media-dialog-panel { display: grid; grid-template-columns: minmax(0, 1fr) 380px; max-height: inherit; min-height: 0; }

.media-dialog-stage { display: flex; align-items: center; justify-content: center; min-width: 0; min-height: 0; padding: 10px; background: #060708; }
.media-dialog-stage img { display: block; max-width: 100%; max-height: calc(90dvh - 20px); object-fit: contain; }

.media-dialog-side { display: flex; flex-direction: column; min-width: 0; min-height: 0; border-left: 1px solid var(--border-soft); }
.media-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--border-soft); }
.media-dialog-permalink { color: var(--accent); font-size: 12px; font-weight: 700; text-decoration: none; }
.media-dialog-permalink:hover { text-decoration: underline; }
.media-dialog-close { flex: 0 0 auto; padding: 4px 10px; font-size: 22px; line-height: 1; }

.media-dialog-thread { overflow-y: auto; min-height: 0; padding: 14px 16px; }
/* The thread arrives as the post page's own section, which carries its own
   top border and spacing for sitting under a post card. In here it is the
   whole pane. */
.media-dialog-thread .post-comments { margin-top: 0; padding-top: 0; border-top: 0; }
/* The composer wraps its own row here. Beside a photo the pane is 380px, and
   sharing that line with the attach and send buttons left the box about 190px
   wide - a third of the pane, to write a comment in. */
.media-dialog-thread .comment-composer textarea { flex: 1 1 100%; }

.media-dialog-note { margin: 0; color: var(--muted); font-size: 13px; }
.media-dialog-note.is-error { margin-top: 8px; color: var(--danger, #ff6b6b); }

@media (max-width: 900px) {
    .media-dialog { width: 100%; max-height: 94dvh; margin: auto auto 0; border-width: 1px 0 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
    .media-dialog-panel { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
    .media-dialog-stage { padding: 0; }
    .media-dialog-stage img { max-height: 44dvh; }
    .media-dialog-side { border-top: 1px solid var(--border-soft); border-left: 0; }
    .media-dialog-thread { padding: 12px 14px; }
}

/*
 * Structural links never underline (2026-09-16)
 *
 * Making the global hover rule lose to a class fixed every component that had
 * already said "text-decoration: none". It could not help the ones that never
 * said it - they declare a colour and a layout and then inherit the underline
 * from "a" on hover, which is how the official-news headlines in the home
 * widget came to draw a line under each title.
 *
 * These are links laid out as a block, a grid or a flex container: a card, a
 * chip, a media wrapper, a row. They are not a run of text in a sentence, and
 * an underline on one either draws across a whole card or sits under a picture.
 * The two other links in that same widget, .widget-game-title and
 * .widget-game-video, had been given the rule by hand; this states the
 * principle once instead of waiting for each one to be reported.
 *
 * Deliberately not here: .badge, .repost-open, .widget-link and
 * .external-embed-fallback. Those read as text, and an underline on hover is
 * the ordinary affordance for a link you can read.
 */
.comment-media,
.feed-suggestion,
.gallery-card,
.game-card,
.home-identity,
.footer-social-link,
.message-media-link,
.pill,
.post-external-gif,
.post-media,
.repost-media,
.search-compact,
.social-btn-provider,
.widget-game-cover,
.widget-game-item,
.widget-row { text-decoration: none; }

/*
 * Home identity card (2026-09-16)
 *
 * The card at the top of the left rail: who you are, and three counts.
 *
 * It used to carry two online indicators - the dot on the avatar, and a second
 * dot with the word "Online" under the handle - which told the one person who
 * cannot benefit from it something they already knew, twice. The avatar dot is
 * the whole status now.
 *
 * The counts use .stat-row, which is built for a profile banner: 22px numbers
 * meant to be read across a page. In a 240px rail they were the loudest thing
 * on the screen, over the name they belong to. They read as a summary here, at
 * three classes deep because that is what the base rule is written at - the
 * two-class form silently loses, which this project has learned four times.
 */
.home-identity-card { margin-bottom: 16px; }
.home-identity-card.has-banner { padding-top: 0; }
.home-identity-banner { height: 96px; margin: 0 -20px; border-radius: var(--radius) var(--radius) 0 0; background-position: center; background-size: cover; }

.home-identity { display: flex; align-items: center; gap: 10px; min-width: 0; margin-bottom: 14px; color: inherit; }
.home-identity-card.has-banner .home-identity { margin-top: -28px; }
.home-identity-card.has-banner .avatar-wrap { border: 3px solid var(--surface); border-radius: 50%; }
.home-identity-copy { display: grid; gap: 1px; min-width: 0; }
.home-identity-copy b { overflow: hidden; font-size: 14px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.home-identity-copy small { overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }

.home-identity-stats { gap: 0; }
.home-identity-stats .stat { padding: 0 12px; }
.home-identity-stats .stat:first-child { padding-left: 0; }
.home-identity-stats .stat:last-child { padding-right: 0; }
.home-identity-stats .stat + .stat { border-left: 1px solid var(--border-soft); }
.home-identity-stats .stat .stat-value { font-size: 16px; line-height: 1.25; }
.home-identity-stats .stat .stat-label { font-size: 10px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }


/*
 * Footer community link (2026-09-16)
 *
 * Sits in the footer grid's last column, which puts it on the second row
 * beside the "Ready to play?" block - the two ways in, together.
 *
 * The mark keeps the footer's own muted colour rather than arriving as a patch
 * of brand blue in a row of grey links, and takes Discord's blurple on hover,
 * where it reads as a deliberate destination rather than decoration.
 */
.site-footer-social { display: grid; align-content: start; gap: 9px; grid-column: -2 / -1; }
.footer-social-link { display: inline-flex; align-items: center; gap: 8px; justify-self: start; color: var(--muted); font-size: 13px; line-height: 1.4; }
.footer-social-link .brand-icon { flex: 0 0 auto; }
.footer-social-link:hover { color: #5865f2; }

@media (max-width: 900px) {
    /* Two columns here, with the brand and CTA spanning both: let this find
       its own place rather than being held to a last column that no longer
       sits where it did. */
    .site-footer-social { grid-column: auto; }
}


@media (min-width: 1500px) {
    /* The rail is 480px here, so the poster takes the room: 148px at 9:16 is
       263px, and the headlines beside it still get 290px. */
    .widget-game { grid-template-columns: 148px minmax(0, 1fr); min-height: 263px; }
}


/*
 * Empty-feed suggestions (2026-09-16)
 *
 * A strip of clans worth joining, shown only where the feed has nothing. It
 * wraps rather than scrolls sideways: there are at most six, and a horizontal
 * scroller hides whatever does not fit behind a gesture people have to guess
 * at.
 */
.feed-suggestions { width: 100%; margin-top: 6px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
.feed-suggestions h3 { margin: 0 0 12px; color: var(--muted-2); font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.feed-suggestion-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 0; padding: 0; list-style: none; }
.feed-suggestion-list > li { min-width: 0; }

.feed-suggestion { display: flex; align-items: center; gap: 9px; min-width: 0; max-width: 240px; padding: 8px 14px 8px 8px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: inherit; text-align: left; }
.feed-suggestion:hover { border-color: var(--accent-dim); background: var(--surface-hover); }
.feed-suggestion .avatar-img, .feed-suggestion .avatar-fallback { width: 30px; height: 30px; flex: 0 0 30px; font-size: 12px; }
.feed-suggestion-copy { display: grid; gap: 1px; min-width: 0; }
.feed-suggestion-copy b { overflow: hidden; color: var(--text); font-size: 13px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.feed-suggestion-copy small { color: var(--muted); font-size: 11px; }

@media (max-width: 560px) {
    .placeholder-panel { padding: 32px 16px; }
    .placeholder-actions { width: 100%; }
    .placeholder-actions .btn { flex: 1 1 auto; justify-content: center; }
    .feed-suggestion { max-width: none; width: 100%; }
    .feed-suggestion-list > li { width: 100%; }
}

/*
 * Games directory (2026-09-17)
 *
 * The page was capped at 820px, which fits three or four posters and leaves
 * most of a desktop screen empty. It uses the same width as the landing grid
 * now, and lays the shared poster out on auto-fill so the column count follows
 * the viewport instead of being declared breakpoint by breakpoint.
 *
 * minmax(clamp(...), 1fr): the floor is the poster's own minimum width, which
 * grows a little on a wide screen so twelve tiny posters do not appear on an
 * ultrawide, and shrinks on a phone so two still fit side by side.
 */
.games-directory { max-width: 1680px; padding: 20px 20px 64px; }
.games-directory h1 { margin-bottom: 6px; }
.games-directory .page-lede { max-width: 70ch; margin-bottom: 20px; }

.games-directory-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.games-directory-filters input[type="search"] { flex: 1 1 220px; min-width: 0; }
.games-directory-filters input[type="search"],
.games-directory-filters select { padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--text); font: inherit; font-size: 14px; }

.games-directory-grid { grid-template-columns: repeat(auto-fill, minmax(clamp(150px, 12vw, 200px), 1fr)); gap: 14px; }

.games-directory-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 26px; }
.games-directory-gap { color: var(--muted-2); padding: 0 2px; }

@media (max-width: 560px) {
    .games-directory { padding: 12px 16px 48px; }
    .games-directory-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
    .games-directory-filters { gap: 6px; }
    .games-directory-filters select, .games-directory-filters .btn { flex: 1 1 auto; }
}

/*
 * Artwork galleries (2026-09-17)
 *
 * The directory card is a landscape crop of the cover rather than the 9:16
 * poster the games use: a gallery is a set of pictures, not one piece of box
 * art, and a portrait frame would crop most screenshots to a sliver.
 */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(clamp(220px, 20vw, 300px), 1fr)); gap: 16px; }

/* Hashtags (2026-09-17). A tag is a label first - inside a sentence it should
   read as part of the sentence, not as a button. */
.post-hashtag { color: var(--tag-tint, var(--accent)); text-decoration: none; }
.post-hashtag:hover { text-decoration: underline; }
/* The tint the server worked out for this slug. Set as a variable rather than
   a colour so .post-hashtag keeps one colour declaration, and so anything else
   wearing a tag (the chip below) can reuse the same eight attribute rules
   without restating the palette.

   The attribute is data-tag-tint, not data-tint: these selectors are scoped to
   nothing but the attribute name, so a generic one would hand a tag colour to
   any future element that happened to reuse it - and --tag-tint inherits, so
   it would reach that element's whole subtree too.

   Every reader of --tag-tint passes a fallback. An unknown value here - a 0, a
   9, an empty string - matches [data-tag-tint] but defines nothing, and an
   undefined var() with no fallback voids the whole declaration at
   computed-value time rather than leaving the previous value standing. That is
   the same failure that left --surface-2's ten backgrounds transparent. */
[data-tag-tint="1"] { --tag-tint: var(--tag-1); }
[data-tag-tint="2"] { --tag-tint: var(--tag-2); }
[data-tag-tint="3"] { --tag-tint: var(--tag-3); }
[data-tag-tint="4"] { --tag-tint: var(--tag-4); }
[data-tag-tint="5"] { --tag-tint: var(--tag-5); }
[data-tag-tint="6"] { --tag-tint: var(--tag-6); }
[data-tag-tint="7"] { --tag-tint: var(--tag-7); }
[data-tag-tint="8"] { --tag-tint: var(--tag-8); }
.tag-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: var(--muted); font-size: 12px; text-decoration: none; }
.tag-chip[data-tag-tint] { color: var(--tag-tint, var(--muted)); }
.tag-chip:hover { border-color: var(--tag-tint, var(--accent)); color: var(--tag-tint, var(--accent)); text-decoration: none; }
.tag-chip-lg { padding: 7px 14px; font-size: 14px; }
.tag-chip-count { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.tag-cloud { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.tag-list { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }
.admin-tag-block { display: flex; gap: 6px; align-items: center; margin: 0; }
.admin-tag-block input[type="text"] { min-width: 0; width: 160px; padding: 5px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 12px; }

.tag-page { max-width: 1100px; padding: 20px 20px 64px; }
.tag-head { margin-bottom: 22px; }
.tag-head h1 { margin: 0 0 4px; }
.tag-count { margin: 0; color: var(--muted); font-size: 14px; }
.tag-section { margin-bottom: 30px; }
.tag-comments { display: grid; gap: 2px; }
.tag-comment { display: block; padding: 10px 12px; border-radius: var(--radius-sm); color: inherit; text-decoration: none; }
.tag-comment:hover { background: var(--surface-hover); text-decoration: none; }
.tag-comment-text { display: block; font-size: 14px; line-height: 1.5; }
.tag-comment small { color: var(--muted); font-size: 12px; }

/* The guide directory (2026-09-19).
 *
 * A header block rather than an <h1> over a grey sentence: this page is a
 * landing surface for people who arrived from a search engine, and it has to
 * introduce itself. Narrower than the games directory's 1680px on purpose - a
 * text card wants about four columns, and at five the grid was reading as a
 * spreadsheet with one row in it. */
.guides-directory { max-width: 1320px; padding: 20px 20px 64px; }
.guides-hero { margin: 0 0 24px; padding-bottom: 20px; border-bottom: 1px solid var(--border-soft); }
.guides-hero h1{ margin: 0 0 8px; font-size: clamp(26px, 3.2vw, 34px); line-height: 1.12; letter-spacing: -.01em; }
.guides-hero .page-lede { max-width: 62ch; margin: 0; font-size: 14px; }
/* The count pulled out of the lede and shown as a fact: "4 guides" is the one
   thing a visitor decides on before scrolling, and buried mid-sentence in
   muted 13px text it was not answering anybody. */
.guides-hero-stats { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin: 14px 0 0; color: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.guides-hero-stats span { display: inline-flex; align-items: center; gap: 14px; }
.guides-hero-stats span + span::before { content: ""; width: 3px; height: 3px; border-radius: 50%; background: var(--border); }
.guides-hero-stats strong { color: var(--text); font-size: 13px; }
.guides-hero .login-cta-inline { margin-top: 16px; }
/* The header already owns the space under itself; the details' own top margin
   on top of it left a 46px hole between the rule and the button. */
.guides-directory .guide-create { margin: 0 0 22px; }

/* Guides (2026-09-17, card rebuilt 2026-09-19).
 *
 * Every card carries a 16:9 head panel now, drawn rather than empty when the
 * guide has no cover - see guide-card.php. Mixing framed and unframed cards in
 * one grid was the directory's worst defect: a row of short text cards next to
 * one twice their height, ragged at every boundary. .is-textual is still the
 * one with no picture; it no longer has to earn its height from a stripe. */
.guide-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 16px; }
.guide-grid.guide-grid-compact { grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); gap: 12px; }
.guide-card { display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--border-soft); border-radius: var(--radius); background: var(--surface); color: inherit; text-decoration: none; transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease; }
.guide-card:hover { border-color: var(--accent-dim); transform: translateY(-2px); box-shadow: var(--shadow-card); text-decoration: none; }
.guide-card-art { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--surface-alt); }
/* Absolutely positioned, not height:100%: a percentage height against a parent
   sized only by aspect-ratio does not resolve, so the image fell back to its
   intrinsic height and the one card in the grid with a cover came out twice as
   tall as its row. */
.guide-card-art img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.guide-card:hover .guide-card-art img { transform: scale(1.04); }
/* The stand-in panel. Tinted from the guide's game, so a directory of mostly
   coverless guides still has colour in it and every Arma guide shares one -
   the grid reads as grouped without being sorted. */
.guide-card-art-drawn { display: grid; place-items: center; color: var(--tag-tint, var(--accent)); background: repeating-linear-gradient(135deg, transparent 0 10px, rgba(255, 255, 255, .02) 10px 20px), linear-gradient(155deg, var(--surface-2), var(--surface-alt)); box-shadow: inset 0 2px 0 currentColor; }
/* The tint as a wash behind the mark. currentColor rather than the token
   directly, because a background needs the colour at 10% and there is no way
   to take an alpha off a var() without color-mix. */
.guide-card-art-drawn::before { content: ""; position: absolute; top: -34%; left: 50%; width: 72%; aspect-ratio: 1; transform: translateX(-50%); border-radius: 50%; background: currentColor; opacity: .1; filter: blur(34px); }
.guide-card-mark { position: relative; display: grid; place-items: center; width: 52px; height: 52px; border: 1px solid var(--border); border-radius: 50%; background: rgba(7, 9, 11, .42); color: var(--tag-tint, var(--accent)); }
.guide-card-mark svg { display: block; }
/* The step count moves onto the panel: it is a property of the guide, not the
   headline, and on the kicker line it was competing with the game's name. */
.guide-card-badge { position: absolute; left: 10px; bottom: 10px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 999px; background: rgba(7, 9, 11, .76); color: var(--text-soft); font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.guide-card-body { display: flex; flex: 1; flex-direction: column; gap: 7px; padding: 13px 14px 14px; }
.guide-card-kicker { color: var(--tag-tint, var(--accent)); font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
/* Both clamped to two lines, so a long title and a long summary cannot make
   one card in a row twice the height of the rest. */
.guide-card-title, .guide-card-summary { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.guide-card-title { -webkit-line-clamp: 2; color: var(--text); font-size: 15px; line-height: 1.35; }
.guide-card:hover .guide-card-title { color: var(--accent); }
.guide-card-summary { -webkit-line-clamp: 2; color: var(--muted); font-size: 13px; line-height: 1.5; }
/* margin-top:auto pins the byline to the bottom of whichever card is tallest
   in the row, so the footers line up across a row even when the summaries do
   not. */
.guide-card-meta { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin-top: auto; padding-top: 11px; border-top: 1px solid var(--border-soft); color: var(--muted); font-size: 12px; }
.guide-card-author { display: inline-flex; align-items: center; min-width: 0; gap: 6px; }
.guide-card-author > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guide-card-facts { display: inline-flex; flex-wrap: wrap; gap: 5px; }
.guide-card-fact { padding: 1px 7px; border: 1px solid var(--border-soft); border-radius: 999px; font-size: 11px; line-height: 1.6; }
@media (prefers-reduced-motion: reduce) {
    .guide-card, .guide-card-art img { transition: none; }
    .guide-card:hover { transform: none; }
    .guide-card:hover .guide-card-art img { transform: none; }
}
/* The same difficulty colours the guide page uses, so the badge means the same
   thing in the directory as it does once you are reading. */
.guide-card-fact.guide-fact-beginner { border-color: var(--accent); color: var(--accent); }
.guide-card-fact.guide-fact-intermediate { border-color: var(--warning); color: var(--warning); }
.guide-card-fact.guide-fact-advanced { border-color: var(--danger); color: var(--danger); }

.guide-page { max-width: 820px; padding: 20px 20px 64px; }
.guide-article { min-width: 0; }
.guide-head { margin-bottom: 26px; }
.guide-kicker { margin: 0 0 6px; color: var(--accent); font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.guide-head h1 { margin: 0 0 8px; line-height: 1.15; }
.guide-byline { margin: 0 0 14px; color: var(--muted); font-size: 14px; }
/* 70ch, because a guide is read rather than scanned and a line much longer
   than that loses the reader on the way back to the left margin. */
.guide-summary { max-width: 70ch; margin: 0; color: var(--text); font-size: 16px; line-height: 1.65; }
.guide-notice { margin: 16px 0 0; padding: 10px 14px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--muted); font-size: 13px; }

/* The step numbers are the list's own, so removing a step never leaves a gap
   in the numbering the reader sees. */
.guide-steps { display: grid; gap: 22px; margin: 0; padding: 0; list-style: none; counter-reset: guide-step; }
/*
 * A grid item defaults to min-width:auto, which refuses to shrink below its
 * content's minimum. A command block is white-space:pre, so its minimum is the
 * longest line - and a long apt-get line would widen the item, then the grid,
 * then the page, giving the whole document a horizontal scrollbar instead of
 * scrolling inside the block where it belongs.
 */
.guide-steps > li { min-width: 0; }
.guide-step { position: relative; counter-increment: guide-step; padding-left: 52px; }
.guide-step::before { content: counter(guide-step); position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; border: 1px solid var(--border); border-radius: 50%; background: var(--surface-alt); color: var(--accent); font-size: 14px; font-weight: 700; }
.guide-step-body { min-width: 0; padding-top: 4px; }
.guide-step-heading { margin: 0 0 6px; font-size: 18px; line-height: 1.3; }
/*
 * No pre-wrap any more. GuideMarkup turns a body into real block elements -
 * paragraphs, lists, <kbd> - so the whitespace that used to be load-bearing is
 * now structure, and preserving it on top would double every blank line.
 */
.guide-step-text { max-width: 70ch; min-width: 0; font-size: 15px; line-height: 1.7; }
.guide-step-text > :first-child { margin-top: 0; }
.guide-step-text > :last-child { margin-bottom: 0; }
.guide-step-text p { margin: 0 0 12px; }
.guide-step-text ul, .guide-step-text ol { margin: 0 0 12px; padding-left: 22px; }
.guide-step-text li { margin-bottom: 4px; }
/*
 * A fenced block. Commands are the reason guides exist here, so they get room
 * rather than being squeezed into the sentence around them: full width, its
 * own background, and a horizontal scroll instead of a wrap - a wrapped
 * command line is one somebody copies wrong.
 */
.guide-step-text pre {
    margin: 0 0 14px;
    padding: 12px 14px;
    max-width: 100%;
    overflow-x: auto;
    border: 1px solid var(--border-soft);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    -webkit-overflow-scrolling: touch;
}
.guide-step-text pre code {
    display: block;
    white-space: pre;
    color: var(--text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    line-height: 1.75;
    /* The kbd styling must not follow a key cap into a code block. */
    padding: 0;
    border: 0;
    background: none;
}
.guide-step-text strong { color: var(--text); font-weight: 700; }
.guide-step-text a { overflow-wrap: anywhere; }
/* A key cap, which is the single most useful thing a game guide can show and
   the one a plain-text field could never render. */
.guide-step-text kbd {
    display: inline-block;
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 3px;
    background: var(--surface-alt);
    color: var(--text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .88em;
    line-height: 1.5;
    white-space: nowrap;
}
.guide-step-image { display: block; margin-top: 12px; overflow: hidden; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); cursor: zoom-in; }
.guide-step-image img { display: block; width: 100%; height: auto; }
.guide-step-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
.guide-step-actions form { margin: 0; }
.guide-step-edit { width: 100%; }
.guide-step-form { display: grid; gap: 8px; margin-top: 8px; }
.guide-step-form input[type="text"], .guide-step-form textarea { width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 14px; }
.guide-step-form textarea { resize: vertical; }
.guide-step-form .btn { justify-self: start; }

.guide-create, .guide-manage { margin: 22px 0; }
.guide-create > summary, .guide-manage > summary { display: inline-flex; align-items: center; justify-content: center; list-style: none; cursor: pointer; }
.guide-create > summary::-webkit-details-marker, .guide-manage > summary::-webkit-details-marker { display: none; }
.guide-create-form, .guide-manage .guide-step-form { display: grid; gap: 12px; max-width: 640px; margin-top: 14px; padding: 16px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.guide-create-mine { margin: 10px 0 0; font-size: 13px; }

/* --- What it takes: difficulty, time, requirements ---------------------- */

.guide-facts { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 0; padding: 0; list-style: none; }
.guide-fact { display: flex; flex-direction: column; gap: 2px; padding: 8px 14px; border: 1px solid var(--border-soft); border-left: 3px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); }
.guide-fact-label { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.guide-fact-value { color: var(--text); font-size: 14px; font-weight: 600; }
/* Difficulty reads as a colour and as a word, never colour alone. */
.guide-fact-beginner { border-left-color: var(--accent); }
.guide-fact-intermediate { border-left-color: var(--warning); }
.guide-fact-advanced { border-left-color: var(--danger); }

.guide-requirements { margin: 18px 0 0; padding: 14px 16px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.guide-requirements h2 { margin: 0 0 8px; font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.guide-requirements ul { margin: 0; padding-left: 20px; color: var(--text); font-size: 14px; line-height: 1.6; }

/* --- Contents ----------------------------------------------------------- */

.guide-contents { margin: 0 0 26px; padding: 14px 16px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.guide-contents h2 { margin: 0 0 10px; font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.guide-contents ol { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
.guide-contents a { display: flex; gap: 10px; padding: 5px 6px; border-radius: var(--radius-sm); color: var(--text); font-size: 14px; line-height: 1.45; text-decoration: none; }
.guide-contents a:hover { background: var(--surface); color: var(--accent); text-decoration: none; }
.guide-contents-n { flex: 0 0 auto; min-width: 18px; color: var(--muted); font-variant-numeric: tabular-nums; font-weight: 700; }
/* The step the reader jumped to has to clear the sticky header. */
.guide-step, .guide-callout { scroll-margin-top: 88px; }

/* --- Tips and warnings -------------------------------------------------- */

/*
 * A callout is an <li> of the same ordered list, so it keeps its place in the
 * flow, but it is not a .guide-step - which is where the counter lives, so it
 * silently takes no number. Six steps and two callouts, not eight steps.
 */
.guide-callout { padding-left: 52px; }
.guide-callout-box { padding: 12px 14px; border: 1px solid var(--border-soft); border-left: 3px solid var(--accent); border-radius: var(--radius-sm); background: var(--surface-alt); }
.guide-callout-tip .guide-callout-box { border-left-color: var(--accent); }
.guide-callout-warning .guide-callout-box { border-left-color: var(--warning); background: var(--warning-soft); }
.guide-callout-label { margin: 0 0 4px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.guide-callout-warning .guide-callout-label { color: var(--warning); }
.guide-callout .guide-step-heading { font-size: 15px; }
.guide-callout .guide-step-text { font-size: 14px; }

/* --- Authoring ---------------------------------------------------------- */

/* Two fields side by side where they fit and stacked where they do not, with
   no breakpoint: the fields decide, not the viewport. */
.guide-field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px; }
.guide-field-row .field { margin: 0; min-width: 0; }

.guide-settings > summary { margin-bottom: 4px; }
.guide-settings .guide-create-form { max-width: none; }
.guide-step-kind select { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 14px; }

.guide-format-hint { margin: 2px 0 0; color: var(--muted); font-size: 12px; }
.guide-format-hint > summary { cursor: pointer; color: var(--muted); }
.guide-format-hint ul { margin: 8px 0 6px; padding-left: 18px; line-height: 1.7; }
.guide-format-hint code { padding: 1px 5px; border-radius: 3px; background: var(--surface); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11.5px; }
.guide-format-hint p { margin: 8px 0 0; }
.guide-format-hint pre { margin: 6px 0 0; padding: 8px 10px; overflow-x: auto; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface); }
.guide-format-hint pre code { padding: 0; background: none; white-space: pre; line-height: 1.6; }

.guide-danger { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-top: 16px; padding: 12px 14px; border: 1px solid var(--danger); border-radius: var(--radius-sm); background: var(--danger-soft); }
.guide-danger-title { margin: 0; color: var(--danger); font-size: 14px; font-weight: 700; }
.guide-danger .hint { margin: 2px 0 0; }
.guide-danger .btn { border-color: var(--danger); color: var(--danger); }

/*
 * Drag-to-reorder states. The handle is only ever added by script, and the
 * up/down buttons stay regardless - a pointer is not the only way to reorder a
 * list, and this is decoration on top of controls that already work.
 */
.guide-step-drag { cursor: grab; touch-action: none; }
.guide-step-drag:active { cursor: grabbing; }
[data-step-sortable].is-dragging { opacity: .5; }
[data-step-sortable].is-drop-target { outline: 2px dashed var(--accent); outline-offset: 4px; }
[data-guide-steps].is-busy { opacity: .6; pointer-events: none; }

/* --- Related ------------------------------------------------------------ */

.guide-related { margin: 32px 0 8px; padding-top: 22px; border-top: 1px solid var(--border-soft); }
.guide-related h2 { margin: 0 0 14px; font-size: 16px; }
.guide-related-all { margin: 14px 0 0; font-size: 13px; }

.guide-social { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 28px 0 18px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
.guide-social .btn.is-active { border-color: var(--accent); color: var(--accent); }
.guide-social [aria-disabled="true"] { cursor: default; color: var(--muted); }
.guide-page .discussion { margin-top: 0; }

.guide-step-panel { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.guide-step-panel-kicker { margin: 0; color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.guide-step-panel h2 { margin: 0; font-size: 16px; }
/* No pre-wrap: the panel renders a body through guide_markup now, so its
   whitespace is real block structure rather than something to preserve. */
.guide-step-panel-text { color: var(--muted); font-size: 14px; line-height: 1.6; }
.guide-step-panel-text > :first-child { margin-top: 0; }
.guide-step-panel-text > :last-child { margin-bottom: 0; }
.guide-step-panel-text p { margin: 0 0 8px; }
.guide-step-panel-text ul, .guide-step-panel-text ol { margin: 0 0 8px; padding-left: 20px; }
.guide-step-panel-text kbd { display: inline-block; padding: 0 5px; border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 3px; background: var(--surface); color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .88em; white-space: nowrap; }
.guest-guides { margin-top: 28px; }

.admin-guide-card .admin-guide-meta { margin: 4px 0 0; color: var(--muted); font-size: 12px; }
.admin-guide-summary { margin: 10px 0 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
.admin-guide-steps { display: grid; gap: 12px; margin: 14px 0 0; padding-left: 20px; }
.admin-guide-steps li { color: var(--text); font-size: 13px; line-height: 1.55; }
.admin-guide-steps p { margin: 4px 0 0; white-space: pre-wrap; }
.admin-guide-steps img { max-width: 180px; margin-top: 8px; border-radius: var(--radius-sm); }

/* Artwork shown inside another page - a game hub, the landing page - rather
   than in the directory (2026-09-17). Smaller cards, because here the row is a
   taste of what is on /artwork and not the thing the page is for. The rule has
   two classes so it beats the single-class .gallery-grid above it regardless
   of source order, and the mobile override further down keeps working because
   that one is two classes too. */
.gallery-grid.gallery-grid-compact { grid-template-columns: repeat(auto-fill, minmax(clamp(160px, 15vw, 220px), 1fr)); gap: 12px; }
.guest-artwork { margin-top: 28px; }

/*
 * The landing page's artwork row carries eight galleries (2026-09-20), and a
 * fixed column count is what makes eight a shape rather than a leftover.
 *
 * The compact grid above is auto-fill, which inside this 1400px container
 * lands on six or seven tracks depending on the viewport - so the eighth card
 * sat alone on a second row, which reads as something that failed to load
 * rather than as a full grid. Four columns makes eight two complete rows, and
 * the step below is two rather than three for the same reason: three would
 * leave a 3/3/2 tail. Four columns is also a much wider card than the compact
 * row ever drew, which is the point - a screenshot is the argument this
 * section makes, and it was being made at thumbnail size.
 */
.gallery-grid.guest-artwork-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
/*
 * The step down lives here rather than in the shared 900px block far above,
 * and that is not a style preference: both selectors are two classes, so the
 * one further down the file wins, and an override written up there lost to
 * this rule and left four 170px cards on a tablet. Any later change to the
 * artwork row's breakpoints belongs in this block for the same reason.
 */
@media (max-width: 900px) {
    .gallery-grid.guest-artwork-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
}

/* A moderator note that changes what approving means (2026-09-17). */
.admin-artwork-warning { margin: 10px 0 0; padding: 9px 12px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--muted); font-size: 13px; line-height: 1.5; }
.admin-artwork-warning code { font-size: 12px; }
.gallery-card { isolation: isolate; position: relative; display: flex; flex-direction: column; justify-content: flex-end; min-width: 0; aspect-ratio: 4 / 3; overflow: hidden; border: 1px solid var(--border-soft); border-radius: 12px; background: linear-gradient(145deg, var(--surface-hover), var(--surface)); color: var(--text); transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
.gallery-card:hover { transform: translateY(-4px); border-color: var(--accent-dim); box-shadow: 0 16px 32px rgba(0, 0, 0, .3); }
.gallery-card::after { content: ""; position: absolute; z-index: 1; inset: 0; background: linear-gradient(180deg, rgba(6, 8, 10, .05) 38%, rgba(6, 8, 10, .58) 66%, rgba(6, 8, 10, .96) 100%); pointer-events: none; }
.gallery-card-art { position: absolute; inset: 0; }
.gallery-card-art img { width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.gallery-card:hover .gallery-card-art img { transform: scale(1.035); }
.gallery-card-count { position: absolute; z-index: 2; top: 10px; right: 10px; min-width: 26px; padding: 4px 8px; border: 1px solid rgba(255, 255, 255, .2); border-radius: 999px; background: rgba(5, 7, 9, .72); color: #fff; font-size: 11px; font-weight: 700; text-align: center; backdrop-filter: blur(6px); }
.gallery-card-body { position: relative; z-index: 2; display: grid; gap: 3px; min-width: 0; padding: 16px 14px 13px; }
.gallery-card strong { overflow: hidden; color: #fff; font-size: 15px; line-height: 1.25; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 1px 4px #000; }
.gallery-card-meta { overflow: hidden; color: #cfd6db; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 1px 3px #000; }

.gallery-page { max-width: 1100px; padding: 20px 20px 64px; }
.gallery-head { margin-bottom: 24px; }
.gallery-head h1 { margin-bottom: 6px; }
.gallery-byline { margin: 0 0 12px; color: var(--muted); font-size: 14px; }
.gallery-description { max-width: 70ch; margin: 0; color: var(--muted); line-height: 1.6; }
.gallery-notice { margin: 14px 0 0; padding: 10px 14px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--muted); font-size: 13px; }

/* One column on a phone, two above it. A screenshot in a third of a phone
   screen is not a screenshot any more. */
.gallery-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); gap: 18px; }
.gallery-image { margin: 0; overflow: hidden; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.gallery-image img { display: block; width: 100%; height: auto; }
.gallery-image figcaption { padding: 10px 14px; color: var(--muted); font-size: 13px; line-height: 1.5; }

/* Like and comment on a gallery (2026-09-17). The thread itself is the shared
   .discussion component, so only the bar above it is styled here. */
/* One image opened in the lightbox, with its own likes and comments
   (2026-09-17). The gallery page's thumbnails become links; the panel beside
   the image reuses the shared .discussion component, so only its own header
   and action row need styling here. */
.gallery-image-open { display: block; cursor: zoom-in; }
.gallery-image-open img { display: block; width: 100%; height: auto; }
.gallery-image-counts { display: flex; gap: 12px; margin: 0; padding: 8px 14px 0; color: var(--muted); font-size: 12px; }
.gallery-item-panel { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.gallery-item-head h2 { margin: 0; font-size: 16px; }
.gallery-item-caption { margin: 6px 0 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
.gallery-item-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.gallery-item-actions .btn.is-active { border-color: var(--accent); color: var(--accent); }
.gallery-item-panel .discussion { margin: 0; padding: 0; border: 0; background: none; }
.gallery-item-panel .discussion-head { margin-bottom: 10px; }

.gallery-social { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 22px 0 18px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
.gallery-social .btn.is-active { border-color: var(--accent); color: var(--accent); }
.gallery-social [aria-disabled="true"] { cursor: default; color: var(--muted); }
.gallery-page .discussion { margin-top: 0; }

@media (prefers-reduced-motion: reduce) {
    .gallery-card, .gallery-card-art img { transition: none; }
    .gallery-card:hover { transform: none; }
    .gallery-card:hover .gallery-card-art img { transform: none; }
}

@media (max-width: 560px) {
    .guide-page { padding: 12px 16px 48px; }
    /* The number moves above the step rather than beside it: 52px of gutter on
       a 375px screen is a third of the column. Callouts lose the same gutter,
       which they only ever had to line up with the steps. */
    .guide-step, .guide-callout { padding-left: 0; }
    .guide-step::before { position: static; margin-bottom: 8px; width: 28px; height: 28px; font-size: 13px; }
    .guide-facts { gap: 6px; }
    .guide-fact { flex: 1 1 auto; padding: 7px 12px; }
    .guide-danger { flex-direction: column; align-items: flex-start; }
    .guides-directory { padding: 12px 16px 48px; }
    .guides-hero { margin-bottom: 18px; padding-bottom: 16px; }
    .guides-hero-stats { gap: 6px 10px; }
    .guides-hero-stats span { gap: 10px; }
    /* One per row rather than two 160px cards: a 16:9 panel above two lines of
       summary has nowhere to go at half of a 375px screen. */
    .guide-grid, .guide-grid.guide-grid-compact { grid-template-columns: minmax(0, 1fr); gap: 12px; }
    /* The drawn panel becomes a band. At one card per row it is 343px wide, and
       16:9 of that is 193px of decoration per guide - two screens of scrolling
       to see three titles. A card with a real cover keeps its 16:9, because
       there the picture is content. */
    .guide-card-art-drawn { aspect-ratio: 4 / 1; }
    .guide-card-mark { width: 40px; height: 40px; }
    .guide-card-mark svg { width: 22px; height: 22px; }
    .gallery-page { padding: 12px 16px 48px; }
    .gallery-grid, .gallery-grid.gallery-grid-compact { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
    /* Stated rather than inherited from the rule above: auto-fill puts three
       tracks in a 540px viewport, and eight cards over three columns ends on
       a half-empty row. Two holds all the way down. */
    .gallery-grid.guest-artwork-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .gallery-images { gap: 12px; }
}

/* Gallery create and manage forms (2026-09-17) */
.gallery-create, .gallery-manage { margin-bottom: 22px; }
.gallery-create > summary, .gallery-manage > summary { display: inline-flex; align-items: center; justify-content: center; list-style: none; cursor: pointer; }
.gallery-create > summary::-webkit-details-marker, .gallery-manage > summary::-webkit-details-marker { display: none; }
.gallery-create-form, .gallery-manage-form { display: grid; gap: 12px; max-width: 620px; margin-top: 14px; padding: 18px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.gallery-create-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.gallery-create-form input[type="text"], .gallery-create-form textarea, .gallery-create-form select,
.gallery-manage-form input[type="text"], .gallery-manage-form input[type="file"] { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 14px; }
.gallery-create-form .btn, .gallery-manage-form .btn { justify-self: start; }
.gallery-create-note { margin: 0; color: var(--muted-2); font-size: 12px; line-height: 1.5; }

.gallery-image-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px 10px; border-top: 1px solid var(--border-soft); }
.gallery-image-actions form { margin: 0; }
.gallery-image-cover { padding: 3px 9px; border: 1px solid var(--accent-dim); border-radius: 999px; color: var(--accent); font-size: 11px; font-weight: 700; }

/* Artwork review queue (2026-09-17) */
.admin-artwork-card { margin-bottom: 18px; padding-bottom: 16px; }
.admin-artwork-meta { margin: 4px 0 0; color: var(--muted); font-size: 12px; }
.admin-artwork-description { margin: 0 18px 12px; color: var(--muted); font-size: 13px; line-height: 1.55; }
/* A row that scrolls rather than wraps: a moderator scanning a queue wants each
   gallery to occupy a predictable height. */
.admin-artwork-strip { display: flex; gap: 8px; overflow-x: auto; padding: 0 18px 12px; scrollbar-width: thin; }
.admin-artwork-strip img { display: block; width: 104px; height: 104px; object-fit: cover; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.admin-artwork-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 0 18px; }
.admin-artwork-actions form { display: flex; align-items: center; gap: 8px; margin: 0; }
.admin-artwork-hide input[type="text"] { min-width: 0; width: clamp(180px, 28vw, 320px); padding: 7px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 13px; }
.gallery-create-mine { margin: 10px 0 0; font-size: 13px; }

/* ---- Clan cards (2026-09-18) -----------------------------------------
 *
 * The directory used to draw its own row inline - avatar, name, excerpt and a
 * member count in a flex line, held together by six style="" attributes - and
 * it was the last listing on the site without a component or a stylesheet
 * entry. It looked it: a clan with a banner, an avatar, a region and a primary
 * game showed none of them.
 *
 * The grid has no column count, so the same markup is one column on a phone,
 * two on a tablet and three or four on a wide screen without a breakpoint per
 * page. Only the phone rule below exists, and it exists to drop the minimum
 * below 300px, which auto-fit would otherwise overflow a 320px screen with.
 *
 * auto-fit rather than auto-fill. The landing row holds exactly four clans, and
 * auto-fill keeps the empty fifth track it has room for on a wide screen - a
 * row that looks like something failed to load, which is the same defect the
 * guide row was fixed for by asking for a fourth guide. auto-fit collapses the
 * empty track and the four cards share the width instead.
 *
 * The card's own max-width is the other end of that: a 1fr track with one clan
 * in it is one card the full width of the page, which a directory with a single
 * clan would otherwise draw.
 */
.clan-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 14px; }
.clan-card-grid.clan-card-grid-compact { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 12px; }
.clan-card { position: relative; display: flex; min-width: 0; max-width: 460px; flex-direction: column; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); color: inherit; text-decoration: none; transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
.clan-card:hover { transform: translateY(-3px); border-color: var(--accent-dim); background: var(--surface-hover); box-shadow: var(--shadow-card); text-decoration: none; }
/* The banner is the only thing that makes one clan look different from the
   next, so a clan that has not uploaded one gets a tinted gradient rather than
   a grey bar - deliberate, not missing. */
.clan-card-banner { display: block; height: 78px; background-color: var(--surface-hover); background-position: center; background-size: cover; }
.clan-card.is-plain .clan-card-banner { background-image: linear-gradient(118deg, color-mix(in srgb, var(--accent) 30%, var(--surface-alt)), var(--surface-hover) 58%, var(--surface-alt)); }
.clan-card-body { display: grid; gap: 9px; min-width: 0; padding: 0 16px 16px; }
.clan-card-avatar { margin-top: -26px; }
.clan-card-avatar .avatar-img, .clan-card-avatar .avatar-fallback { width: 52px; height: 52px; border: 3px solid var(--surface); font-size: 18px; }
.clan-card-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 10px; min-width: 0; }
.clan-card-title { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.clan-card-title b { font-size: 16px; font-weight: 750; line-height: 1.25; overflow-wrap: anywhere; }
.clan-card:hover .clan-card-title b { color: var(--accent); }
.clan-card-members { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); white-space: nowrap; font-size: 12px; font-weight: 600; }
.clan-card-members .ui-icon { color: var(--accent); }
/* Two lines, clamped: the cards in a row have to end level, and a description
   is 1000 characters of somebody else's formatting. */
.clan-card-description { display: -webkit-box; overflow: hidden; color: var(--text-soft); font-size: 13px; line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.clan-card-facts { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.clan-card-facts > span { padding: 3px 9px; border: 1px solid var(--border-soft); border-radius: 999px; background: var(--surface-alt); color: var(--muted); font-size: 11px; font-weight: 600; }
/* Same rhythm as the artwork and guide rows it sits above. */
.guest-clans { margin-top: 28px; }

@media (prefers-reduced-motion: reduce) {
    .clan-card { transition: none; }
    .clan-card:hover { transform: none; }
}

@media (max-width: 560px) {
    .clan-card-grid, .clan-card-grid.clan-card-grid-compact { grid-template-columns: minmax(0, 1fr); gap: 10px; }
    .clan-card-banner { height: 64px; }
}

/* ---- Phone: form controls, touch targets and safe areas ------------------
 *
 * Appended at the end of the file deliberately. Several of the declarations
 * below repeat a selector that already appears earlier - .discussion-reply-form
 * input, .guide-step-form input, .games-directory-filters select - at the same
 * specificity. A media query adds none of its own, so the only thing that can
 * settle those is source order, and source order means last. Putting this block
 * anywhere but the end would make it dead on arrival, which is the failure this
 * file has already been through three times.
 */

/*
 * iOS Safari zooms the whole page when a text field under 16px takes focus, and
 * does not zoom back out. Every control here was 14px or less, so signing in,
 * registering, searching, writing a post or answering a message each threw the
 * layout sideways on an iPhone and left it there.
 *
 * 16px below the tablet breakpoint only. On a phone these controls are full
 * width anyway, so nothing has to be given up for it; the desktop density the
 * mockups asked for is untouched.
 */
@media (max-width: 899px) {
    textarea,
    select,
    input[type="text"],
    input[type="email"],
    input[type="password"],
    input[type="search"],
    input[type="url"],
    input[type="tel"],
    input[type="number"],
    input[type="date"],
    input[type="datetime-local"],
    input[type="time"],
    input[type="month"],
    input[type="week"],
    .field input[type="text"],
    .field input[type="email"],
    .field input[type="password"],
    .field input[type="search"],
    .field input[type="url"],
    .field input[type="tel"],
    .field input[type="number"],
    .field input[type="date"],
    .field input[type="datetime-local"],
    .field input[type="time"],
    .field input[type="month"],
    .field input[type="week"],
    .field textarea,
    .field select,
    .comment-composer textarea,
    .discussion-reply-form input,
    .discussion-composer textarea,
    .games-directory-filters select,
    .games-directory-filters input[type="search"],
    .admin-tag-block input[type="text"],
    .admin-artwork-hide input[type="text"],
    .guide-step-form input[type="text"],
    .guide-step-form textarea,
    .gallery-manage-form input[type="text"],
    .gallery-create-form input[type="text"],
    .gallery-create-form textarea,
    .message-composer-input,
    .message-edit-form input,
    .messages-member-form input,
    .event-manage-form input,
    .event-manage-form select,
    .event-manage-form textarea,
    .admin-toolbar input,
    .admin-toolbar select,
    .search-box input {
        font-size: 16px;
    }
}

/*
 * Touch targets. Apple asks for 44px, Material for 48; the topbar links were
 * 34px, a .btn 36-39px and a .btn-sm 28px, so almost nothing on a phone met
 * either. min-height rather than padding, so a button that already has room
 * keeps the padding the design gave it and only the short ones grow.
 */
@media (max-width: 899px) {
    .btn {
        min-height: 44px;
    }

    .btn-sm {
        min-height: 40px;
    }

    /*
     * 40px, not the 44 the rest of this block uses. The topbar is sticky and
     * already takes two rows on a phone; 44px links plus 44px buttons put it at
     * 101px, which is 12% of an iPhone screen given over permanently to
     * navigation. 40px is a comfortable target and, with the row padding below
     * trimmed to match, leaves the bar at roughly the height it already was
     * while every link in it grows from 34px.
     */
    .topnav a {
        display: inline-flex;
        align-items: center;
        min-height: 40px;
    }

    .topbar-inner {
        gap: 4px 12px;
        padding-top: 4px;
        padding-bottom: 4px;
    }

    /*
     * A checkbox is the one control that cannot simply be grown - the box is
     * drawn at its own size and a 44px box looks absurd. 22px is as far as it
     * goes visually; the rest of the target comes from the label beside it,
     * which is why .field-consent lays the two out as one row.
     */
    input[type="checkbox"],
    input[type="radio"] {
        width: 22px;
        height: 22px;
        accent-color: var(--accent);
    }

    .field-consent {
        align-items: center;
        gap: 12px;
        min-height: 44px;
    }

    .field-consent label {
        padding: 6px 0;
    }
}

/*
 * The safe-area work lives with the rules it belongs to - .topbar, .container,
 * .site-footer and .pwa-install each carry their own env() - because this file
 * allows one rule per selector per scope and a test enforces it. Worth the
 * detour: a reader looking at .topbar sees why it has top padding, instead of
 * finding it eight hundred lines away under a heading about phones.
 */

/*
 * The hero call to action, at 320px.
 *
 * .game-hero-actions becomes a one-column grid on a phone, and a grid item's
 * min-width defaults to auto - its min-content size. .btn is white-space:nowrap,
 * so min-content was the full unbroken label: "Log in to follow Arma Reforger
 * and join its clans" measured 289px inside a 266px track and .game-hero's
 * overflow:hidden cut the last two words off. min-width:0 lets the item match
 * its track, and the button is allowed to wrap onto a second line rather than
 * demanding one.
 */
.game-hero-actions > * {
    min-width: 0;
}

@media (max-width: 560px) {
    .game-hero-actions .btn,
    .login-cta-inline .btn,
    .login-cta-actions .btn {
        white-space: normal;
        text-wrap: balance;
    }
}

/*
 * A failed image used to leave a hole: the artwork viewer drew a 341x512 void
 * with nothing in it, and a guide card lost its whole art panel. The containers
 * carry the surface themselves, so a broken or still-loading image reads as an
 * empty frame that belongs to the page instead of a gap in it.
 */
.gallery-image,
.gallery-card-art,
.guide-card-art,
.game-poster-art {
    background: var(--surface-alt);
}

/* ---------------------------------------------------------------------------
 * Live streams
 *
 * The card is a 16:9 thumbnail with a caption block, which is the shape every
 * streaming service uses and therefore the shape a visitor already knows how to
 * read. The aspect-ratio is declared on the frame rather than left to the image
 * so the grid does not reflow as thumbnails arrive - they come from Twitch's
 * CDN one by one, and a grid that jumps while it loads is worse than a grid
 * that holds empty boxes for a moment.
 * ------------------------------------------------------------------------- */

.streams-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

/* ---- Filters ---------------------------------------------------------- */

.streams-filters {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 18px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border-soft);
}

.streams-chips { display: flex; gap: 8px; flex-wrap: wrap; }

.streams-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--muted);
    font: inherit;
    font-size: 13px;
    font-weight: 650;
    cursor: pointer;
}

.streams-chip span {
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--surface-alt);
    color: var(--muted-2);
    font-size: 11px;
    font-weight: 700;
}

.streams-chip:hover { border-color: var(--border); background: var(--surface-hover); color: var(--text); }

.streams-chip.is-active {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--text);
}

.streams-chip.is-active span { background: var(--accent); color: var(--accent-fg); }

.streams-filter-controls { display: flex; gap: 8px; flex-wrap: wrap; }
.streams-filter-controls select,
.streams-filter-controls input[type="search"] { min-width: 170px; }

.streams-no-matches { margin: 24px 0 0; color: var(--muted); font-size: 14px; text-align: center; }

.link-button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.streams-head h1 { margin: 0; }
.streams-count { margin: 0; color: var(--muted); font-size: 13px; }
.streams-empty p { margin: 0 0 6px; }
.streams-empty p:last-child { margin-bottom: 0; }

.streams-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(clamp(240px, 22vw, 300px), 1fr));
    gap: 18px;
}

.stream-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
    transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}

.stream-card[hidden] { display: none; }

.stream-card:hover {
    border-color: var(--border);
    transform: translateY(-2px);
    box-shadow: var(--shadow-card);
}

/*
 * The play affordance. It sits under the thumbnail until the card is hovered or
 * something inside it takes focus - the second half matters, because a keyboard
 * user never triggers :hover and would otherwise get no hint that the card
 * opens a player rather than leaving the site.
 */
.stream-card-play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    color: #fff;
    background: rgba(7, 9, 11, .45);
    opacity: 0;
    transition: opacity .15s ease;
}

.stream-card-play .ui-icon {
    padding: 10px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-fg);
    box-sizing: content-box;
}

.stream-card:hover .stream-card-play,
.stream-card:focus-within .stream-card-play { opacity: 1; }

.stream-card-art {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    background: var(--surface-alt);
    /*
     * The frame is a flex item, and a flex item's min-height is auto - which
     * lets its content push it past the aspect-ratio rather than being cropped
     * to it. A thumbnail that is not 16:9 then makes its card taller than the
     * ones beside it, which is the ragged grid the aspect-ratio was declared to
     * prevent. Twitch serves 16:9 today, so this only shows up the day it
     * serves something else - measured at 194px against a correct 164px.
     */
    min-height: 0;
}

.stream-card-art img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.stream-card-art-fallback {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    color: var(--muted-2);
}

/*
 * The live pill and the viewer count sit on the thumbnail, which is an
 * arbitrary photograph - so both carry their own solid backing rather than
 * relying on the image behind them being dark enough to read against.
 */
.stream-live-pill,
.stream-viewers {
    position: absolute;
    top: 8px;
    padding: 3px 7px;
    border-radius: var(--radius-sm);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1.4;
}

.stream-live-pill {
    left: 8px;
    background: var(--danger);
    color: #fff;
}

.stream-viewers {
    right: 8px;
    background: rgba(7, 9, 11, 0.82);
    color: var(--text);
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
}

.stream-card-body { padding: 12px 13px 13px; }

.stream-card-who {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-bottom: 9px;
}

.stream-card-avatar {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    overflow: hidden;
}

.stream-card-avatar img { width: 100%; height: 100%; object-fit: cover; }

.stream-card-avatar-official {
    display: grid;
    place-items: center;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--muted);
}

.stream-card-names { min-width: 0; line-height: 1.3; }
.stream-card-names a,
.stream-card-official { font-weight: 650; font-size: 13px; color: var(--text); }
.stream-card-names a:hover { color: var(--accent); }

.stream-card-handle {
    display: block;
    font-size: 11px;
    color: var(--muted);
}

/*
 * Two lines, then an ellipsis. Stream titles run long and are often a list of
 * commands and social links; letting one push a card taller than its neighbours
 * would break the grid's rhythm for no gain in meaning.
 */
.stream-card-title {
    margin: 0 0 7px;
    font-size: 13px;
    color: var(--text-soft);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-decoration: none;
}

a.stream-card-title:hover { color: var(--text); }

/* ---- The player ------------------------------------------------------- */

/*
 * Wider and darker than the video dialog on purpose: this is a live broadcast
 * somebody chose to sit and watch, not a two-minute clip glanced at from a game
 * hub. The backdrop goes almost to black so the page behind stops competing.
 */
/*
 * The third term is what keeps a 16:9 player on screen. Width alone is not
 * enough: on a short or landscape viewport a dialog 776px wide needs 436px of
 * frame plus a header and a footer, which is taller than the window - the panel
 * then overflows and the footer is unreachable. Sizing the dialog by the height
 * available for a 16:9 frame makes it narrow instead of overflow, which is the
 * same trick the video dialog uses for portrait clips.
 */
.stream-dialog {
    width: min(1120px, calc(100vw - 24px), calc((100dvh - 150px) * 16 / 9));
    max-width: none;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 30px 90px rgba(0, 0, 0, .8);
}

.stream-dialog::backdrop { background: rgba(3, 5, 7, .92); backdrop-filter: blur(6px); }
.stream-dialog-panel { display: grid; gap: 0; }

.stream-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px 12px 16px;
    border-bottom: 1px solid var(--border-soft);
}

.stream-dialog-heading { display: flex; align-items: center; gap: 10px; min-width: 0; }

.stream-dialog-live {
    flex: 0 0 auto;
    padding: 2px 7px;
    border-radius: var(--radius-sm);
    background: var(--danger);
    color: #fff;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .04em;
    text-transform: uppercase;
}

/* .stream-dialog-head h2 shares the video dialog's clamp - see that rule. */

.stream-dialog-close { flex: 0 0 auto; padding: 4px 10px; font-size: 22px; line-height: 1; }
.stream-dialog-frame { position: relative; aspect-ratio: 16 / 9; width: 100%; min-height: 0; background: #000; }
.stream-dialog-frame iframe { display: block; width: 100%; height: 100%; border: 0; }

.stream-dialog-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
}

/* .stream-dialog-foot small shares the video dialog's rule - see it above. */

/* The blocked-player hint sits beside the Open on Twitch button, and pushes the
   channel line out of the way rather than wrapping the footer onto two rows. */
.stream-dialog-hint { flex: 0 1 auto; margin-left: auto; color: var(--warning) !important; }
.stream-dialog-hint[hidden] { display: none; }

/*
 * On a phone the player fills the screen. A 16:9 frame inside a rounded panel
 * inside a 24px gutter leaves a video about as tall as a line of text.
 */
@media (max-width: 560px) {
    .stream-dialog {
        width: 100vw;
        max-height: 100dvh;
        margin: 0;
        border: 0;
        border-radius: 0;
    }

    .stream-dialog-head { padding: 10px 10px 10px 14px; }
    .stream-dialog-head h2 { font-size: 14px; }
}

.stream-card-meta {
    margin: 0;
    font-size: 12px;
    color: var(--muted);
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.stream-card-meta a { color: var(--accent); }
.stream-card-since { color: var(--muted-2); }

/*
 * The live ring on an avatar, wherever a member appears.
 *
 * The wrapper is inline-block and sized by its content, so it inherits whatever
 * dimensions the avatar already had at that call site - a 32px stream card and a
 * 96px profile header both work without either one being named here.
 */
.avatar-live { position: relative; display: inline-block; line-height: 0; }

.avatar-live > .avatar-img,
.avatar-live > .avatar-fallback {
    box-shadow: 0 0 0 2px var(--danger);
}

.avatar-live-pill {
    position: absolute;
    left: 50%;
    bottom: -6px;
    transform: translateX(-50%);
    padding: 1px 5px;
    border-radius: 999px;
    background: var(--danger);
    color: #fff;
    font-size: 9px;
    font-weight: 800;
    line-height: 1.5;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* The profile's own live card. Shares the pill and viewer-count rules above so
 * one broadcast looks the same wherever KLANS shows it. */
.profile-live-card { overflow: hidden; }

.profile-live-art {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    min-height: 0;
    background: var(--surface-alt);
}

.profile-live-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.profile-live-body { padding: 12px 14px 14px; }
.profile-live-title { margin: 0 0 6px; font-size: 13px; color: var(--text-soft); }
.profile-live-meta { margin: 0 0 11px; font-size: 12px; color: var(--muted); }
.profile-live-meta a { color: var(--accent); }
.profile-live-watch { display: block; width: 100%; text-align: center; }

@media (max-width: 640px) {
    .streams-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
    .stream-card-body { padding: 10px 11px 11px; }
}

/*
 * Cookie consent: the banner, the preferences dialog, and the option rows the
 * two share with the standalone /consent.php page.
 *
 * The banner is a bar at the foot of the viewport rather than a modal over the
 * middle of it. That is a deliberate choice and not a cosmetic one: a modal
 * with a backdrop makes reading the page conditional on answering, which is
 * the "consent or leave" shape regulators treat as coercion, and it traps
 * focus in a dialog the visitor did not open. A bar leaves the page readable,
 * leaves the Privacy Policy link reachable, and still cannot be missed.
 *
 * Accept and Reject are the same width, the same height and the same weight of
 * type. Only the fill differs, and only because one is the affirmative action.
 * If these two ever stop matching, the banner has become a dark pattern.
 */
.cookie-banner {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 60;
    border-top: 1px solid var(--border);
    background: var(--surface);
    box-shadow: 0 -18px 48px rgba(0, 0, 0, .55);
    padding: 14px max(16px, env(safe-area-inset-right)) max(14px, calc(env(safe-area-inset-bottom) + 14px)) max(16px, env(safe-area-inset-left));
    /*
     * Capped, and the cap is what the body reserves below. A fixed bar cannot
     * be scrolled out of the way, so on a small screen an uncapped one both
     * eats the viewport and covers the foot of the page - including the
     * Privacy Policy link this banner itself points at. The cap and the
     * reserve are written in the same unit on purpose: they cannot drift apart
     * the way a measured height and a hardcoded padding do.
     */
    max-height: 62vh;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.cookie-banner-inner {
    display: flex;
    align-items: center;
    gap: 22px;
    max-width: var(--max-width);
    margin: 0 auto;
}

.cookie-banner-copy { flex: 1 1 auto; min-width: 0; }
.cookie-banner-copy h2 { margin: 0 0 5px; font-size: 15px; letter-spacing: -.01em; }
.cookie-banner-copy p { margin: 0; color: var(--text-soft); font-size: 13px; line-height: 1.6; }
.cookie-banner-links { margin-top: 7px !important; font-size: 12px !important; }
.cookie-banner-links a { color: var(--accent); text-decoration: none; }
.cookie-banner-links a:hover { text-decoration: underline; }

.cookie-banner-actions {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}

/* Equal footprint for accept and reject - see the note above this block. */
.cookie-banner-btn {
    min-width: 132px;
    justify-content: center;
    text-align: center;
}

/*
 * The install dock is fixed in the same corner. Without this it sits on top of
 * the banner's buttons, which is the one place on the site where a covered
 * control cannot simply be scrolled to.
 */
body:has(.cookie-banner) .pwa-install-dock {
    bottom: max(18px, calc(env(safe-area-inset-bottom) + 150px));
}

/* The banner covers the foot of the page for as long as it is there. */
body:has(.cookie-banner) {
    padding-bottom: 168px;
}

.cookie-dialog {
    width: min(620px, calc(100vw - 28px));
    max-width: none;
    max-height: min(86vh, 760px);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 24px 80px rgba(0, 0, 0, .74);
}

.cookie-dialog::backdrop { background: rgba(0, 0, 0, .78); backdrop-filter: blur(4px); }

.cookie-dialog-panel {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    max-height: inherit;
}

.cookie-dialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 15px 14px 13px 18px;
    border-bottom: 1px solid var(--border-soft);
}

.cookie-dialog-head h2 { margin: 0 0 4px; font-size: 16px; letter-spacing: -.01em; }
.cookie-dialog-head p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.6; }
.cookie-dialog-close { flex: 0 0 auto; padding: 2px 9px; font-size: 20px; line-height: 1.2; }

.cookie-dialog-body {
    display: grid;
    gap: 10px;
    overflow-y: auto;
    padding: 16px 18px;
}

.cookie-dialog-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 13px 18px;
    border-top: 1px solid var(--border-soft);
    background: var(--surface-alt);
}

/* Shared by the dialog and the standalone page, so the two cannot drift into
   describing the same category differently. */
.cookie-option {
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-alt);
}

.cookie-option-locked { border-style: dashed; }

.cookie-option-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
}

.cookie-option-label {
    display: flex;
    flex: 1 1 auto;
    gap: 10px;
    align-items: flex-start;
    cursor: pointer;
    min-width: 0;
}

.cookie-option-locked .cookie-option-label { cursor: default; }
.cookie-option-label input { flex: 0 0 auto; width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent); }
.cookie-option-label span { display: grid; gap: 2px; min-width: 0; }
.cookie-option-label strong { font-size: 13px; }
.cookie-option-label small { color: var(--muted); font-size: 12px; line-height: 1.5; }

.cookie-option-detail {
    margin: 9px 0 0;
    padding-left: 26px;
    color: var(--muted-2);
    font-size: 12px;
    line-height: 1.65;
}

/* The same options again, in the flow of the /consent.php document. */
.consent-form { display: grid; gap: 10px; margin: 18px 0 8px; }
.consent-form-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }

@media (max-width: 899px) {
    .cookie-banner-inner { flex-direction: column; align-items: stretch; gap: 13px; }
    .cookie-banner-actions { justify-content: stretch; }
    .cookie-banner-btn { flex: 1 1 140px; min-width: 0; }
    body:has(.cookie-banner) { padding-bottom: 64vh; }
    body:has(.cookie-banner) .pwa-install-dock { bottom: max(18px, calc(env(safe-area-inset-bottom) + 64vh)); }
}

/*
 * Reduced motion, for everything rather than the four components that had it.
 * Someone who has asked the operating system to stop animation should not have
 * to hope each new card remembered to check.
 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ---- Events directory + create form (2026-09-20) ------------------------
 *
 * The create form used to be one 35-field column with the layout re-typed as
 * an inline style on every control: a wall that gave a title the same weight
 * as a longitude, and an organiser no way to see where "when" ended and
 * "where" began. It is now six labelled fieldsets over a two-column grid that
 * collapses to one on a phone, and the controls take their appearance from
 * the .field block like every other form on the site.
 *
 * Appended rather than merged into the event-detail block above: nothing here
 * shares a selector with it, and the page's own phone rules need to sit after
 * the generic ones in this file to win on source order.
 */

/* 980 rather than 860: the row carries three columns now - date, detail,
   attendance - and 860 squeezed the middle one until the meta line wrapped on
   a desktop. Wider than that and the attendance block drifts so far from the
   title that the two stop reading as one row. */
.events-directory { max-width: 980px; padding: 20px 20px 72px; }
.events-directory-backlinks { margin: 0; color: var(--muted); font-size: 13px; }
.events-directory-pager { display: flex; gap: 8px; margin-top: 22px; }

/* ---- Page header ----
 * Title and lede on the left, the create disclosure on the right. When that
 * disclosure opens it claims the whole grid, because the form inside it is
 * six fieldsets wide and a 35-field form in a header's right-hand cell is
 * not a form. */
.events-hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 12px 20px; margin-bottom: 20px; }
.events-hero-text { display: grid; gap: 6px; min-width: 0; }
.events-hero-text h1 { margin: 0; }
.events-hero-text .page-lede { margin: 0; }
.events-hero > .event-create { margin: 0; justify-self: end; }
.events-hero > .event-create[open] { grid-column: 1 / -1; justify-self: stretch; margin-top: 4px; }

/* ---- Filter bar ---- */
.events-filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; padding: 10px 12px; }
.events-filter-bar > label { display: inline-flex; align-items: center; gap: 6px; margin: 0; color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.events-filter-bar > label .ui-icon { color: var(--accent); }
.events-filter-bar select { flex: 1 1 180px; min-width: 0; max-width: 260px; padding: 8px 10px; font-size: 13px; }
.events-filter-count { margin-left: auto; color: var(--muted-2); font-size: 12px; white-space: nowrap; }

/* ---- Month rails ----
 * A directory ordered by date but printed without any date structure makes
 * the reader re-read rows to find where next month starts. The heading sticks
 * under the topbar so that answer stays on screen through a long scroll. */
.events-month { margin-bottom: 26px; }
.events-month:last-of-type { margin-bottom: 0; }
.events-month-heading {
    position: sticky;
    top: calc(56px + env(safe-area-inset-top));
    z-index: 5;
    margin: 0 0 10px;
    padding: 8px 2px;
    background: linear-gradient(var(--bg) 70%, transparent);
    color: var(--text);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.events-list { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }

/* ---- One event ----
 * The whole row is the link, so it is laid out on the anchor itself with
 * inline children rather than divs - a block-level child of an <a> is legal
 * but a <details> inside one is not, and the series list has to be a sibling
 * of the link for that reason. */
.event-card { overflow: hidden; transition: border-color .15s ease, background .15s ease; }
.event-card:hover { border-color: var(--accent-dim); background: var(--surface-hover); }
.event-card.is-live { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.event-card.is-ended { opacity: .68; }
.event-card.is-ended:hover { opacity: 1; }

.event-card-link {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 16px;
    padding: 14px 16px;
    color: inherit;
    text-decoration: none;
}
.event-card-link:hover { text-decoration: none; }
.event-card-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; border-radius: var(--radius); }

/* ---- The tile: artwork with the date sitting on it ----
 * Forty rows of type differing only in their words is a list nobody scans;
 * the game's box art is recognised before the title beside it is read. The
 * date did not move out of the way for it - it is printed over the foot of
 * the artwork, so the row still has exactly one landmark column rather than
 * two competing ones.
 *
 * 3:4 because box art is portrait nearly everywhere it comes from, and a
 * landscape crop takes a strip out of the middle of a cover that was drawn to
 * be read whole. object-fit keeps a header image from stretching into it. */
.event-card-media {
    position: relative;
    display: grid;
    align-content: end;
    width: 76px;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: linear-gradient(155deg, var(--surface-hover), var(--surface-alt));
}
.event-card-art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .3s ease;
}
.event-card:hover .event-card-art { transform: scale(1.06); }
/* The scrim is the whole reason a date can be printed on artwork nobody
   vetted: covers come in white, in neon and in photographs of daylight, and
   without this the day number vanished on about one in four of them.
   It is clear for the first two thirds and near-solid under the date, so the
   cover is shown rather than dimmed everywhere the date is not. The
   near-solid part is not optional: a box that puts its own logo along the
   bottom - which most of them do - otherwise prints that logo straight
   through the day number. */
.event-card-media:not(.is-blank)::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(6,10,16,0) 0%, rgba(6,10,16,.04) 52%, rgba(6,10,16,.62) 70%, rgba(6,10,16,.94) 80%, rgba(6,10,16,.95) 100%);
}
/* A game with no cover uploaded still gets its initials rather than an empty
   box - two letters are recognised on a second visit, blank space is not.
   The padding keeps them clear of the date line underneath. */
.event-card-art-initials {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding-bottom: 24px;
    color: var(--muted-2);
    font-size: 16px;
    font-weight: 800;
    letter-spacing: .04em;
}
.event-card-art-initials.is-glyph { opacity: .55; }

.event-card-date {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 4px;
    padding: 5px 4px 7px;
    line-height: 1;
}
.event-card.is-live .event-card-media { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.event-card.is-live .event-card-media.is-blank { background: var(--accent-soft); }
.event-card-day { font-size: 16px; font-weight: 800; letter-spacing: -.02em; }
.event-card-mon { color: var(--muted); font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
/* Over artwork both colours are read against the scrim rather than against
   the card, so both are restated. The shadow is the belt to the scrim's
   braces: a cover whose own logo sits at the foot of the box - which is where
   most of them put it - can still show through the plate as a light shape
   directly behind the date. */
.event-card-media:not(.is-blank) .event-card-day { color: #fff; }
.event-card-media:not(.is-blank) .event-card-mon { color: rgba(255,255,255,.82); }
.event-card-media:not(.is-blank) .event-card-date { text-shadow: 0 1px 3px rgba(2,5,9,.9); }
@media (prefers-reduced-motion: reduce) {
    .event-card-art { transition: none; }
    .event-card:hover .event-card-art { transform: none; }
}

.event-card-main { display: grid; gap: 5px; min-width: 0; }
.event-card-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.event-card-pills .pill { gap: 4px; }
.event-card-title { font-size: 16px; font-weight: 700; line-height: 1.3; overflow-wrap: anywhere; }
.event-card:hover .event-card-title { color: var(--accent); }

.event-card-when { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; color: var(--text-soft); font-size: 13px; }
.event-card-when .ui-icon { flex: 0 0 auto; color: var(--accent); }
.event-card-when time { font-weight: 650; }
.event-card-zone { color: var(--muted-2); font-size: 12px; }

/* The facts that used to run together in one dot-separated string. Each one
   gets its own mark, so the eye can jump to "which game" without reading the
   organiser first. */
.event-card-who { display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; color: var(--muted); font-size: 12.5px; }
.event-card-who > span { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.event-card-who .ui-icon { flex: 0 0 auto; opacity: .8; }

/* Live now, as a dot that is only a dot when motion is unwelcome. */
.event-pill-live { background: var(--accent-soft); color: var(--accent); }
.event-pill-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: event-pulse 1.8s ease-in-out infinite; }
@keyframes event-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .event-pill-dot { animation: none; } }

/* ---- Attendance ----
 * "1/8 going" as flat grey text told the reader a number and nothing about
 * whether it was worth turning up. The bar makes the same two numbers a
 * shape, and the line under it says the thing they were being asked to work
 * out: how many places are left. */
.event-card-rsvp { display: grid; justify-items: end; gap: 2px; width: 108px; text-align: right; }
.event-card-count { color: var(--text); font-size: 19px; font-weight: 800; line-height: 1.1; }
.event-card-cap { color: var(--muted-2); font-size: 14px; font-weight: 700; }
.event-card-going { color: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
/* --border, not --surface-alt: the alt surface is darker than the card it
   sits on, so the unfilled part of the track disappeared and a nine-of-
   thirty-two fill read as a green dash floating in space rather than as a
   bar that is mostly empty - which is the whole of what it has to say. */
.event-card-bar { display: block; width: 100%; height: 5px; margin-top: 4px; border-radius: 999px; background: var(--border); overflow: hidden; }
.event-card-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.event-card-bar-fill.is-full { background: var(--warning); }
.event-card-spots { color: var(--muted-2); font-size: 11px; }
.event-card-spots.is-full { color: var(--warning); font-weight: 650; }

/* ---- The dates of a series ----
 * A recurring series is one card, not one card per date. This is where the
 * dates it swallowed come back - each still its own page, its own RSVPs. */
.event-card-series { border-top: 1px solid var(--border-soft); }
.event-card-series > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 9px 16px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 650;
    cursor: pointer;
    list-style: none;
}
.event-card-series > summary::-webkit-details-marker { display: none; }
.event-card-series > summary::marker { content: ''; }
.event-card-series > summary:hover { color: var(--text); background: var(--surface-hover); }
.event-card-series > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.event-card-series > summary .ui-icon { transition: transform .15s ease; }
.event-card-series[open] > summary .ui-icon { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .event-card-series > summary .ui-icon { transition: none; } }
.event-card-series ul { display: grid; gap: 1px; margin: 0; padding: 0 10px 10px; list-style: none; }
.event-card-series ul a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    color: var(--text-soft);
    font-size: 13px;
    text-decoration: none;
}
.event-card-series ul a:hover { background: var(--surface-alt); color: var(--text); text-decoration: none; }
.event-card-series ul a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.event-card-series ul span { flex: 0 0 auto; color: var(--muted-2); font-size: 12px; }

/* ---- The disclosure that holds the form ---- */

.event-create { margin-bottom: 24px; }
.event-create-summary {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--accent-fg);
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
    list-style: none;
}
.event-create-summary::-webkit-details-marker { display: none; }
.event-create-summary::marker { content: ''; }
.event-create-summary:hover { background: var(--accent-dim); border-color: var(--accent-dim); }
.event-create-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.event-create[open] > .event-create-summary { margin-bottom: 12px; background: transparent; border-color: var(--border); color: var(--text); }
.event-create[open] > .event-create-summary:hover { background: var(--surface-hover); }

.event-create-form { display: grid; gap: 16px; padding: 20px; }

/* ---- A section of the form ---- */

.event-form-section { min-width: 0; margin: 0; padding: 16px 16px 4px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface-alt); }
.event-form-section > legend {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-left: -4px;
    padding: 0 6px;
    color: var(--text);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .055em;
    text-transform: uppercase;
}
.event-form-section > legend .ui-icon { color: var(--accent); }

.event-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
.event-form-grid > .field { min-width: 0; margin-bottom: 14px; }
.event-form-wide { grid-column: 1 / -1; }
.event-form-grid textarea { resize: vertical; }

/* "required" as a word rather than a bare asterisk nobody has a legend for. */
.field-required { margin-left: 6px; color: var(--accent); font-size: 10px; font-weight: 700; letter-spacing: .06em; }

.event-form-checks { display: grid; gap: 10px; margin-bottom: 14px; }
.event-form-check { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface); cursor: pointer; }
.event-form-check:hover { border-color: var(--border); }
.event-form-check input[type="checkbox"] { flex: 0 0 auto; width: 16px; height: 16px; margin: 1px 0 0; accent-color: var(--accent); }
.event-form-check > span { display: grid; gap: 2px; min-width: 0; }
.event-form-check strong { font-size: 13px; font-weight: 650; }
.event-form-check small { color: var(--muted); font-size: 12px; line-height: 1.5; }

.event-create-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.event-create-actions p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; }

/* ---- Repeat: the recurrence panel ----
 *
 * The old panel asked for "Every N" with a bare number box beside it, which
 * says nothing about N of what, and offered no clue that the field it was
 * about to reject took 1 to 52. It is now one sentence - "Repeat every [2]
 * week(s)" - where the unit is the frequency select itself, so there is no
 * second control to reconcile it with, and both numbers state their range in
 * the markup as well as in the min/max the browser enforces.
 *
 * The panel opens on its checkbox in CSS. A browser without :has() shows the
 * fields all the time, which is what happens with JavaScript off and is
 * harmless: the server only reads them when the box is ticked.
 */

.event-repeat-switch { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 14px; }
.event-repeat-switch input[type="checkbox"] { flex: 0 0 auto; width: 17px; height: 17px; margin: 1px 0 0; accent-color: var(--accent); }
.event-repeat-switch label { display: grid; gap: 3px; min-width: 0; cursor: pointer; }
.event-repeat-switch strong { font-size: 13px; }
.event-repeat-switch span { color: var(--muted); font-size: 12px; line-height: 1.55; }

.event-repeat:not(:has(#make_recurring:checked)) .event-repeat-body { display: none; }
.event-repeat-body { display: grid; gap: 10px; margin-bottom: 14px; padding: 14px; border: 1px solid var(--border-soft); border-radius: var(--radius-sm); background: var(--surface); }

.event-repeat-line { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 14px; font-weight: 650; }
.event-repeat-line label { margin: 0; color: var(--text); font-size: 14px; font-weight: 650; letter-spacing: 0; text-transform: none; }
.event-repeat-number { width: 84px; padding: 9px 10px; text-align: center; }
.event-repeat-unit { width: auto; min-width: 118px; padding: 9px 10px; }
.event-repeat-hint { margin: -2px 0 2px; color: var(--muted-2); font-size: 12px; line-height: 1.55; }
.event-repeat-hint strong { color: var(--text-soft); }

.event-repeat-end { display: grid; gap: 8px; padding-top: 10px; border-top: 1px solid var(--border-soft); }
.event-repeat-heading { color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.event-repeat-option { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 13px; }
.event-repeat-option input[type="radio"] { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.event-repeat-option label { margin: 0; color: var(--text); font-size: 13px; font-weight: 650; letter-spacing: 0; text-transform: none; cursor: pointer; }
.event-repeat-option > span { color: var(--muted); }
.event-repeat-option small { color: var(--muted-2); font-size: 12px; }
.event-repeat-option input[type="datetime-local"] { width: auto; max-width: 100%; padding: 9px 10px; }
/* The unchosen end condition stays readable but stops competing for the eye. */
.event-repeat-option:has(input[type="radio"]:not(:checked)) > span,
.event-repeat-option:has(input[type="radio"]:not(:checked)) > label { color: var(--muted-2); }

.event-repeat-summary {
    margin: 2px 0 0;
    padding: 10px 12px;
    border: 1px solid var(--accent-dim);
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--text);
    font-size: 13px;
    line-height: 1.55;
}
.event-repeat-summary b { color: var(--accent); }
.event-repeat-summary.is-warning { border-color: var(--warning); background: var(--warning-soft); }

@media (max-width: 720px) {
    .event-form-grid { grid-template-columns: minmax(0, 1fr); }
    .event-create-form { padding: 14px; }
    .event-form-section { padding: 14px 13px 2px; }
    .event-create-summary { display: flex; justify-content: center; width: 100%; }
    .event-create-actions { display: grid; }
    .event-create-actions .btn { width: 100%; }
    .event-repeat-option { gap: 8px; }
    .event-repeat-option > span { flex: 1 1 100%; margin-left: 25px; }
    .event-repeat-option input[type="datetime-local"] { flex: 1 1 auto; min-width: 0; }
}

/* ---- The directory on a narrower screen ----
 *
 * Three columns need about 760px before the middle one starts wrapping its
 * own meta line; below that the attendance block leaves the right-hand column
 * and becomes a strip along the bottom of the card, which is also where a
 * thumb expects a summary. The date chip keeps its column at every width -
 * it is the thing the reader scans down, and turning it into another line of
 * text would take the list's one fixed landmark away.
 */
@media (max-width: 860px) {
    .events-directory { max-width: none; }
    .events-hero { grid-template-columns: minmax(0, 1fr); }
    .events-hero > .event-create { justify-self: stretch; }
}

@media (max-width: 760px) {
    .event-card-link {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas: "media main" "rsvp rsvp";
        gap: 12px;
        padding: 13px 13px 12px;
    }
    /* The tile tops out with the title rather than centring against a row it
       no longer spans - centred, it floated beside the second line of a
       wrapped title with white space above it. */
    .event-card-media { grid-area: media; align-self: start; width: 62px; }
    .event-card-art-initials { padding-bottom: 22px; font-size: 14px; }
    .event-card-day { font-size: 15px; }
    .event-card-main { grid-area: main; }
    .event-card-rsvp {
        grid-area: rsvp;
        display: flex;
        align-items: center;
        gap: 9px;
        width: auto;
        padding-top: 11px;
        border-top: 1px solid var(--border-soft);
        text-align: left;
    }
    .event-card-count { font-size: 15px; }
    .event-card-cap { font-size: 13px; }
    .event-card-bar { flex: 1 1 auto; min-width: 36px; margin-top: 0; }
    .event-card-spots { margin-left: auto; white-space: nowrap; }
    /* The select keeps the label's company rather than taking a row of its
       own: a four-row filter bar was taller than the card it filtered. */
    .events-filter-bar { gap: 8px 10px; }
    .events-filter-bar select { flex: 1 1 150px; max-width: none; }
}

@media (max-width: 480px) {
    .events-directory { padding: 16px 14px 56px; }
    .event-card-title { font-size: 15px; }
    .event-card-who { gap: 5px 12px; }
    /* 54px still reads as artwork at arm's length, and the ~20px it gives
       back is the difference between a three-word title fitting on one line
       and wrapping onto three. The date line tightens with it: "20 SEP" at
       the desktop sizes is wider than the tile it sits in. */
    .event-card-media { width: 54px; }
    .event-card-date { gap: 3px; padding: 4px 3px 6px; }
    .event-card-day { font-size: 14px; }
    .event-card-mon { font-size: 9px; letter-spacing: .04em; }
    .event-card-art-initials { padding-bottom: 20px; font-size: 13px; }
    /* Every row is a tap target on a phone; the pager and the series toggle
       need the 44px a finger actually lands on. */
    .events-directory-pager .btn { flex: 1 1 auto; padding-block: 11px; }
    .event-card-series > summary { min-height: 44px; }
}

/* iOS zooms any field under 16px on focus; these three are new since the
   phone block above was written and need the same floor. */
@media (max-width: 767px) {
    .event-repeat-number,
    .event-repeat-unit,
    .event-repeat-option input[type="datetime-local"] { font-size: 16px; }
}

/* ---- Friends (2026-09-20) ------------------------------------------------
 *
 * The page was four copies of one row - avatar, a name, and a button pinned
 * to the far right of a 680px column - re-typed with inline styles in each
 * tab. At thirteen friends that produced thirteen near-empty bands whose only
 * content was a "Remove" button, which is the loudest thing on a page about
 * people you like. It also showed nothing the row of a friends list exists to
 * show: who is online, when anyone was last seen, or how to reach them.
 *
 * Rebuilt as a responsive card grid over the shared vocabulary (.card
 * surfaces, .tabs, .status-text, .stat-row, .pill, the empty-state partial),
 * with presence, a last-seen line, a Message action, and an in-browser filter
 * for a list too long to scan. Remove is demoted to a ghost button that only
 * turns red when a finger is actually on it.
 *
 * Appended rather than merged into a section above: the page's phone rules
 * have to sit after the generic touch-target and 16px-field blocks in this
 * file to win on source order.
 */

/* 940 rather than the old 680: three cards fit across a desktop at this
   width, and a two-line lede stays under 64ch beside the counts. */
.friends-page { max-width: 940px; padding-top: 22px; padding-bottom: 72px; }

/* ---- Page header ----
 * Title and lede on the left, the three counts that answer "is anything
 * waiting on me?" on the right, where the eye lands last. */
.friends-hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    gap: 14px 28px;
    margin-bottom: 18px;
}
.friends-hero-text { min-width: 0; }
.friends-hero-text h1 { margin: 0 0 6px; }
.friends-hero-text .page-lede { margin: 0; }

/* .stat-row is built for a profile banner, where each count takes an equal
   share of a fixed column. Here it is an auxiliary block beside a heading, so
   the stats size to their own content and stop stretching. */
.friends-hero-stats {
    flex: 0 0 auto;
    gap: 22px;
    padding: 12px 18px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    background: var(--surface);
}
.friends-hero-stats .stat { flex: 0 0 auto; text-align: center; }
.friends-hero-stats .stat-value { font-size: 20px; }
.friends-hero-stats .stat.is-online .stat-value { color: var(--accent); }
.friends-hero-stats .stat.is-waiting .stat-value { color: var(--warning); }

/* ---- Tabs ----
 * The four tabs were .btn-sm pills, so the current tab read as a button you
 * had not pressed yet. They are the site's underline tabs now, and they scroll
 * sideways rather than wrapping to a second line on a narrow screen. */
.friends-tabs {
    gap: 20px;
    margin-bottom: 18px;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
}
.friends-tabs::-webkit-scrollbar { display: none; }
.friends-tabs a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    flex: 0 0 auto;
    white-space: nowrap;
    scroll-snap-align: start;
}

.friends-tab-count {
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
}
.friends-tabs a.active .friends-tab-count { background: var(--accent-soft); color: var(--accent); }
/* A request waiting on an answer is the one count on this page that is a task,
   so it keeps its colour whether or not you are looking at that tab. */
.friends-tab-count.is-waiting { background: var(--warning-soft); color: var(--warning); }

/* ---- The grid of people ----
 * auto-fill, not auto-fit: with two friends left, auto-fit would stretch each
 * card to half the page and a 460px-wide card for one name looks broken. */
.friends-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.friend-card {
    display: grid;
    align-content: start;
    gap: 9px;
    padding: 14px 15px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    background: var(--surface);
    transition: border-color .15s ease, background .15s ease;
}
.friend-card:hover { border-color: var(--border); background: var(--surface-hover); }
/* The filter hides a card with the hidden attribute, and display:grid above
   outranks the UA sheet's [hidden] - without this the count said "1 of 13
   shown" over thirteen visible cards. Same rule .stream-card needs, for the
   same reason. */
.friend-card[hidden] { display: none; }
/* Online is the only thing on this page worth a colour of its own, and the dot
   alone is easy to miss across a three-column grid. */
.friend-card.is-online { border-color: color-mix(in srgb, var(--accent) 32%, var(--border-soft)); }

.friend-identity {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}
.friend-identity:hover { text-decoration: none; }
.friend-identity:hover .friend-identity-text strong { color: var(--accent); }
.friend-identity:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-sm); }

/* min-width:0 on both the flex item and the grid inside it, or a long display
   name pushes the avatar out of the card instead of ellipsing. */
.friend-identity-text { display: grid; gap: 1px; min-width: 0; }
.friend-identity-text strong { overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }
.friend-identity-text small { overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }

.friend-card .status-text { margin: 0; }
.friend-card-meta { margin: 0; color: var(--muted); font-size: 12px; }

.friend-card-actions {
    display: flex;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--border-soft);
}
.friend-card-actions form { flex: 1 1 auto; min-width: 0; }
.friend-card-actions .btn { width: 100%; gap: 6px; }
.friend-card-actions > .btn { flex: 1 1 auto; }

/*
 * Remove / Decline / Cancel. Destructive, but not the point of the card - the
 * old page gave each one a full-width outlined button and made losing a friend
 * the most prominent action in the list. Muted until it is hovered, red once
 * the pointer is on it so the consequence is stated before the click.
 */
.friend-card-dismiss { border-color: transparent; }
.friend-card-dismiss:hover,
.friend-card-dismiss:focus-visible { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }

/* ---- Filtering your own list, in the browser ---- */
.friends-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
/* The toolbar ships hidden and app.js reveals it. display:flex outranks the UA
   sheet's [hidden], so without this it would show with scripting off - which is
   the one state the hidden attribute is there to cover. */
.friends-toolbar[hidden] { display: none; }
.friends-toolbar-count { margin: 0 0 0 auto; color: var(--muted-2); font-size: 12px; white-space: nowrap; }

/* The search field and its mark. The icon is positioned rather than a flex
   sibling so the input keeps its own full-width box and the placeholder starts
   clear of the glyph. */
.friends-filter,
.friends-search-field { position: relative; display: block; flex: 1 1 260px; min-width: 0; }
.friends-filter > .ui-icon,
.friends-search-field > .ui-icon {
    position: absolute;
    top: 50%;
    left: 12px;
    transform: translateY(-50%);
    color: var(--muted-2);
    pointer-events: none;
}
.friends-filter input[type="search"],
.friends-search-field input[type="search"] { width: 100%; padding: 10px 12px 10px 36px; }
.friends-filter input[type="search"]::-webkit-search-cancel-button,
.friends-search-field input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }

.friends-search { display: flex; gap: 10px; margin-bottom: 16px; }
.friends-search .btn { flex: 0 0 auto; }

.friends-no-matches { margin: 22px 0 0; color: var(--muted); font-size: 13px; text-align: center; }

/* ---- Friends on a narrower screen ---- */
@media (max-width: 720px) {
    .friends-page { padding-top: 16px; padding-bottom: 56px; }
    /* The counts move under the heading and spread across the width, rather
       than squeezing the lede into a two-word column beside them. */
    .friends-hero { grid-template-columns: minmax(0, 1fr); gap: 14px; }
    .friends-hero-stats { justify-content: space-between; gap: 10px; }
    .friends-hero-stats .stat { flex: 1 1 0; }
    .friends-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
    .friends-hero-text h1 { font-size: 24px; }
    .friends-tabs { gap: 16px; }
    /* One search box per row: at this width the field and its Search button
       shared a line that left the field too short to hold a username. */
    .friends-search { flex-wrap: wrap; }
    .friends-search .btn { flex: 1 1 100%; }
    .friends-toolbar-count { margin-left: 0; }
}

/* ---- People picker (group chats) -------------------------------------
 * Built by app.js over the Start a group / Add to group forms. The list is
 * in the flow rather than floating: the group form sits at the bottom of a
 * sidebar, and a popover there would open off the edge of the card.
 */
.member-picker { position: relative; display: grid; gap: 6px; flex: 1; min-width: 0; }
.member-picker-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 0; padding: 0; list-style: none; }
.member-picker-chips[hidden] { display: none; }
.member-picker-chip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px 4px 3px 9px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); border-radius: 999px; background: var(--accent-soft); font-size: 12px; }
.member-picker-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-picker-chip button { display: grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%; background: none; color: var(--muted); font-size: 15px; line-height: 1; cursor: pointer; }
.member-picker-chip button:hover, .member-picker-chip button:focus-visible { background: var(--surface-hover); color: var(--text); }
.member-picker-input { width: 100%; min-width: 0; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); color: var(--text); font: inherit; font-size: 13px; }
.member-picker-list { max-height: 260px; margin: 0; padding: 4px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); list-style: none; }
.member-picker-list[hidden] { display: none; }
.member-picker-option { display: flex; align-items: center; gap: 9px; padding: 6px 7px; border-radius: var(--radius-sm); cursor: pointer; }
.member-picker-option.is-active { background: var(--surface-hover); box-shadow: inset 2px 0 0 var(--accent); }
.member-picker-option.is-disabled { cursor: default; opacity: .55; }
.member-picker-avatar { display: grid; flex: 0 0 28px; }
.member-picker-avatar .avatar-img, .member-picker-avatar .avatar-fallback { width: 28px; height: 28px; font-size: 10px; }
.member-picker-text { display: grid; flex: 1; min-width: 0; gap: 1px; font-size: 13px; }
.member-picker-text b, .member-picker-text small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-picker-text b { font-weight: 600; }
.member-picker-text small { color: var(--muted); font-size: 11px; }
.member-picker-tag { flex: 0 0 auto; max-width: 45%; overflow: hidden; color: var(--muted); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.member-picker-empty { padding: 10px 8px; color: var(--muted); font-size: 12px; }
.member-picker-note { margin: 0; color: var(--muted); font-size: 11px; }
.member-picker-note:empty { display: none; }
.messages-member-form:has(.member-picker) { align-items: flex-start; }
