/* Umbrella Regiment — application styles.
 *
 * tokens.css is the design system and is loaded first. It owns the palette, the
 * type scale, and the primitives (.card, .btn, .toggle, .tabs, .table, .shell).
 * Nothing in this file redefines those, and nothing here hardcodes a colour that
 * already exists as a token — the three themes work by swapping six variables,
 * and a literal hex is how that silently stops being true.
 *
 * What lives here: the page-level layouts the frames need, and the components
 * that only appear once or twice (war strip, tile grid, settings rail, event
 * card, empty state).
 */

/* ---------------------------------------------------------------- Base --- */

/* All three themes are dark, and the parts of the UI the browser draws itself
   -- select popups, scrollbars, autofill, the caret -- have to be told. The
   <meta name="color-scheme"> in the head does not reach the computed property,
   so it is declared here as well. */
html { color-scheme: dark; }

body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Screen-reader-only, but focusable — used by the skip link. */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
.skip-link:focus {
    position: fixed;
    top: var(--space-3);
    left: var(--space-3);
    z-index: 100;
    width: auto; height: auto;
    clip: auto;
    padding: var(--space-3) var(--space-5);
    background: var(--accent);
    color: var(--on-accent);
    border-radius: var(--radius-sm);
}

/* --------------------------------------------------------------- Shell --- */

.shell-brand {
    display: flex;
    align-items: center;
    gap: 11px;
    /* Padded sideways only: the height is fixed so this frame lines up exactly
     * with the topbar beside it, rather than being pushed taller by the crest
     * and its padding. */
    padding: 0 var(--space-5);
    border-bottom: 1px solid var(--border);
    height: var(--shell-topbar);
    flex: none;
}
.shell-brand img { width: 26px; height: 26px; object-fit: contain; }
.shell-brand span {
    font-family: var(--font-display);
    font-size: 14px;
    letter-spacing: .2em;
    color: var(--text-primary);
}

.nav-list {
    padding: 16px var(--space-3);
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.nav-item { gap: var(--space-3); }
.nav-item:hover { background: var(--bg-surface); color: var(--text-primary); }
.nav-item[aria-current="page"]:hover { background: var(--accent); color: var(--on-accent); }

/* The admin group is always visible, so members know the console exists, but
 * inert for them: dimmed and not focusable. It tells them where the boundary
 * is without implying they may cross it. */
.nav-item[data-inert="true"] {
    opacity: .38;
    cursor: default;
    pointer-events: none;
}

.nav-group { display: flex; align-items: center; gap: 7px; }
.nav-group .dot { color: var(--accent); }

.shell-account {
    margin-top: auto;
    padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
}
.shell-account .avatar {
    width: 28px; height: 28px;
    border-radius: 50%;
    background: var(--border-strong);
    object-fit: cover;
    flex: none;
}
.shell-account .who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.shell-account .name {
    font-size: 13px;
    color: var(--text-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.shell-account .tier {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .08em;
    color: var(--accent);
    text-transform: uppercase;
}

.shell-account .sign-out { margin-left: auto; display: flex; }
.sign-out-button {
    /* 44px, per the handoff's floor for anything you tap. The icon inside is
       16px; the rest is target. */
    width: var(--tap-min);
    height: var(--tap-min);
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    background: none;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text-faint);
    cursor: pointer;
}
.sign-out-button:hover {
    color: var(--text-primary);
    background: var(--bg-surface);
}

.shell-top .page-title {
    font-family: var(--font-display);
    font-size: 19px;
    letter-spacing: .06em;
    color: var(--text-primary);
}
.shell-top .top-right { display: flex; align-items: center; gap: 18px; }

.bot-state {
    display: flex;
    align-items: center;
    gap: 7px;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .12em;
    color: var(--text-faint);
    white-space: nowrap;
}
.bot-state .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--state-ok); flex: none; }
.bot-state[data-online="false"] .dot { background: var(--state-danger); }

.shell-body {
    flex: 1;
    padding: 32px 40px;
    display: flex;
    flex-direction: column;
    gap: 26px;
    min-width: 0;
}

/* ------------------------------------------------------- Theme control --- */
/* Frame 5d. Two faction swatches plus an auto chip that follows the war. */

.theme-control {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .12em;
    color: var(--text-faint);
}
.theme-swatch {
    width: 18px; height: 18px;
    border-radius: 2px;
    border: 0;
    padding: 0;
    cursor: pointer;
}
/* Faction swatches are literal faction colour, not the current accent: they are
 * labels for "Warden" and "Colonial", and must read the same on every theme. */
.theme-swatch[data-theme="warden"]   { background: var(--faction-warden); }
.theme-swatch[data-theme="colonial"] { background: var(--faction-colonial); }
.theme-swatch[data-theme="auto"] {
    background: linear-gradient(135deg, var(--faction-warden) 50%, var(--faction-colonial) 50%);
}
.theme-swatch[aria-pressed="true"] {
    outline: 1px solid var(--accent);
    outline-offset: 1px;
}
.theme-control .current { color: var(--accent); }

/* ----------------------------------------------------------- War strip --- */

