/* Couchside Leagues target desktop layout (#828).
 *
 * The browser platform is the layout dependency: this file uses its grid, flex and overflow
 * primitives instead of adding a responsive framework. The one breakpoint contract is 768 CSS
 * pixels: below it this DESKTOP document presents its honest phone/unavailable posture; from
 * 768 through 1023 it is the compact desktop tier; at 1024 and above it is the full desktop tier.
 * The exact boundary is documented in frontend-desktop.md and asserted at 767/768/1023/1024 by
 * tests/browser/leagues-compact-desktop-geometry.spec.js. Routing does not branch on viewport:
 * the shared hash grammar remains the server-selected client's grammar at every width.
 */

:root {
  color-scheme: light dark;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --surface: Canvas;
  --text: CanvasText;
  --muted: color-mix(in srgb, CanvasText 68%, Canvas 32%);
  --border: color-mix(in srgb, CanvasText 22%, Canvas 78%);
  --accent: Highlight;
  --accent-text: HighlightText;
  --control-size: 44px;
  /* #1169: the one bordered-card radius this document uses. Before it, seven card idioms
   * agreed on .5rem by repetition and `.bracket-contest` quietly disagreed at .4rem -- four
   * radii across this file and mobile.css for one visual idea. A token makes the agreement
   * checkable and the disagreement impossible to reintroduce by copy-paste. Form CONTROLS are
   * a separate family and keep their own .4rem (see `.panel-form` below). */
  --card-radius: .5rem;
}

* { box-sizing: border-box; }

html, body { margin: 0; min-width: 0; overflow-x: hidden; }

body {
  min-height: 100dvh;
  padding: 1.5rem;
  background: var(--surface);
  color: var(--text);
  line-height: 1.45;
}

main { width: 100%; max-width: 76rem; margin: 0 auto; min-width: 0; }
header { margin-bottom: 1rem; }
/* #1622 fix round: the product-wide "Unofficial Fan Project" label
 * (shellChrome.renderUnofficialFanLabel), styled on the same tokens every other bordered chip on
 * this document uses -- Analysis's own `.unofficial-fan-label` (frontend/styles.css) is this
 * rule's reference, translated onto this document's own custom properties rather than copied. */
.unofficial-fan-label {
  margin: 0 0 .75rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  padding: .25rem .6rem;
  display: inline-block;
  cursor: help;
}
h1, h2, h3, p, li, dt, dd, span { min-width: 0; overflow-wrap: anywhere; }
h1 { margin: 0; font-size: 1.5rem; }
h2 { margin: 0 0 .5rem; font-size: 1.25rem; }
h3 { margin: 0 0 .5rem; font-size: 1.05rem; }

.screen-heading:focus-visible,
button:focus-visible,
select:focus-visible,
a:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

nav.screens {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 1rem 0;
  min-width: 0;
}

/* A standalone button or select is page chrome and shares the card radius; a control INSIDE a
 * form steps down to the tighter .4rem its own rule sets (`.panel-form`, `.creation-form`). */
button, select {
  min-width: var(--control-size);
  min-height: var(--control-size);
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
}

button { padding: .55rem .85rem; font-weight: 650; cursor: pointer; }
button.current, button.primary { border-color: var(--accent); background: var(--accent); color: var(--accent-text); }

.actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0; min-width: 0; }
.panel { display: grid; gap: .5rem; width: 100%; min-width: 0; margin: 1rem 0; }
.panel-row { margin: 0; }

/* Panels may acquire semantic standings/result tables without ever widening the document. The
 * panel, rather than the page, becomes the bounded horizontal reading region when a table's
 * headers genuinely need more room. */
.panel { overflow-x: auto; overscroll-behavior-inline: contain; }
.panel table { width: 100%; border-collapse: collapse; }
.panel th, .panel td { padding: .55rem; text-align: start; border-bottom: 1px solid var(--border); }
.result-heading { margin: .75rem 0 0; }
.result-meta { margin: 0; color: var(--muted); }
.result-table { min-width: 30rem; }

.lineup-editor-host { margin-top: 1rem; }
.lineup-editor { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; min-width: 0; }
.lineup-editor label { display: grid; gap: .25rem; min-width: 0; font-weight: 650; }
.lineup-editor select { width: 100%; padding: .45rem; }
.lineup-editor button, .lineup-status, .lineup-source { grid-column: 1 / -1; }
.lineup-status, .lineup-source { margin: 0; color: var(--muted); }

/* The commissioner/manager panel idiom, shared by the Pick'em pick form, the invite issuer, and
 * the bracket and phase control panels -- see mobile.css's own header comment for the full S1
 * rationale (`panel-card`/`panel-heading`/`panel-form`/`panel-status` emitted by the seven
 * `render*Form` call sites in `bracketControl.js`/`phaseControl.js` plus `panel-field` by the
 * shared `labelled()` primitive, collapsing each merged rule to one single-selector rule; pick's
 * own `pick-status` and invite's own `invite-status` family stay declared separately since
 * neither module's call sites set the shared classes; `pick-entry` itself DOES carry
 * `panel-form` since #1169). This file's own values (padding
 * .45rem/.85rem, the card/control radii, the two-column `grid-template-columns` this layout
 * tier adds) differ from mobile.css's throughout, but the SAME shape repeats here too. `min-width:
 * 0` is dropped from every `-host` selector (and `.lineup-editor-host` above) for the same reason:
 * review finding (d) measured it inert at every acceptance width. */