.war-strip {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    flex-wrap: wrap;
    padding: 20px 24px;
    background: linear-gradient(135deg, #141B22 0%, #10151A 100%);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.war-strip .about { display: flex; flex-direction: column; gap: 6px; }
.war-strip .state {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .2em;
    color: var(--accent);
}
.war-strip .headline {
    font-family: var(--font-display);
    font-size: 26px;
    letter-spacing: .02em;
    color: var(--text-primary);
}
.war-strip .detail { font-size: 14px; color: var(--text-muted); }
.war-strip .figures { display: flex; gap: 34px; }

/* The Top VC race, beside the member currently holding the role. */
.podium { display: flex; flex-direction: column; gap: var(--space-2); min-width: 280px; }
.podium ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.podium .key { display: flex; align-items: baseline; gap: var(--space-3); }
.podium .rank { min-width: 14px; }
.podium a { color: var(--text-body); }
.podium a:hover { color: var(--accent); }
/* Only the leader's time takes the accent — the whole point is who is ahead. */
.podium .value.leads { color: var(--accent); }
.podium .is-self a { color: var(--highlight-self); }

.figure { display: flex; flex-direction: column; gap: 4px; text-align: right; }
.figure .label {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .14em;
    color: var(--text-faint);
}
.figure .value {
    font-family: var(--font-display);
    font-size: 26px;
    color: var(--text-primary);
}

/* ----------------------------------------------------------- Tile grid --- */

.tile-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
}
.tile-grid.two-up { grid-template-columns: repeat(2, 1fr); }

.tile {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 150px;
    padding: 24px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: inherit;
}
a.tile:hover { border-color: var(--border-strong); color: inherit; }
.tile h2 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 21px;
    letter-spacing: .05em;
    color: var(--text-primary);
}
.tile p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--text-muted); }
/* Every tile ends in a live figure, never a generic "View". */
.tile .figure-line {
    margin-top: auto;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--accent);
}

.tile.quiet {
    background: color-mix(in oklch, var(--bg-base) 94%, white);
    min-height: 0;
    padding: 20px 24px;
}
.tile.quiet h2 { font-size: 18px; color: var(--text-body); }

/* --------------------------------------------------------------- Table --- */

.table-row, .table-head {
    display: grid;
    grid-template-columns: var(--table-cols, 1fr);
}
.table-row:hover { background: var(--bg-surface); }
.table a { color: var(--text-body); }
.table a:hover { color: var(--accent); }

/* Gold marks "you" on every theme, so a member can find their own row without
 * reading names. Semantic — never remapped to the accent. */
.table-row.is-self { background: var(--bg-raised); box-shadow: inset 2px 0 0 var(--highlight-self); }
.table-row.is-self .name { color: var(--highlight-self); }

/* Wide tables scroll rather than crushing their columns together; tokens.css
 * keeps overflow-y hidden so the rounded corners still clip the rows. */
.table { overflow-x: auto; }

/* Headers are data-driven labels ("Enemy Structure/Vehicle Damage"), so they
 * have to wrap inside narrow numeric columns instead of spilling into the
 * neighbouring column. Bottom-aligned so wrapped labels share a baseline. */
.table-head.table-row { align-items: end; }
.table-head .table-cell { line-height: 1.35; }
.table-head .cell-num {
    font-size: inherit;         /* a numeric header is a header first: 14px belongs to the figures */
    overflow-wrap: anywhere;    /* last resort for a label with no break point */
}

.sort-button {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    cursor: pointer;
    display: inline;            /* not flex: the arrow must follow the last word */
    text-transform: inherit;
}
.table-head [aria-sort="ascending"] .sort-button,
.table-head [aria-sort="descending"] .sort-button { color: var(--accent); }
.sort-button .arrow { font-size: 9px; opacity: .8; margin-left: 5px; }

.faction-warden   { color: var(--faction-warden); }
.faction-colonial { color: var(--faction-colonial); }
.state-ok     { color: var(--state-ok); }
.state-warn   { color: var(--state-warn); }
.state-danger { color: var(--state-danger); }

.rank {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-faint);
}
.member-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
.member-cell img { width: 24px; height: 24px; border-radius: 50%; flex: none; }
.member-cell .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------------------------------------------------- Page parts --- */

.page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-5);
    flex-wrap: wrap;
}
.page-head .lede { font-size: 14px; color: var(--text-muted); max-width: 70ch; }

.section { display: flex; flex-direction: column; gap: var(--space-5); }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
.toolbar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.spacer { flex: 1; }