.invite-host, .bracket-host, .phase-host, .pick-form-host { margin-top: 1rem; }
.bracket-heading, .phase-heading, .pick-form-heading { margin: 0 0 .5rem; }
.panel-card {
  margin-top: .75rem;
  padding: .85rem;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
}
.panel-heading { margin: 0 0 .5rem; }
.panel-field { display: grid; gap: .25rem; min-width: 0; font-weight: 650; }
.panel-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .75rem;
  min-width: 0;
}
.panel-form select, .panel-form input, .panel-form textarea {
  width: 100%;
  min-height: var(--control-size);
  padding: .45rem;
  border: 1px solid var(--border);
  border-radius: .4rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
.panel-form button { grid-column: 1 / -1; }
.panel-status { margin: 0; color: var(--muted); }
/* `pick-entry` carries `panel-form` since #1169 (`pickForm.js`'s own `renderSeries`), so the
 * three rules that used to restate the shared grid, control sizing and full-width submit here
 * are gone with it -- and with them the .5rem/.4rem select divergence this comment used to
 * record as deliberate. */
.bracket-status, .phase-status, .pick-status { margin: 0; color: var(--muted); }
/* `.invite-note`/`.invite-expiry`/`.invite-status` are declared in the invite section below, NOT
 * merged here: this file's own real divergence is `.35rem 0` margin, not `0`. */

/* The Pick'em pick form's own shape, beyond the shared idiom above: one card per series. A
 * locked, decided or unscheduled card carries no control at all -- it is read-only honest state
 * rather than a disabled form. */
.pick-series {
  display: grid;
  gap: .4rem;
  min-width: 0;
  margin-bottom: .75rem;
  padding: .85rem;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
}
.pick-round { margin: 0; }
.pick-matchup, .pick-own, .pick-series-state, .pick-lock { margin: 0; overflow-wrap: anywhere; }
.pick-own, .pick-series-state, .pick-lock { color: var(--muted); }

/* The commissioner invite issuer's own shape. The link is a text node, never an anchor: a
 * capability must not become an attribute. It wraps inside the panel rather than widening it. */
.invite-heading { margin: 0 0 .35rem; }
.invite-note, .invite-expiry, .invite-status { margin: .35rem 0; color: var(--muted); }
.invite-link {
  margin: .5rem 0;
  padding: .6rem;
  min-width: 0;
  overflow-wrap: anywhere;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  background: var(--surface);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.invite-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0; }
.invite-actions button { min-height: var(--control-size); }

/* The commissioner bracket control panel's own shape (#819 round B): a read-only round/contest
 * list, then the slot/result/advance forms the shared idiom above builds. */
.bracket-reconfirm { margin: 0 0 .5rem; padding: .6rem; border: 1px solid var(--border); border-radius: var(--card-radius); color: var(--muted); }
.bracket-round { margin-bottom: .5rem; }
.bracket-round-label { margin: 0 0 .25rem; }
/* #1169: was .4rem -- the one card in this file that disagreed with every other card. */
.bracket-contest { margin-bottom: .35rem; padding: .5rem .6rem; border: 1px solid var(--border); border-radius: var(--card-radius); }
.bracket-matchup, .bracket-result { margin: 0; overflow-wrap: anywhere; }
.bracket-result { color: var(--muted); }
.bracket-slot-action, .bracket-result-action { grid-column: 1 / -1; margin: 0; color: var(--muted); }

/* The commissioner phase and lifecycle control panel's own shape (#819 round C): a read-only
 * round list -- each round's own effective window included -- then the declare/window/activate/
 * lifecycle forms the shared idiom above builds. */
.phase-lifecycle-summary { margin: 0 0 .35rem; font-weight: 650; }
.phase-round-summary { margin: 0 0 .35rem; overflow-wrap: anywhere; color: var(--muted); }
.phase-activate-target { margin: 0 0 .5rem; color: var(--muted); }
/* `font-weight: 400` dropped -- see mobile.css's own `.phase-hint` comment: inert, same sweep
 * discipline as `min-width: 0`. */
.phase-hint { grid-column: 1 / -1; margin: 0; color: var(--muted); }

ul.offerings { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: .75rem; margin: 0; padding: 0; list-style: none; }
ul.offerings li { display: grid; gap: .4rem; min-width: 0; padding: .85rem; border: 1px solid var(--border); border-radius: var(--card-radius); }
.offering-rosters, .offering-state, .hint, .notice { color: var(--muted); }

/* The real league-creation form (#1159, closing #819's own desktop half): built from
 * `panelJourney.js`'s shared render primitives exactly as the commissioner write forms are.
 * Laid out as its OWN single column rather than joining the flat bracket/phase/pick forms' shared
 * two-column `.panel-form` grid: unlike those, this form nests a whole rebuildable section
 * (`.creation-season-part`, `shellChrome.js`'s own `renderSeasonPart`) inside itself, and pairing
 * that div beside a single labelled field in a strict two-column grid would put unrelated controls
 * side by side for no reason -- the same "no unrequired control" discipline that keeps a
 * one-option season/round from becoming a `<select>` in the first place. */
.creation-form { display: grid; gap: .5rem; min-width: 0; margin-top: .5rem; }
.creation-season-part { display: grid; gap: .5rem; min-width: 0; }
.creation-season-fixed, .creation-phase-fixed { margin: 0; color: var(--muted); }
/* `.creation-field` covers a `decimal`/`choice`/`decimalList` configuration step's own wrapper
 * (`renderConfigStep`'s `decimalList`/`weights` branches) and its legend/label
 * (`.creation-field-name`) -- ported from `mobile.css`'s identical pair (fix round, review F1
 * fold-in e): this file declared the two classes in the comment above without ever styling them,
 * leaving Pick'em's `points_by_round` row and every fieldset legend unstyled on desktop. */
.creation-field { display: grid; gap: .25rem; font-weight: 650; min-width: 0; }
.creation-field-name { font-weight: 650; }
/* #1167: the published bound, shown beside its control. Muted and lighter than the field name it
 * follows -- it is what the server will accept, not a second label. */
.creation-field-hint { font-weight: 400; color: var(--muted); }
/* Scoped to a plain labelled control's own DIRECT input/select -- never the weighted-attribute
 * row's own checkbox/value pair below (`.creation-weight-selected`/`.creation-weight-value`),
 * which need their own, narrower sizing and would otherwise lose to this rule's higher specificity
 * (a class-plus-tag selector beats a bare class one). Fix round (review F1): this used to read
 * `.creation-form input`, which out-specified `.creation-weight-selected`'s `flex: 0 0 auto` with
 * an inherited `width: 100%` -- stretching every one of the 15 Gauntlet weight checkboxes to the
 * form's own full width and pushing the form past every desktop viewport (measured 714.8px at a
 * 768px viewport, 13px off-screen). `.creation-field > input` scopes to the same direct-child
 * shape `mobile.css`'s own rule already uses. */
.creation-form select, .creation-field > input {
  width: 100%;
  min-height: var(--control-size);
  padding: .45rem;
  border: 1px solid var(--border);
  border-radius: .4rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
fieldset.creation-field {
  margin: 0;
  padding: .6rem;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  font-weight: 400;
  min-width: 0;
}
.creation-weight-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  min-height: var(--control-size);
  font-weight: 400;
}
.creation-weight-selected { width: var(--control-size); height: var(--control-size); flex: 0 0 auto; }
.creation-weight-name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.creation-weight-value {
  flex: 0 1 6rem;
  min-width: 0;
  width: auto;
  min-height: var(--control-size);
  padding: .45rem;
  border: 1px solid var(--border);
  border-radius: .4rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
.creation-status { margin: .35rem 0; color: var(--muted); }

/* The floating league chat entry and room (#1245 round 1). Fixed, so it stays in the same corner
 * regardless of scroll and never widens or reflows the page underneath it -- the property the
 * geometry checks actually rely on: an in-flow, unpositioned host would shift every other
 * control's own measured box by however tall it happened to render.
 *
 * `.chat-panel` is appended to `.chat-host` only while open (`chatPanel.js`'s own header on why
 * it is never merely `hidden`) and removed on close, so a real accessibility/geometry check never
 * finds a "visible" control sitting at zero size behind a hidden ancestor. */
.chat-host { position: fixed; right: 1rem; bottom: 1rem; z-index: 40; }
.chat-toggle {
  position: relative;
  width: var(--control-size);
  height: var(--control-size);
  padding: 0;
  border-radius: 50%;
}
.chat-unread-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  background: var(--accent);
}
.chat-panel {
  position: fixed;
  right: 1rem;
  bottom: calc(var(--control-size) + 1.25rem);
  z-index: 41;
  display: flex;
  flex-direction: column;
  width: min(22rem, calc(100vw - 2rem));
  max-height: min(30rem, calc(100vh - 6rem));
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  background: var(--surface);
  color: var(--text);
}
.chat-panel-heading { margin: .75rem 2.75rem 0 .75rem; }
.chat-panel-close { position: absolute; top: .5rem; right: .5rem; width: var(--control-size); height: var(--control-size); }
.chat-first-unread { align-self: flex-start; margin: .35rem .75rem 0; }
.chat-first-unread-cancel { align-self: flex-start; margin: .25rem .75rem 0; }
.chat-first-unread-status { margin: .25rem .75rem 0; color: var(--muted); font-size: .85rem; }
.chat-scroll { flex: 1 1 auto; min-width: 0; margin-top: .5rem; padding: 0 .75rem .75rem; overflow-y: auto; }
.chat-scroll p { margin: .5rem 0; color: var(--muted); }
.chat-date { margin: .6rem 0 .3rem; text-align: center; color: var(--muted); font-size: .85rem; }
.chat-new_messages_divider {
  margin: .3rem 0;
  padding-top: .25rem;
  border-top: 1px solid var(--accent);
  text-align: center;
  color: var(--accent);
  font-weight: 650;
}
.chat-blocked_run, .chat-tombstone { margin: .3rem 0; color: var(--muted); font-style: italic; }
.chat-message {
  margin: .3rem 0;
  padding: .4rem .5rem;
  min-width: 0;
  overflow-wrap: anywhere;
  border-radius: var(--card-radius);
  background: color-mix(in srgb, CanvasText 6%, Canvas 94%);
}
.chat-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  margin-inline-end: .35rem;
  border-radius: 50%;
  /* The background is a per-sender computed hue (`chatRoom.js`'s own `color()`); a fixed light
   * text is the one property this token-driven stylesheet cannot derive from `var(--text)` and
   * still read against every hue -- the same "no `var()` reaches here" exception this file's own
   * `<meta name="theme-color">` allowlist documents for a different unreachable surface. */
  color: white;
  font-size: .7rem;
  font-weight: 650;
}
.chat-message strong { margin-inline-end: .35rem; }
.chat-sender-team, .chat-sender-team-choice {
  margin-inline-end: .35rem; padding: 0; border: 0; background: transparent;
  color: var(--accent); font: inherit; font-weight: 700; text-decoration: underline; cursor: pointer;
}
.chat-sender-team-choices { display: flex; flex-wrap: wrap; gap: .35rem; margin: .2rem 0; }
.chat-message .chat-sender-team-unavailable { display: inline; margin-inline-end: .35rem; }
.chat-message time { margin-inline-start: .35rem; color: var(--muted); font-size: .8rem; }
.chat-message blockquote {
  margin: .3rem 0 0;
  padding: .3rem .5rem;
  min-width: 0;
  overflow-wrap: anywhere;
  border-inline-start: 3px solid var(--border);
  color: var(--muted);
}
.chat-message small { display: block; margin-top: .25rem; color: var(--muted); }
/* Copy/Reply/React/Edit/Delete (#1245 rounds 2-3): small, quiet actions -- a real message never
 * competes with them for attention, so they sit compact and muted rather than as a second row of
 * primary buttons. */
.chat-message-actions { display: flex; gap: .35rem; margin-top: .25rem; }
.chat-message-copy, .chat-message-reply, .chat-message-react, .chat-message-edit, .chat-message-delete {
  min-width: 0;
  min-height: 1.75rem;
  padding: 0 .5rem;
  font-size: .8rem;
  color: var(--muted);
}
/* The quote's own jump target is a real button (every click-carrying control here is), styled to
 * read as the quoted text it wraps rather than as a boxed control -- the surrounding
 * `<blockquote>` still carries the visual quote treatment. */
.chat-quote-jump {
  display: block;
  width: 100%;
  min-height: 0;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  overflow-wrap: anywhere;
}
/* Reactions (#1245 round 3). Each existing reaction is its own tappable pill -- tapping shows who
 * reacted (`chatPanel.js`'s own `showReactionUsers`); a pill is display-only, never a toggle for
 * the viewer's own reaction (#1245 fix round B1/D1 -- `chatMutations.unreact` had no production
 * caller and is gone, so no `aria-pressed`/pressed-state styling exists either). `var(--card-
 * radius)` here, not a new literal -- see this file's own #1169 card-radius token. */
.chat-reactions { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .25rem; }
.chat-reaction-pill {
  min-height: 1.5rem;
  padding: 0 .4rem;
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  background: var(--surface);
  color: var(--text);
  font-size: .8rem;
}