.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 16px;
}
.stat {
    padding: 18px 20px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.stat .label {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .14em;
    color: var(--text-faint);
    text-transform: uppercase;
}
.stat .value { font-family: var(--font-display); font-size: 28px; color: var(--text-primary); }
.stat .note { font-size: 12px; color: var(--text-muted); }

/* ------------------------------------------------------ Service record --- */
/* Frame 2d. One identity card, then a 1.3/1 split: war history beside a
 * column of small cards. The point of the layout is density — headings are
 * mono strips inside each panel rather than page-level h2s, so the whole
 * record reads at a glance instead of scrolling past three wide tables. */

/* Layout only — the surface comes from .card-hero, which already carries 2d's
 * gradient. */
.record-head {
    display: flex;
    align-items: center;
    gap: var(--space-7);
    flex-wrap: wrap;
    padding: 26px 30px;
}
.record-head .avatar {
    width: 88px;
    height: 88px;
    border-radius: 50%;
    background: var(--border-strong);
    object-fit: cover;
    flex: none;
}
.record-head .about { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.record-head .standing {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .2em;
    color: var(--accent);
}
.record-head h1 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 34px;
    letter-spacing: .02em;
    color: var(--text-primary);
}
.record-head .service-line { font-size: 14px; color: var(--text-muted); }
.record-head .figures { margin-left: auto; display: flex; gap: 34px; }

.record-layout { display: grid; grid-template-columns: 1.3fr 1fr; gap: 20px; align-items: start; }
.record-side { display: flex; flex-direction: column; gap: 20px; }

/* A panel is a titled box. Two flavours: a table sits flush inside one, while
 * a list of rows gets .panel-body's padding. */
.panel {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.panel-head {
    margin: 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 14px 18px;
    background: var(--bg-raised);
    border-bottom: 1px solid var(--border);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .16em;
    color: var(--text-faint);
}
.panel-head .total { color: var(--text-body); }
.panel-body { padding: 20px 22px; display: flex; flex-direction: column; gap: var(--space-3); }
.panel-body .label {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .16em;
    color: var(--text-faint);
}
/* The panel already draws the frame; the table inside must not draw a second. */
.panel .table { border: 0; border-radius: 0; }
/* Headers set the width of the auto columns, so they must not wrap to do it. */
.panel .table-head { white-space: nowrap; }
.panel .empty { border: 0; border-radius: 0; }

/* Specialisations are .pill, the primitive tokens.css already defines at
 * exactly the size and colour 2d draws them. */
.pills { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Label left, figure right — the compact form the side cards use instead of
 * a two-column table. */
.kv { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
.kv .key { font-size: 14px; color: var(--text-body); }
.kv .value { font-family: var(--font-mono); font-size: 14px; color: var(--text-primary); }

/* ------------------------------------------------------ Activity stats --- */
/* Frame 4b. Three boards side by side rather than stacked full-width tables —
 * they are read against each other, not down. */

.board-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: start; }

.board { list-style: none; margin: 0; padding: 0; }
.board-row-item {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--space-3);
    padding: 13px 16px;
    border-bottom: 1px solid color-mix(in oklch, var(--border) 70%, black);
}
.board-row-item:last-child { border-bottom: 0; }
.board-row-item .rank { text-align: right; }
.board-row-item .name {
    color: var(--text-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.board-row-item .name:hover { color: var(--accent); }
.board-row-item .value { font-family: var(--font-mono); font-size: 14px; color: var(--text-primary); }
/* Gold marks the viewer's own row here as it does in every other table. */
.board-row-item.is-self { background: var(--bg-raised); box-shadow: inset 2px 0 0 var(--highlight-self); }
.board-row-item.is-self .name,
.board-row-item.is-self .rank,
.board-row-item.is-self .value { color: var(--highlight-self); }
/* The viewer's row can jump from 3rd to 9th. Say so, rather than letting the
 * numbers read as consecutive. */
.board-row-item.after-gap { border-top: 1px dashed var(--border-strong); }

/* Twelve weeks of voice time. Heights are a share of the busiest week — an
 * absolute scale is unreadable off a 60px bar, and the shape is the point. */
.chart {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    align-items: end;
    gap: var(--space-2);
    height: 120px;
}
.chart-slot { height: 100%; display: flex; align-items: flex-end; }
.chart-slot .bar {
    width: 100%;
    background: var(--border-strong);
    border-radius: 2px 2px 0 0;
    min-height: 2px;
}
.chart-slot.is-peak .bar { background: var(--accent); }
/* A week whose sessions were pruned is not a quiet week. Draw the absence. */
.chart-slot.unknown {
    align-items: stretch;
    border-bottom: 1px dashed var(--border-strong);
}
.chart-slot.unknown .bar { display: none; }

.chart-axis { display: flex; justify-content: space-between; gap: var(--space-4); }
.chart-axis .peak { color: var(--accent); }

/* --------------------------------------------------------- Empty state --- */
/* Says what to do. Never just "No data". */

.empty {
    padding: 40px 24px;
    text-align: center;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    align-items: center;
}
.empty h3 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 19px;
    letter-spacing: .04em;
    color: var(--text-body);
}
.empty p { margin: 0; font-size: 14px; color: var(--text-muted); max-width: 52ch; }

/* --------------------------------------------------------- Event cards --- */

.event-list { display: flex; flex-direction: column; gap: 12px; }
.event {
    display: flex;
    align-items: stretch;
    gap: var(--space-5);
    padding: var(--space-5);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.event .date-block {
    flex: none;
    width: 64px;
    text-align: center;
    padding: var(--space-3) 0;
    background: var(--bg-raised);
    border-radius: var(--radius-sm);
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
}
.event .date-block .month {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .14em;
    color: var(--accent);
    text-transform: uppercase;
}
.event .date-block .day { font-family: var(--font-display); font-size: 24px; color: var(--text-primary); }
.event .body { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.event .body h3 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 19px;
    letter-spacing: .03em;
    color: var(--text-primary);
}
.event .body p { margin: 0; font-size: 14px; color: var(--text-muted); }
.event .meta {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: .1em;
    color: var(--text-faint);
}
.event .action { flex: none; display: flex; align-items: center; }
.event.is-ended { opacity: .6; }

/* ------------------------------------------------------------ Settings --- */
/* Frames 1c and 3a-3f: a category rail beside a detail pane.
 *
 * The frame is one region, not two boxes with air between them: the rail is a
 * full-height column separated from the pane by a single rule, and the pane
 * runs to the edges of the content area. That is why this page sheds the shell
 * body's padding -- the header's bottom rule and the rail's right rule are
 * meant to reach the sides of the screen, the way they do under the topbar in
 * 3a. Boxing the pane in a card puts a second border a few pixels inside the
 * first and turns the console into a widget sitting on a page. */

.shell-body:has(> .settings-layout) { padding: 0; gap: 0; }

.settings-layout {
    display: grid;
    grid-template-columns: var(--settings-rail) 1fr;
    gap: 0;
    align-items: stretch;
    /* The rail's rule runs the height of the content area even when the
       category holds three settings, as War Tracker does. */
    min-height: calc(100vh - var(--shell-topbar));
}

.settings-rail {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 16px var(--space-3);
    border-right: 1px solid var(--border);
}
.settings-rail > .t-label { padding: 6px 10px 8px; }

.rail-item {
    min-height: var(--tap-min);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 0 var(--space-3);
    /* Transparent on the inactive items so selecting one moves the accent in,
       not the label sideways. */
    border-left: 2px solid transparent;
    border-radius: var(--radius-sm);
    font-size: 14px;
    color: var(--text-secondary);
}
.rail-item:hover { background: var(--bg-surface); color: var(--text-primary); }
.rail-item[aria-current="page"] {
    background: var(--bg-raised);
    color: var(--text-primary);
    border-left-color: var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.rail-item .count {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-dim);
}
/* The count on the open category takes the accent too, so the rail reads as
   one selection rather than a highlighted row with a stray grey number. */
.rail-item[aria-current="page"] .count { color: var(--accent); }

/* Frame 3a: each setting is its own card, not a row in a ruled list. The
   separation matters because a setting is an independent thing you change and
   get told about -- the confirmation and any error belong to one card. */
.settings-detail { display: flex; flex-direction: column; min-width: 0; }
.settings-header {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: 26px 28px 18px;
    border-bottom: 1px solid var(--border);
}
.settings-header h1 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 26px;
    letter-spacing: .03em;
    color: var(--text-primary);
}
.settings-header .description { font-size: 14px; line-height: 1.6; color: var(--text-muted); }
/* Every settings page names its Discord equivalent, so an admin can find the
   same control in chat rather than wondering whether the two agree. */
.settings-header .equivalent {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-faint);
    padding-top: 4px;
}

.setting-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: 22px 28px;
}
.setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    padding: 15px 16px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.setting-row .about { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.setting-row .label { font-size: 14px; color: var(--text-body); }
.setting-row .description { font-size: 13px; color: var(--text-faint); max-width: 60ch; }
.setting-row .control { flex: none; display: flex; align-items: center; gap: var(--space-3); }

/* A logging or event toggle stays disabled until its channel is set, because
   the bot refuses to enable it otherwise. Without this the control would flip,
   the write would fail, and the row would revert for no visible reason. */
.setting-row[data-locked="true"] .label { color: var(--text-muted); }
.setting-row[data-locked="true"] .description { color: var(--text-dim); }

/* Frames 1c and 3a: a channel or role reads as a pill -- "# leaderboard" the
   width of its own name -- not as a form field. .pill supplies the surface and
   the wrapper draws the chevron. The 44px floor stays on whatever you tap. */
.picker {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 0;
    max-width: min(340px, 40vw);
}
.picker select,
.picker-trigger {
    appearance: none;
    -webkit-appearance: none;
    min-height: var(--tap-min);
    /* Room on the right for the chevron. */
    padding: 0 30px 0 12px;
    max-width: 100%;
    font: inherit;
    font-size: 13px;
    color: inherit;
    text-align: left;
    /* Opaque, and exactly the pill it sits in: Chrome paints the open list from
       the select's own background colour, and a transparent one comes out white
       -- with near-white option text on it. */
    background-color: inherit;
    background-image: none;
    border: 0;
    border-radius: inherit;
    cursor: pointer;
}
/* The native list is the browser's, but it honours these two, so a channel is
   legible whether or not the pill it opened from was an unset one. This is what
   an admin with JavaScript off sees. */
.picker option {
    background-color: var(--bg-surface);
    color: var(--text-primary);
}
/* No outline suppression anywhere here: the control keeps the site-wide focus
   ring, which is the only thing telling a keyboard user where they are. */
.picker:focus-within { border-color: var(--accent); }
/* Same size and colour as the value beside it, as in 1c -- and because the
   colour is inherited, an unset picker dims its chevron along with its text. */
.picker::after {
    content: "▾";
    position: absolute;
    top: 50%;
    right: 11px;
    transform: translateY(-50%);
    font-size: 13px;
    color: inherit;
    pointer-events: none;
}

/* A guild has dozens of channels, and a native select only jumps to the first
 * item starting with what you typed -- no way to reach "# war-log" by typing
 * "war". So once JavaScript is running the select is swapped for a listbox that
 * filters as you type. The select stays in the DOM as the value and as the
 * fallback, which is why it is hidden here rather than removed. */
.picker[data-enhanced] > select { display: none; }
.picker-value {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.picker-pop {
    position: absolute;
    z-index: 30;
    top: calc(100% + 5px);
    right: -1px;
    display: flex;
    flex-direction: column;
    /* Wider than the pill: the pill is only as wide as the channel it holds,
       which is usually narrower than the longest one in the list. */
    min-width: max(100%, 260px);
    max-width: min(360px, 88vw);
    background: var(--bg-surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: 0 12px 28px rgb(0 0 0 / .5);
}
.picker-pop[hidden] { display: none; }
/* Flipped above the pill when the list would run off the bottom of the window;
   the last setting in a category is often the one being changed. */
.picker-pop[data-drop="up"] { top: auto; bottom: calc(100% + 5px); }
/* The filter takes focus the moment the list opens, so the site-wide ring would
   be drawn around it every single time, doubled up against the panel edge a few
   pixels away. The panel border carries the focus instead -- still one clear
   accent outline, and the active row is marked as well. */
.picker-pop:focus-within { border-color: var(--accent); }
.picker-filter:focus-visible { outline: none; }

.picker-filter {
    min-height: var(--tap-min);
    padding: 0 12px;
    font: inherit;
    font-size: 13px;
    color: var(--text-primary);
    background: none;
    border: 0;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
}
.picker-filter::placeholder { color: var(--text-dim); }

.picker-list {
    list-style: none;
    margin: 0;
    padding: 4px;
    max-height: 264px;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.picker-list li {
    min-height: var(--tap-min);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0 10px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text-body);
    cursor: pointer;
    /* A channel name is one line; a long one is cut, not wrapped, so the row
       heights stay even enough to arrow through. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.picker-list li[hidden] { display: none; }
/* Two separate states: which one the keyboard is on, and which one is saved.
   They are usually the same row and must still be distinguishable when the
   keyboard has moved off the saved one. */
.picker-list li[data-active="true"] { background: var(--bg-raised); color: var(--text-primary); }
.picker-list li[aria-selected="true"] { color: var(--accent); }

.picker-none {
    margin: 0;
    padding: var(--space-4) 12px;
    font-size: 13px;
    color: var(--text-faint);
}
.picker-none[hidden] { display: none; }

.input {
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    font: inherit;
    font-size: 14px;
    color: var(--text-primary);
    background: var(--bg-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
}
.input:focus { border-color: var(--accent); outline: none; }
textarea.input { padding: var(--space-3); min-height: 88px; resize: vertical; line-height: 1.6; }
select.input { cursor: pointer; }

/* Saving is instant on change, with the confirmation on the row that changed. */
.save-state {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .12em;
    white-space: nowrap;
    opacity: 0;
    transition: opacity .15s;
}
.save-state[data-state="saving"] { opacity: 1; color: var(--text-faint); }
.save-state[data-state="saved"]  { opacity: 1; color: var(--state-ok); }
.save-state[data-state="error"]  { opacity: 1; color: var(--state-danger); }
/* Out of the flow entirely until it has something to say, so it does not hold
   a third slot open in a row the phone lays out as label / control. */
.save-state:empty { display: none; }

/* ------------------------------------------------ Tickets & transcripts --- */
/* Frame 4f. Master/detail: a rail of tickets, one transcript beside it.
 *
 * Close cousin of .settings-layout, but the rail here is a list of records
 * rather than of destinations, so each entry carries two lines and the whole
 * thing scrolls independently of the transcript. */

.ticket-layout {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 0;
    align-items: stretch;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    /* Frame 4f is a fixed panel with its two halves scrolling inside it, and a
       transcript is unbounded — a hundred messages would otherwise push the
       rail, the page head and the whole shell down with it. Subtracted: the
       topbar, the body's padding, the page head and its gap. */
    height: calc(100vh - var(--shell-topbar) - 190px);
    min-height: 440px;
}

.ticket-rail {
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--border);
    min-width: 0;
    overflow-y: auto;
}
/* Sticky, so the group a ticket belongs to is still named once the list has
   scrolled past its heading. */
.ticket-rail .rail-heading {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border);
    background: var(--bg-raised);
}
.ticket-rail .rail-heading .clear { font-size: 10px; letter-spacing: .12em; }

/* Also sticky, and above the headings: a search box you have to scroll back up
   to reach is a search box you stop using. */
.ticket-search {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
    background: var(--bg-surface);
}
.ticket-search .input { flex: 1; min-width: 0; }
.ticket-search .btn { flex: none; padding: 0 var(--space-3); }
.ticket-rail .rail-none {
    margin: 0;
    padding: var(--space-4) var(--space-5);
    font-size: 13px;
    color: var(--text-dim);
}

.ticket-entry {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border);
    /* The left edge is where the current entry is marked, so every entry
       reserves the width — otherwise selecting one shifts the whole rail. */
    border-left: 2px solid transparent;
    color: var(--text-body);
    min-width: 0;
}
.ticket-entry:hover { background: var(--bg-raised); color: var(--text-body); }
.ticket-entry.is-current {
    background: var(--bg-raised);
    border-left-color: var(--accent);
}
.ticket-entry .line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.ticket-entry .name {
    font-size: 14px;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ticket-entry .ref { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); flex: none; }
.ticket-entry.is-current .ref { color: var(--accent); }
.ticket-entry .sub { font-size: 13px; color: var(--text-muted); }

.rail-pager {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-5);
}

.ticket-detail { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ticket-detail-head { flex: none; }
.ticket-detail > .empty { border: 0; border-radius: 0; margin: auto; }

.ticket-detail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    padding: var(--space-6) 26px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.ticket-who { display: flex; align-items: center; gap: var(--space-4); min-width: 0; }
.ticket-who .avatar {
    width: 44px; height: 44px;
    border-radius: 50%;
    background: var(--border-strong);
    object-fit: cover;
    flex: none;
}
.ticket-who .stack { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }

.ticket-transcript {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    padding: var(--space-6) 26px;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
}
.ticket-transcript > .t-label { margin: 0; }

.messages { display: flex; flex-direction: column; gap: var(--space-5); }
.message { display: flex; gap: var(--space-3); min-width: 0; }
.message .avatar {
    width: 30px; height: 30px;
    border-radius: 50%;
    background: var(--border-strong);
    object-fit: cover;
    flex: none;
}
.message .said { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.message .byline { margin: 0; display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.message .author { font-size: 14px; color: var(--text-primary); }
/* The bot's own messages are the scaffolding of a ticket, not what anyone came
   to read. Accent marks them so the eye can skip them. */
.message .author.is-bot { color: var(--accent); }
.message .body {
    margin: 0;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
    /* Member-authored: it can be one very long unbroken string. */
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* A resolved mention. Discord tints these, and the tint is what tells you at a
   glance that the word is a person or a place rather than part of the sentence. */
.mention {
    color: var(--accent);
    background: color-mix(in oklch, var(--accent) 14%, transparent);
    border-radius: var(--radius-sm);
    padding: 0 3px;
    white-space: nowrap;
}
/* Nothing to point at any more — the member left, or the role was deleted. Said
   quietly, so it reads as missing rather than as a live link. */
.mention[data-kind="emoji"],
.mention[data-kind="time"] { color: var(--text-body); background: none; padding: 0; }
.mention[title] { color: var(--text-muted); background: none; border-bottom: 1px dotted var(--border-strong); }

.message .embed {
    margin-top: var(--space-1);
    padding: var(--space-3) var(--space-4);
    border-left: 2px solid var(--border-strong);
    background: var(--bg-raised);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.message .embed-title { margin: 0; font-size: 14px; color: var(--text-primary); }
.message .embed-field { margin: 0; font-size: 13px; }
.message .embed-field .k { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; color: var(--text-faint); }
.message .embed-field .v { color: var(--text-secondary); }

.attachments { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-2); }
.attachments .shot {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    line-height: 0;
}
/* Verification screenshots are full-size game captures. Capped so one does not
   push the rest of the transcript off the screen; the link opens the original. */
.attachments .shot img { max-width: 280px; max-height: 220px; object-fit: cover; }
.attachments .file {
    font-family: var(--font-mono);
    font-size: 12px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow-wrap: anywhere;
}
.attachments .file.is-gone { color: var(--text-dim); border-style: dashed; }

.ticket-note {
    margin: auto 0 0;
    padding: 13px 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-base);
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
}

/* ------------------------------------------------------------- Landing --- */
/* Frame 1b: cinematic hero, art full-bleed, mono top bar.
 *
 * The frame is a 1280px artboard and its measurements are taken literally --
 * 74px bar, 34px band, 680px hero, 44px gutters, 88px section padding. What is
 * not literal is the width: the artboard is centred in a max-width container so
 * the design holds its proportions on a wide monitor instead of stretching.
 *
 * The whole page is centre-aligned. That is the frame's most load-bearing
 * decision and the easiest one to lose, because every other page in the design
 * is left-aligned and the instinct is to match those instead. */

.landing {
    --landing-bar-h: 74px;
    --landing-band-h: 34px;
    --landing-gutter: 44px;
    /* The artboard is 1280px. That is the width of the *content*, not of the
       page: the handoff calls for the hero art to be full-bleed, so the page
       runs edge to edge and each row centres its own content inside this. */
    --landing-measure: 1280px;

    background: var(--bg-base);
}

/* Two ways of holding the artboard width, depending on whether the row draws
   anything of its own.
 *
 * Rows that are just text centre a capped block. Rows that carry a border or a
 * background -- the bar, the war band, the footer, the banded section -- must
 * run edge to edge or the rule stops short of the screen edge and the page
 * reads as a box sitting on a background. Those grow a gutter instead, big
 * enough to bring their content back to the measure. */
.landing-section,
.pillars,
.joining {
    max-width: var(--landing-measure);
    margin-inline: auto;
}

.landing-bar,
.war-band,
.site-footer,
.landing-section.alt {
    padding-inline: max(
        var(--landing-gutter),
        calc((100% - var(--landing-measure)) / 2 + var(--landing-gutter))
    );
}

.landing-bar {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    height: var(--landing-bar-h);
}
.landing-bar .brand { display: flex; align-items: center; gap: 14px; }
.landing-bar .brand img { width: 34px; height: 34px; object-fit: contain; }
.landing-bar .brand span {
    font-family: var(--font-display);
    font-size: 18px;
    letter-spacing: .24em;
    color: var(--text-primary);
}
.landing-bar nav {
    display: flex;
    align-items: center;
    gap: 34px;
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: .12em;
    color: var(--text-body);
    text-transform: uppercase;
}
.landing-bar nav a { color: inherit; }
.landing-bar nav a:hover { color: var(--text-primary); }
/* The sign-in control reads as a button without being a filled one, so it does
   not compete with the two calls to action in the hero below it. */
.landing-bar nav a.sign-in {
    color: var(--text-primary);
    border: 1px solid color-mix(in srgb, var(--text-body) 32%, transparent);
    border-radius: var(--radius-sm);
    padding: 10px 18px;
    letter-spacing: .1em;
    backdrop-filter: blur(4px);
}

/* A 34px band, hairline-ruled, with the current war at one end and the last one
   at the other. Off-season shows the last war rather than hiding: an empty band
   tells a visitor nothing about whether the regiment is active. */
.war-band {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    /* Centred, not justified apart. The frame pushes the two halves to the
       edges of a 1280px artboard; across a full-width page that leaves them in
       opposite corners reading as two unrelated labels rather than one status. */
    justify-content: center;
    text-align: center;
    gap: var(--space-5);
    height: var(--landing-band-h);
    border-top: 1px solid color-mix(in srgb, var(--text-body) 9%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--text-body) 9%, transparent);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: .14em;
    color: var(--text-secondary);
}

/* The hero is pulled up behind the bar and the band, which is what makes the
   art run to the very top of the page while the navigation still sits on it.
   The offset is derived rather than written as -108px so it stays correct when
   those two heights change at a breakpoint. */
.hero {
    position: relative;
    /* Full-bleed: the art is the point of this page, and boxing it at the
       content width is what makes a cinematic hero look like a panel. */
    max-width: none;
    height: 680px;
    margin-top: calc(-1 * (var(--landing-bar-h) + var(--landing-band-h)));
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.hero .art {
    position: absolute;
    inset: 0;
    background-image: url('/img/storm.jpg');
    background-image: image-set(url('/img/storm.webp') type('image/webp'),
                                url('/img/storm.jpg') type('image/jpeg'));
    /* The frame zooms to 150%, which works on a 1280px artboard. Across a full
       viewport that asks a 1024px-wide source to cover up to 3840px. `cover`
       keeps the upscale to the minimum the layout actually needs, and the
       framing survives because the scrim hides most of the image anyway. */
    background-size: cover;
    background-position: center 82%;
    /* Desaturated so the gold and the type stay the brightest things on screen. */
    filter: saturate(.65) contrast(1.05);
}
/* Heavier at both ends than in the middle: the top has to carry the nav, the
   bottom has to dissolve into the section beneath it. */
.hero .scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        180deg,
        color-mix(in srgb, var(--bg-base) 86%, transparent) 0%,
        color-mix(in srgb, var(--bg-base) 55%, transparent) 38%,
        color-mix(in srgb, var(--bg-base) 92%, transparent) 82%,
        var(--bg-base) 100%
    );
}
.hero .copy {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 26px;
    text-align: center;
    /* Nudges the block below the optical centre, clear of the nav above it. */
    padding-top: 60px;
    padding-inline: var(--landing-gutter);
    max-width: var(--landing-measure);
}
.hero .crest {
    width: 180px;
    height: 180px;
    object-fit: contain;
    filter: drop-shadow(0 18px 46px rgb(0 0 0 / .75));
}
.hero h1 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 76px;
    line-height: 1;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--text-primary);
    text-shadow: 0 6px 30px rgb(0 0 0 / .7);
}
.hero p {
    margin: 0;
    font-size: 19px;
    line-height: 1.6;
    color: var(--text-body);
    max-width: 620px;
    text-wrap: pretty;
}
.hero .actions { display: flex; gap: 14px; padding-top: 12px; flex-wrap: wrap; justify-content: center; }
.hero .actions .btn { padding: 16px 30px; min-height: 0; }
.hero .actions .btn-ghost { backdrop-filter: blur(4px); }

/* Prose sections ------------------------------------------------------ */

.landing-section {
    padding: 88px var(--landing-gutter);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 22px;
    text-align: center;
}
.landing-section h2 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 38px;
    letter-spacing: .02em;
    color: var(--text-primary);
}
.landing-section p {
    margin: 0;
    font-size: 18px;
    line-height: 1.75;
    color: var(--text-secondary);
    max-width: 780px;
    text-wrap: pretty;
}

/* The band that breaks up the run of prose. */
/* The band bleeds; its text does not. The rule above would centre the whole
   element and leave the background stopping short of the edges, so the banded
   section opts out of the measure and caps its own children instead. */
.landing-section.alt {
    max-width: none;
    margin-inline: 0;
    padding-block: 80px;
    gap: 18px;
    background: var(--bg-surface);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.landing-section.alt > * { max-width: min(var(--landing-measure), 100%); }
.landing-section.alt h2 { font-size: 40px; }
.landing-section.alt p { max-width: 720px; }

/* What we do ---------------------------------------------------------- */

.pillars {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    padding: 0 var(--landing-gutter) 88px;
}
.pillar {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 34px 30px;
    background: linear-gradient(180deg, var(--bg-surface) 0%, var(--bg-base) 100%);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.pillar h3 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 22px;
    letter-spacing: .05em;
    color: var(--text-primary);
}
.pillar p {
    margin: 0;
    font-size: 15px;
    line-height: 1.7;
    color: var(--text-muted);
    text-wrap: pretty;
}

/* How to join --------------------------------------------------------- */

.joining {
    padding: 88px var(--landing-gutter);
    display: flex;
    flex-direction: column;
    gap: 40px;
}
.joining > h2 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 32px;
    letter-spacing: .03em;
    color: var(--text-primary);
    text-align: center;
}
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 44px; }
.step {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
}
.step .number {
    width: 52px;
    height: 52px;
    flex: none;
    border-radius: 50%;
    border: 1px solid var(--accent);
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: 17px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.step h3 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 20px;
    letter-spacing: .05em;
    color: var(--text-primary);
}
.step p {
    margin: 0;
    font-size: 15px;
    line-height: 1.7;
    color: var(--text-muted);
    text-wrap: pretty;
}