/* The composer (#1245 round 2). DOM-present only for an unfrozen room (`chatPanel.js`'s own
 * header on why); its own `.chat-mention-select` is a native multi-select, never an inline
 * `@`-triggered autocomplete -- see that module's header for the build-vs-buy reasoning. */
.chat-composer { display: flex; flex-direction: column; gap: .4rem; padding: .6rem .75rem .75rem; border-top: 1px solid var(--border); }
.chat-mention-select { min-height: 3.5rem; padding: .3rem; border: 1px solid var(--border); border-radius: .4rem; background: var(--surface); color: var(--text); font: inherit; }
.chat-quote-draft { display: flex; align-items: start; gap: .5rem; padding: .4rem .5rem; border-inline-start: 3px solid var(--accent); background: color-mix(in srgb, CanvasText 6%, Canvas 94%); overflow-wrap: anywhere; }
.chat-quote-draft span { flex: 1 1 auto; min-width: 0; color: var(--muted); }
.chat-quote-cancel { min-height: 1.75rem; padding: 0 .5rem; font-size: .8rem; }
/* Editing (#1245 round 3) reuses this same composer -- see `chatPanel.js`'s own header -- so its
 * own draft banner mirrors the quote draft's look exactly; the two are mutually exclusive, never
 * mounted together. */