.site-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    flex-wrap: wrap;
    padding-block: 34px;
    border-top: 1px solid var(--border);
    font-size: 13px;
    color: var(--text-faint);
}
.site-footer nav { display: flex; gap: 26px; }
.site-footer a { color: inherit; }
.site-footer a:hover { color: var(--accent); }

/* Landing, narrow ----------------------------------------------------- */

@media (max-width: 900px) {
    .pillars, .steps { grid-template-columns: 1fr; }
    .steps { gap: 32px; }
    .hero h1 { font-size: 52px; }
    /* The side cards stop being a side and stack under the war history. */
    .record-layout { grid-template-columns: 1fr; }
    /* Three boards do not fit side by side; two do. */
    .board-row { grid-template-columns: repeat(2, 1fr); }
    /* The rail stops being a side: it becomes the list you scroll past to
       reach the transcript, capped so it does not become the whole screen.
       The panes stop scrolling independently too — two scroll areas inside a
       phone-height page is a worse way to read than one page that scrolls. */
    .ticket-layout { grid-template-columns: 1fr; height: auto; min-height: 0; }
    .ticket-rail { border-right: 0; border-bottom: 1px solid var(--border); max-height: 320px; }
    .ticket-transcript { overflow-y: visible; }
}

@media (max-width: 640px) {
    .landing {
        --landing-bar-h: 56px;
        --landing-band-h: auto;
        --landing-gutter: 18px;
    }

    .landing-bar nav { gap: var(--space-4); font-size: 10px; }
    .landing-bar nav a.sign-in { padding: 8px 12px; }
    .landing-bar .brand img { width: 26px; height: 26px; }
    .landing-bar .brand span { font-size: 15px; letter-spacing: .2em; }

    /* Stacked, so neither half of the war status is truncated. */
    .war-band {
        height: auto;
        min-height: var(--tap-min);
        justify-content: center;
        padding-block: var(--space-2);
        font-size: 10px;
        /* One line that wraps, rather than being truncated. */
        line-height: 1.5;
    }

    /* The band is no longer a fixed height, so the hero can only be pulled up
       behind the bar. Tucking it under a variable-height band would clip it. */
    .hero {
        height: 520px;
        margin-top: calc(-1 * var(--landing-bar-h));
    }
    .hero .art { background-size: cover; }
    .hero .copy { gap: var(--space-5); padding-top: var(--space-6); }
    .hero .crest { width: 96px; height: 96px; }
    .hero p { font-size: 16px; }
    .hero .actions { width: 100%; }
    .hero .actions .btn { width: 100%; }

    .landing-section { padding: 48px var(--landing-gutter); }
    .landing-section h2, .landing-section.alt h2 { font-size: 26px; }
    .landing-section p { font-size: 16px; }
    .pillars { padding-bottom: 48px; }
    .joining { padding: 48px var(--landing-gutter); gap: 28px; }
    .joining > h2 { font-size: 24px; }
    .site-footer { padding: 24px var(--landing-gutter); flex-direction: column; align-items: flex-start; }
}

/* --------------------------------------------------------- Notice page --- */
/* Frames 2f and friends: signed in, but nothing to show yet. */

.notice {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-6);
}
.notice .panel {
    max-width: 560px;
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    padding: var(--space-8);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    text-align: center;
    align-items: center;
}
.notice .panel img { width: 56px; height: 56px; object-fit: contain; }
.notice .panel p { margin: 0; font-size: 15px; line-height: 1.65; color: var(--text-body); }
.notice .panel ol {
    margin: 0;
    padding-left: 1.2em;
    text-align: left;
    color: var(--text-body);
    font-size: 15px;
    line-height: 1.8;
}

/* ------------------------------------------------------------- Mobile --- */

.drawer-toggle,
.tabbar { display: none; }

@media (max-width: 640px) {
    .shell-body { padding: 20px 18px; gap: 20px; }

    .drawer-toggle {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 5px;
        width: var(--tap-min);
        height: var(--tap-min);
        background: none;
        border: 0;
        cursor: pointer;
    }
    .drawer-toggle span {
        width: 20px; height: 1.5px;
        background: var(--text-primary);
        display: block;
    }

    /* The drawer's nav list scrolls; the account footer stays pinned. */
    .shell-nav { z-index: 40; }
    .shell-nav .nav-list { flex: 1; min-height: 0; overflow-y: auto; }
    .shell-nav .shell-account { margin-top: 0; }

    .drawer-scrim {
        position: fixed;
        inset: 0;
        z-index: 39;
        background: rgb(0 0 0 / .55);
    }

    .tabbar { display: grid; z-index: 30; }
    .tabbar a {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 4px;
        min-height: var(--tap-min);
        font-family: var(--font-mono);
        font-size: 9px;
        letter-spacing: .1em;
        color: var(--text-muted);
        text-transform: uppercase;
    }
    .tabbar a[aria-current="page"] { color: var(--accent); }

    .tile-grid,
    .tile-grid.two-up { grid-template-columns: repeat(2, 1fr); }
    .steps { grid-template-columns: 1fr; }

    .war-strip { flex-direction: column; align-items: flex-start; gap: var(--space-5); }
    /* Rows become cards on mobile; give them room inside the panel's frame
     * rather than letting them sit flush against it. */
    .panel .table { padding: 12px; }
    .panel .table-row:last-child { margin-bottom: 0; }

    .board-row { grid-template-columns: 1fr; }
    .chart { height: 90px; }

    .record-head { gap: var(--space-5); padding: 20px; }
    .record-head .avatar { width: 64px; height: 64px; }
    .record-head h1 { font-size: 28px; }
    /* The figures drop below the name rather than clinging to the right edge. */
    .record-head .figures { margin-left: 0; gap: var(--space-6); }
    .record-head .figure { text-align: left; }
    .war-strip .figures { gap: var(--space-6); }
    .figure { text-align: left; }

    .hero { min-height: 460px; padding: 24px 18px 32px; }
    .hero .actions .btn { width: 100%; }
    .landing-section { padding: 32px 18px; }
    .site-footer { padding: 24px 18px; flex-direction: column; }

    /* Tables become stacked cards: rank, name, the sorted stat large, two
     * secondary stats beneath. The grid is abandoned for a flex column. */
    /* Cards carry no column header. It has to be hidden here rather than left
     * to tokens.css: this sheet loads last and its base rule would win. The
     * head row carries both classes, so the card rule excludes it by name. */
    .table-head { display: none; }
    .table-row:not(.table-head) { display: flex; }
    .table-row { flex-wrap: wrap; }
    .table-cell { padding: 0; }
    .table-row .secondary {
        flex-basis: 100%;
        display: flex;
        gap: var(--space-5);
        font-family: var(--font-mono);
        font-size: 11px;
        color: var(--text-faint);
    }
    .table-row .sorted-stat {
        margin-left: auto;
        font-family: var(--font-display);
        font-size: 21px;
        color: var(--text-primary);
    }

    /* Rail above the pane rather than beside it: the rule it is separated by
       turns from a right border into a bottom one, and the accent that marked
       the open category on its left marks it underneath. */
    .settings-layout { grid-template-columns: 1fr; min-height: 0; }
    .settings-rail {
        flex-direction: row;
        overflow-x: auto;
        gap: var(--space-2);
        padding: var(--space-3) 18px;
        border-right: 0;
        border-bottom: 1px solid var(--border);
    }
    /* The section heading has nowhere to sit in a horizontal strip. */
    .settings-rail > .t-label { display: none; }
    .rail-item {
        flex: none;
        padding: 0 var(--space-4);
        border-left: 0;
        border-bottom: 2px solid transparent;
    }
    .rail-item[aria-current="page"] {
        border-bottom-color: var(--accent);
        border-radius: var(--radius-sm);
    }

    .settings-header { padding: 20px 18px 16px; }
    .setting-list { padding: 18px; }
    .setting-row { flex-direction: column; align-items: stretch; gap: var(--space-3); }
    .setting-row .control { justify-content: space-between; }
    /* Full width, because on a phone the control is the row. */
    .picker { max-width: none; flex: 1; }
    .picker select { width: 100%; }

    .event { flex-wrap: wrap; }
    .event .action { flex-basis: 100%; }
    .event .action .btn { width: 100%; }
}

@media (max-width: 400px) {
    .tile-grid, .tile-grid.two-up { grid-template-columns: 1fr; }
}

/* Respect a reduced-motion preference; the only motion here is the drawer. */
@media (prefers-reduced-motion: reduce) {
    * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* Printing a roster or a war history is a real thing people do. */
@media print {
    .shell-nav, .shell-top, .tabbar, .landing-bar { display: none; }
    .shell-body { padding: 0; }
    body { background: #fff; color: #000; }
}