.chat-edit-draft { display: flex; align-items: center; gap: .5rem; padding: .4rem .5rem; border-inline-start: 3px solid var(--accent); background: color-mix(in srgb, CanvasText 6%, Canvas 94%); overflow-wrap: anywhere; }
.chat-edit-cancel { min-height: 1.75rem; padding: 0 .5rem; font-size: .8rem; }
.chat-composer-input {
  min-height: var(--control-size);
  padding: .45rem;
  border: 1px solid var(--border);
  border-radius: .4rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
.chat-composer-send { align-self: flex-end; }
.chat-composer-status { margin: 0; color: var(--muted); }
/* The reaction/composer emoji-entry seam (#1245 round 3 -> round 4): a plain text input plus
 * Add, the "Pick an emoji" trigger for the lazily-loaded vendored picker, its host, and Cancel --
 * see `chatPanel.js`'s own `buildEmojiEntry` header. Wraps because the loaded `<emoji-picker>` is
 * wider than one row; its own input still joins the SAME in-form-control `.4rem` family as
 * `.chat-composer-input`/`.chat-mention-select` above, not a new radius. */
.chat-emoji-entry { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; margin-top: .25rem; }
.chat-emoji-entry-input {
  width: 3.5rem;
  min-height: 1.75rem;
  padding: .2rem .3rem;
  border: 1px solid var(--border);
  border-radius: .4rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
.chat-emoji-entry-submit, .chat-emoji-entry-cancel, .chat-emoji-picker-open, .chat-composer-emoji {
  min-height: 1.75rem;
  padding: 0 .5rem;
  font-size: .8rem;
}
/* Its own row: the loaded picker sets its own internal grid/sizing, and a shared parent row would
 * otherwise fight it for width the moment it replaces the "Pick an emoji" trigger. */
.chat-emoji-picker-host { flex-basis: 100%; }
.chat-emoji-picker-host emoji-picker { max-width: 100%; }
/* One shared status line for reaction/delete outcomes and "who reacted" (`chatPanel.js`'s own
 * `actionStatus`), distinct from the composer's own status line below it. */
.chat-action-status { margin: .25rem .75rem 0; color: var(--muted); }

/* A pending row is greyed-out while sending, plainly marked once it fails, and it is the SAME
 * `chat-message`-shaped box the room's own real rows use -- optimistic content belongs among the
 * conversation it will join, not in some separate holding area. */
.chat-pending-message {
  margin: .3rem 0;
  padding: .4rem .5rem;
  min-width: 0;
  overflow-wrap: anywhere;
  border-radius: var(--card-radius);
  background: color-mix(in srgb, CanvasText 6%, Canvas 94%);
  opacity: .6;
}
.chat-pending-message.failed { opacity: 1; border: 1px dashed var(--border); }
.chat-pending-message small { display: block; margin-top: .2rem; color: var(--muted); }
.chat-pending-retry { margin-top: .25rem; min-height: 1.75rem; padding: 0 .5rem; font-size: .8rem; }

footer { margin-top: 2rem; }
footer a { display: inline-flex; align-items: center; min-width: var(--control-size); min-height: var(--control-size); color: var(--muted); }

.desktop-narrow-notice { display: none; }

/* Compact desktop is a layout tier, not the phone client. Navigation wraps before it can clip;
 * actions remain distinct buttons; dense forms become one column. */
@media (min-width: 768px) and (max-width: 1023px) {
  body { padding: 1rem; }
  main { max-width: 100%; }
  nav.screens, .actions { gap: .4rem; }
  .lineup-editor { grid-template-columns: 1fr; }
  .panel-form { grid-template-columns: 1fr; }
  ul.offerings { grid-template-columns: 1fr; }
}

/* Below the shared compact boundary, this desktop entry never pretends to be the phone client.
 * Serving picks mobile.html; an accidental desktop document stays readable but exposes no desktop
 * controls or route-specific content. */
@media (max-width: 767px) {
  body { padding: 1rem; }
  #leagues { display: none; }
  .desktop-narrow-notice {
    display: block;
    max-width: 34rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--card-radius);
  }
}

.activity-panel { margin-top: 1rem; }
.activity-list { display: grid; gap: .75rem; padding-left: 1.5rem; }
.activity-entry { padding-inline-start: .25rem; }
.activity-entry p { margin: 0 0 .25rem; }
.activity-entry time { color: var(--muted); font-size: .875rem; }
.activity-diff { display: grid; grid-template-columns: max-content 1fr; gap: .125rem .5rem; }
.activity-diff dt, .activity-diff dd { margin: 0; }
