/* =============================================================================
   UltiGameMate — authored global stylesheet
   Single source for everything that is NOT an app-shell component.
   Loaded after tokens.css, before components.css.
   App screens (sidebar + .app-main) are styled in components.css.
   Dark by default, light via :root[data-theme='light']. All values come
   from tokens.css.
   ============================================================================= */

/* --- 1. reset + base --------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

/* Lock the page behind an open modal. showModal() makes the rest of the page
   inert, but it does NOT stop the page itself scrolling under the backdrop.
   `:modal` matches only showModal() dialogs, so a non-modal show() dialog
   still leaves the page scrollable. scrollbar-gutter keeps the classic
   scrollbars of Windows/Linux from collapsing and shifting the layout as the
   dialog opens (a no-op with macOS overlay scrollbars). */
html {
  scrollbar-gutter: stable;
}

html:has(dialog:modal) {
  overflow: hidden;
}

body {
  margin: 0;
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
  overflow-x: clip; /* safety net: nothing should scroll the page sideways */
  background: var(--bg-page);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: var(--fz-body);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: 0;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* faint green aurora across the top of every page. A fixed pseudo-element
   rather than background-attachment:fixed (janky/ignored on iOS); z-index -1
   keeps it under all in-flow content but above the canvas background. */
body::before {
  content: '';
  position: fixed;
  inset: 0 0 auto;
  z-index: -1;
  block-size: 34rem;
  background: var(--aurora);
  pointer-events: none;
}

/* per-theme media (e.g. blog screenshots shipped in both themes): only the
   one matching the active theme shows; without JS (no data-theme) it's dark */
:root:not([data-theme='light']) .theme-light-only,
:root[data-theme='light'] .theme-dark-only {
  display: none;
}

img,
svg,
video {
  max-inline-size: 100%;
  block-size: auto;
}

/* duotone icons (partials/icon.liquid, js/icons.js): the silhouette layer is
   a tinted fill under the stroke. Tune per context with --icon-duo. */
.icon .duo {
  fill: currentColor;
  stroke: none;
  opacity: var(--icon-duo);
}

::selection {
  background: color-mix(in srgb, var(--accent-hi) 30%, transparent);
  color: var(--text-primary);
}

::placeholder {
  color: var(--text-muted);
}

:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: var(--space-3xs);
}

@view-transition {
  navigation: auto;
}

/* --- 2. typography (global — app screens included) ------------------------- */
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-snug);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-heading);
  text-wrap: balance;
}

h1 {
  font-size: var(--fz-2xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}

h2 {
  font-size: var(--fz-xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}

h3 {
  font-size: var(--fz-lg);
}

h4 {
  font-size: var(--fz-md);
}

h5,
h6 {
  font-size: var(--fz-body);
}

p {
  margin: 0;
}

strong,
b {
  color: var(--text-primary);
  font-weight: 700;
}

small,
.fz-sm {
  font-size: var(--fz-sm);
}

sup {
  font-size: 0.62em;
  line-height: 0;
  vertical-align: super;
  font-family: var(--font-mono);
}

code,
kbd,
samp {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

main:not(.app-main) .articles ul {
  margin-block-end: var(--space-2xl);
}

/* prose blocks inside content pages + un-migrated app content pages
   (a bare <section> straight inside .app-main: rules / leagues / profile) */
main:not(.app-main) :is(p, li),
.app-main > section:not([class]) :is(p, li) {
  font-family: var(--font-body);
  font-size: var(--fz-body);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

.app-main > section:not([class]) {
  display: flow-root;
}

/* the app column is wide (--w-app) for cards and tables; running text on
   the prose screens (rules / leagues / profile) keeps a readable measure */
.app-main > section:not([class]) :is(p, li) {
  max-inline-size: 72ch;
}

.app-main > section:not([class]) p {
  margin-block: 0 var(--flow-md);
}

.app-main > section:not([class]) :is(ul, ol) {
  margin-block: 0 var(--flow-md);
  padding-inline-start: var(--indent-list);
}

.app-main > section:not([class]) li {
  margin-block: var(--flow-3xs) 0;
}

.app-main > section:not([class]) li::marker {
  color: var(--accent-lo);
}

.app-main > section:not([class]) :is(h2, h3, h4) {
  margin-block: var(--flow-lg) var(--flow-2xs);
}

.app-main > section:not([class]) > :first-child {
  margin-block-start: 0;
}

main:not(.app-main) {
  --flow: var(--flow-md);
}

main:not(.app-main) h1 {
  margin-block-end: var(--flow-3xs);

  /* hero titles: white shading into mint */
  background: var(--grad-text-accent);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* h2 carries the section break now that <hr> is gone */
main:not(.app-main) h2 {
  margin-block: var(--flow-xl) var(--flow-2xs);
}

main:not(.app-main) :is(h1, h2) + h2 {
  margin-block-start: var(--flow-sm);
}

main:not(.app-main) h3 {
  margin-block: var(--flow-lg) var(--flow-3xs);
}

main:not(.app-main) h4 {
  margin-block: var(--flow-md) var(--flow-3xs);
}

main:not(.app-main) p {
  margin-block: 0 var(--flow);
}

main:not(.app-main) :is(ul, ol) {
  margin-block: 0 var(--flow);
  padding-inline-start: var(--indent-list);
}

main:not(.app-main) li {
  margin-block: var(--flow-3xs) 0;
}

main:not(.app-main) li::marker {
  color: var(--accent-lo);
}

/* --- 3. links — two treatments ------------------------------------------- */
a {
  color: var(--accent-hi);
  text-decoration: none;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
  transition: color var(--dur) var(--ease);
}

a:is(:hover, :focus-visible) {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
}

/* links inside flowing text always carry an underline — but not buttons or
   action links that happen to sit inside a <p> (e.g. <p><a class="btn">…) */
:is(main, section, dialog, .app-main)
  :is(p, li)
  a:not(.btn, .cta, .link, [class*='btn--'], [class*='cta--']) {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent-hi) 40%, transparent);
}

/* keep component links flat regardless of surrounding context */
:is(main, section, dialog, .app-main) :is(p, li) :is(a.btn, a.cta, a.link) {
  text-decoration: none;
}

/* chrome links (nav / footer): muted, colour shift only */
:is(.site-nav, .site-footer__nav, header nav, footer nav) a {
  color: var(--text-secondary);
  text-decoration: none;
}

:is(.site-nav, .site-footer__nav, header nav, footer nav) a:is(:hover, :focus-visible) {
  color: var(--text-primary);
  text-decoration: none;
}

/* standalone action link ("View all scores →") — one class */
.link {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--accent-hi);
  text-decoration: none;
  text-transform: none;
  cursor: pointer;
}

.link:is(:hover, :focus-visible) {
  text-decoration: underline;
}

.link--sm {
  font-size: var(--fz-sm);
}

.link--muted {
  color: var(--text-secondary);
}

.link--muted:is(:hover, :focus-visible) {
  color: var(--text-primary);
}

.link svg {
  inline-size: 1em;
  block-size: 1em;
}

/* --- 3b. surfaces — one shiny-border system for every card-like box --------
   Layered backgrounds: a top sheen, an optional translucent tint, an opaque
   fill (all clipped to the padding box) and a lit-from-the-top gradient edge
   painted into the transparent 1px border. No pseudo-elements, and the edge
   follows border-radius.
   Components never set `background`/`border` on a surface — they set these
   custom properties instead:
     --_fill  opaque base colour (default --bg-card; hover -> --bg-elevated)
     --_tint  translucent wash on top (selected -> --accent-wash, boost -> --gold-wash)
     --_edge  border gradient (--edge, --edge-hover, --edge-accent, --edge-gold)
   Keep --_fill opaque: a translucent fill lets the border-box edge gradient
   show through the whole card. Washes always go in --_tint.
   :where() = zero specificity, so every component rule still wins. */
@property --_fill {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}

@property --_tint {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}

:where(
  .match-card,
  .pick-option,
  .match-score-card,
  .stat-tile,
  .boost-pill,
  .round-tab,
  .twrap,
  .result-card,
  .card,
  .pick,
  .select,
  details,
  dialog,
  .sidebar__profile,
  .banner--sticky,
  .combobox__list
) {
  --_fill: var(--bg-card);
  --_tint: transparent;
  --_edge: var(--edge);
  --_sheen: var(--sheen);

  /* the drop shadow is a property too, so clickable surfaces can swap in
     the raised/hover/pressed shadows (components.css) and state rules can
     stack a glow on top with `box-shadow: var(--_shadow), <glow>` */
  --_shadow: var(--shadow-card);

  /* named so a rule that must out-rank a class-level `background`/`border`
     (e.g. .banner--sticky over .banner) can re-assert it in one line */
  --_surface:
    var(--_sheen) padding-box, linear-gradient(var(--_tint), var(--_tint)) padding-box,
    linear-gradient(var(--_fill), var(--_fill)) padding-box, var(--_edge) border-box;
  border: var(--border-hairline) solid transparent;
  background: var(--_surface);
  box-shadow: var(--_shadow);

  /* --_fill/--_tint are registered colours (@property), so hovers fade */
  transition:
    --_fill var(--dur) var(--ease),
    --_tint var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

/* --- 4. buttons — one system ------------------------------------------- */
.btn,
.cta:not(.cta--x) {
  --_bg: var(--bg-elevated);
  --_fg: var(--text-primary);
  --_bd: transparent;
  --_sheen: linear-gradient(145deg, rgb(255 255 255 / 0.08), transparent 60%);

  /* extruded from the page (outer neumorphism, tokens.css) */
  --_shadow: var(--shadow-raised-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-sm);
  min-inline-size: 0;

  /* Not margin-block-start: this shorthand's specificity (.cta:not(.cta--x),
     two classes) beats the admin/app spacing rule's margin-block-start
     (components.css, one class + one type selector), silently cancelling it
     and sticking the button to whatever precedes it. Leaving block-start
     unset here means nothing competes with that rule's declaration; the
     initial value (0) still applies everywhere this component's own rule
     doesn't override it. */
  margin-inline: 0;
  margin-block-end: 0;

  /* one normal size everywhere (the login page's Google button); only the
     main nav's Sign in uses the compact --sm below */
  padding: var(--pad-xl) var(--pad-3xl);
  border: var(--border-hairline) solid var(--_bd);
  border-block-start-color: var(--_bd-top, var(--_bd));
  border-radius: var(--radius-sm);

  /* --_sheen may be several layers; the last layer stays a plain colour so
     the hover ring's color-mix(var(--_bg)) below keeps working */
  background: var(--_sheen), var(--_bg);
  box-shadow: var(--_shadow);
  color: var(--_fg);
  font-family: var(--font-body);
  font-size: var(--fz-md);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tracking-label);
  text-transform: none;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    filter var(--dur) var(--ease),
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

/* Hover: a glow ring in the button's own fill colour + a touch brighter.
   No transform — a moving target flickers when the cursor approaches from below. */
.btn:is(:hover, :focus-visible),
.cta:not(.cta--x):is(:hover, :focus-visible) {
  filter: brightness(1.12);
  box-shadow:
    var(--_shadow),
    0 0 0 4px color-mix(in srgb, var(--_bg) 35%, transparent);
  text-decoration: none;
}

/* ghost / secondary buttons fill in on hover (transparent → solid) */
.btn--ghost:is(:hover, :focus-visible),
.cta--alt:is(:hover, :focus-visible),
.cta--inline:is(:hover, :focus-visible),
.cta--ghost:is(:hover, :focus-visible) {
  --_bg: var(--bg-elevated);
  --_fg: var(--text-primary);
}

/* pressed: the extruded button sinks into a dent */
.btn:active,
.cta:not(.cta--x):active {
  filter: brightness(1);
  box-shadow: var(--shadow-pressed);
}

.btn:disabled,
.btn[disabled],
.cta:disabled,
.cta[disabled] {
  opacity: 0.5;
  filter: none;
  cursor: not-allowed;
}

/* primary: mint -> green gradient fill, a glassy top highlight, a lit top
   edge and a soft green glow underneath */
.btn--primary,
.cta:not(.cta--alt, .cta--inline, .cta--share, .cta--ghost, .cta--x) {
  --_bg: var(--accent-hi);
  --_fg: var(--accent-ink);
  --_bd: color-mix(in srgb, var(--accent-lo) 70%, transparent);
  --_bd-top: color-mix(in srgb, var(--accent-bright) 90%, white);
  --_sheen:
    linear-gradient(180deg, rgb(255 255 255 / 0.22), transparent 55%),
    linear-gradient(180deg, var(--accent-bright), var(--accent-hi) 55%, var(--accent-lo));
  --_shadow: var(--inset-hi), var(--shadow-raised-sm), var(--glow-accent);
  font-weight: 700;
}

.btn--primary:is(:hover, :focus-visible),
.cta:not(.cta--alt, .cta--inline, .cta--share, .cta--ghost, .cta--x):is(:hover, :focus-visible) {
  --_bg: var(--accent-bright);
  filter: brightness(1.08) saturate(1.05);
}

/* ghost: glassy transparent fill with a hairline lit brighter along the top */
.btn--ghost,
.cta--alt,
.cta--inline,
.cta--ghost {
  --_bg: transparent;
  --_fg: var(--text-secondary);
  --_bd: var(--line-strong);
  --_sheen: linear-gradient(180deg, rgb(255 255 255 / 0.04), transparent 70%);
}

.btn--ghost:is(:hover, :focus-visible),
.cta--alt:is(:hover, :focus-visible),
.cta--ghost:is(:hover, :focus-visible) {
  --_fg: var(--text-primary);
}

/* on light, a transparent ghost barely registers against the page — give
   the secondary button a card fill, darker label and a soft lift, still
   clearly a step below the green primary */
:root[data-theme='light'] :is(.btn--ghost, .cta--alt) {
  --_bg: var(--bg-card);
  --_fg: var(--text-primary);
  --_bd: var(--line-strong);
  --_shadow: var(--shadow-raised-sm);
}

/* two buttons side by side in running content (Calculate + Reset, Yes/No,
   Copy link + Share) — flex/grid button rows space themselves with gap */
:is(.btn, .cta:not(.cta--x)) + :is(.btn, .cta:not(.cta--x)) {
  margin-inline-start: var(--space-sm);
}

.dialog-actions :is(.btn, .cta:not(.cta--x)) + :is(.btn, .cta:not(.cta--x)) {
  margin-inline-start: 0;
}

/* compact: main nav only (nav-public Sign in) — every other button is normal size */
.btn--sm,
.cta--small {
  padding: var(--pad-sm) var(--pad-xl);
  font-size: var(--fz-xs);
}

.btn--block,
.cta--block {
  display: flex;
  inline-size: 100%;
}

.btn--sticky,
.cta--sticky {
  position: sticky;
  inset-block-end: var(--space-md);
}

.cta--share {
  --_bg: transparent;
  --_fg: var(--accent-hi);
  --_bd: color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: var(--radius-pill);
}

:is(.btn, .cta):has(img) img,
:is(.btn, .cta) svg {
  inline-size: 1.15em;
  block-size: 1.15em;
  flex: none;
}

/* transient states set by window.runButton / flashButton */
.cta.done,
.cta.error,
.cta.running,
.btn.done,
.btn.error,
.btn.running {
  position: relative;
  color: transparent;
  pointer-events: none;
  cursor: default;
}

.cta.done::after,
.cta.error::after,
.cta.running::after,
.btn.done::after,
.btn.error::after,
.btn.running::after {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--_fg, var(--text-primary));
}

.cta.done::after,
.btn.done::after {
  content: '✔ Done';
}

.cta.error::after,
.btn.error::after {
  content: '✕ Error';
}

.cta.running::after,
.btn.running::after {
  content: '…';
}

/* small square icon button — banner dismiss, dialog close */
.icon-btn,
.cta--x,
.close-x {
  display: inline-grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  flex: none;
  margin: 0;
  padding: 0;

  /* a small raised key (clickable = raised, tokens.css): domed fill, a
     bevelled border lit top-left, extruded shadow; presses in on :active */
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--sheen-convex), var(--bg-elevated);
  box-shadow: var(--shadow-raised-sm);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--fz-md);
  line-height: 1;
  cursor: pointer;
  transition:
    box-shadow var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.icon-btn:is(:hover, :focus-visible),
.cta--x:is(:hover, :focus-visible),
.close-x:is(:hover, :focus-visible) {
  box-shadow: var(--shadow-raised);
  color: var(--text-primary);
}

.icon-btn:active,
.cta--x:active,
.close-x:active {
  box-shadow: var(--shadow-pressed);
}

.icon-btn svg,
.cta--x svg,
.close-x svg {
  inline-size: var(--icon-md);
  block-size: var(--icon-md);
}

/* --- 5. forms ---------------------------------------------------------- */
:is(input, select, textarea):not([type='checkbox'], [type='radio']) {
  inline-size: 100%;
  max-inline-size: 30rem;
  margin: 0;
  padding: var(--pad-md) var(--pad-lg);

  /* background-color, not the `background` shorthand: this selector's
     specificity (one attribute selector via :not()) beats the plain `select`
     rule below that paints the dropdown chevron via background-image — the
     shorthand would silently reset that sub-property back to none and the
     select would render with no chevron at all. */
  background-color: var(--bg-input);
  color: var(--text-primary);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-sm);

  /* sunken well: a shadow inside the top edge, a faint lit lip below */
  box-shadow: var(--shadow-well-input);
  font: inherit;
  font-family: var(--font-body);
  transition:
    border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

textarea {
  max-inline-size: none;
  min-block-size: 6rem;
  resize: vertical;
}

:is(input, select, textarea):focus-visible {
  outline: 0;
  border-color: var(--accent-hi);
  box-shadow:
    var(--ring),
    0 0 20px -4px color-mix(in srgb, var(--accent-hi) 45%, transparent);
}

:is(input, select, textarea)[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

label {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-secondary);
}

label + :is(input, select, textarea) {
  margin-block-start: var(--space-2xs);
}

select {
  appearance: none;
  background-image: var(
    --caret,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%239595a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")
  );
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 1.15rem;
  padding-inline-end: var(--space-2xl);
}

/* A dropdown is a control you press, so it's raised like every other
   clickable surface (outer neumorphism, tokens.css) rather than sunk into a
   well like a text input: domed fill under the caret, a bevelled border lit
   top-left, extruded shadow; hover lifts, open/active presses in.
   :not([multiple]) lifts specificity to (0,1,1) so this beats the shared
   input rule above; the :focus-visible rule is restated for the same reason. */
select:not([multiple]) {
  background-color: var(--bg-elevated);
  background-image:
    var(
      --caret,
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%239595a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")
    ),
    var(--sheen-convex);
  background-repeat: no-repeat, no-repeat;
  background-position:
    right 0.7rem center,
    0 0;
  background-size:
    1.15rem,
    100% 100%;
  border-color: var(--line-strong);
  box-shadow: var(--shadow-raised-sm);
  cursor: pointer;
}

select:not([multiple]):hover:not([disabled]) {
  box-shadow: var(--shadow-raised);
}

select:not([multiple]):active:not([disabled]) {
  box-shadow: var(--shadow-pressed);
}

select:not([multiple]):focus-visible {
  border-color: var(--accent-hi);
  box-shadow: var(--ring), var(--shadow-raised-sm);
}

input[type*='datetime']::-webkit-calendar-picker-indicator {
  filter: invert(0.7);
  cursor: pointer;
}

input:is([type='checkbox'], [type='radio']) {
  accent-color: var(--accent-hi);
  inline-size: 1.15rem;
  block-size: 1.15rem;
  margin: 0;
  cursor: pointer;
}

/* Label wrapping its own checkbox — keeps box and text on one baseline. */
main:not(.app-main) .combobox__list {
  padding-block: var(--space-xs);
  padding-inline: var(--space-xs);
}

main:not(.app-main) .combobox__list li {
  margin: 0;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  cursor: pointer;
}

.checkbox-label:has(input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Not part of the surface system: the legend notch would sit over a
   continuous border-box gradient. Same lit-from-the-top look, done with a
   brighter top border colour and a sheen instead. */
fieldset {
  margin: 0 0 var(--space-lg);
  padding: var(--space-lg);
  border: var(--border-hairline) solid rgb(var(--rim-rgb) / 0.05);
  border-block-start-color: rgb(var(--rim-rgb) / 0.1);
  border-radius: var(--radius-card);
  background: var(--sheen), var(--bg-card);
  box-shadow: var(--shadow-card);
}

fieldset:has(.column) {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md) var(--space-lg);
}

@media (max-width: 34rem) {
  fieldset:has(.column) {
    grid-template-columns: 1fr;
  }
}

legend {
  padding: 0;
  grid-column: 1 / -1;
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-secondary);
}

.column {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-inline-size: 0;
}

.column.separator {
  border-block-start: var(--border-hairline) solid var(--line);
  margin-block-start: var(--space-xs);
  padding-block-start: var(--space-lg);
}

.column small {
  color: var(--text-muted);
  font-size: var(--fz-xs);
}

form
  :is(input, select, textarea, .cta, .btn, button)
  + :is(input, select, textarea, .cta, .btn, button) {
  margin-block-start: var(--space-sm);
}

/* --- 6. tables ------------------------------------------------------ */
.twrap {
  inline-size: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-block: var(--space-lg);
  border-radius: var(--radius-card);
}

/* header row reads as a slightly raised band */
.twrap thead tr {
  background: linear-gradient(180deg, rgb(var(--rim-rgb) / 0.035), rgb(var(--rim-rgb) / 0.01));
}

/* a table hidden while its data loads (or because there's nothing to show)
   collapses its wrapper too, instead of leaving an empty styled box on screen */
.twrap:has(> table.hidden) {
  display: none;
}

.twrap table {
  inline-size: 100%;
  min-inline-size: 28rem;
  border-collapse: collapse;
  table-layout: auto;
  font-family: var(--font-body);
  font-size: var(--fz-sm);
}

.twrap :is(th, td) {
  padding: var(--pad-lg) var(--pad-xl);
  text-align: start;
  vertical-align: middle;
}

.twrap :is(th, td):first-child {
  white-space: nowrap;
}

.twrap th {
  color: var(--text-secondary);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  white-space: nowrap;
}

.twrap td {
  color: var(--text-body);
}

.twrap :is(thead, tbody) tr + tr :is(th, td),
.twrap tbody tr:first-child :is(th, td) {
  border-block-start: var(--border-hairline) solid var(--line);
}

.twrap tbody tr:hover {
  background: color-mix(in srgb, var(--bg-elevated) 60%, transparent);
}

.twrap .m,
.twrap td[colspan] {
  text-align: end;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.twrap .c {
  text-align: center;
}

.twrap :is(.current-user, tr.current-user) td {
  background: var(--accent-wash);
  color: var(--text-primary);
  font-weight: 700;
}

/* a glowing accent bar marks your own row */
.twrap :is(.current-user, tr.current-user) td:first-child {
  box-shadow: inset 3px 0 0 var(--accent-hi);
}

.twrap td img {
  display: inline-block;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  vertical-align: middle;
}

.twrap .cta,
.twrap .btn {
  margin: 0;
}

/* empty table: show a resting message instead of a bare header */
.twrap table:not(:has(tbody tr)) thead {
  opacity: 0.5;
}

/* --- 7. announcement / install banner ----------------------------- */
.banner {
  border: 0;
  background: var(--accent-wash);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  text-align: center;
}

.banner--warning {
  background: var(--gold-wash);
}

.banner--error {
  background: color-mix(in srgb, var(--neg) 14%, transparent);
}

/* In-flow banner inside a form or section (the calculator's restore-draft
   prompt), in place of a floating toast. */
.banner--inline {
  border-radius: var(--radius-md);
  margin-block-end: var(--space-md);
}

/* Floating card, bottom-right, clear of the mobile tab bar. Used by the PWA
   install prompt (partials/install.liquid) and the onboarding walkthrough
   prompt (built in src/js/walkthrough.js). */
.banner--sticky {
  position: fixed;
  z-index: 45;
  inset-block-start: auto;
  inset-block-end: calc(var(--tabbar-h) + var(--space-md));
  inset-inline-start: auto;
  inset-inline-end: var(--space-md);
  inline-size: min(22rem, calc(100vw - 2 * var(--space-md)));
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-hover);

  /* out-rank .banner's flat wash + border:0 */
  border: var(--border-hairline) solid transparent;
  background: var(--_surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  text-align: start;
}

@media (min-width: 64rem) {
  .banner--sticky {
    /* no tab bar on desktop */
    inset-block-end: var(--space-lg);
    inset-inline-end: var(--space-lg);
  }
}

.banner__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs) var(--space-sm);
  inline-size: min(var(--w-wide), 100%);
  margin-inline: auto;
  padding: 0.55rem var(--pad-inline);
}

.banner--sticky .banner__inner {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: var(--space-sm);
  inline-size: auto;
  margin: 0;
  padding: var(--space-md);

  /* keep the first line of text clear of the corner close button */
  padding-inline-end: calc(var(--space-md) + 1.5rem);
}

.banner--sticky .banner__message {
  color: var(--text-body);
  font-size: var(--fz-sm);
  line-height: var(--lh-ui);
}

.banner--sticky .banner__btn {
  flex: none;
}

.banner--sticky .banner__dismiss {
  position: absolute;
  inset-block-start: var(--space-2xs);
  inset-inline-end: var(--space-2xs);
  inline-size: 1.75rem;
  block-size: 1.75rem;
}

.banner--sticky .banner__dismiss svg {
  inline-size: var(--icon-sm);
  block-size: var(--icon-sm);
}

/* When the install prompt is already occupying the bottom-right slot, stack the
   walkthrough prompt above it. */
body:has(.banner--sticky#install-banner:not([style*='none'])) #walkthrough-banner {
  inset-block-end: calc(var(--tabbar-h) + var(--space-md) + 8.5rem);
}

@media (min-width: 64rem) {
  body:has(.banner--sticky#install-banner:not([style*='none'])) #walkthrough-banner {
    inset-block-end: calc(var(--space-lg) + 8.5rem);
  }
}

.banner__inner :is(p, a) {
  margin: 0;
  display: inline;
}

.banner__inner a {
  color: var(--accent-hi);
  text-decoration: underline;
}

/* --- 8. public header -------------------------------------------- */
.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
  border: 0;
  background: color-mix(in srgb, var(--bg-page) 78%, transparent);
  backdrop-filter: blur(14px) saturate(1.3);
}

/* hairline under the bar that fades out at both ends, lit green mid-way */
.site-header::after {
  content: '';
  position: absolute;
  inset: auto 0 0;
  block-size: var(--border-hairline);
  background: linear-gradient(
    90deg,
    transparent,
    var(--line-strong) 20%,
    color-mix(in srgb, var(--accent-hi) 40%, transparent) 50%,
    var(--line-strong) 80%,
    transparent
  );
  pointer-events: none;
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-lg);
  inline-size: min(var(--w-wide), 100%);
  margin-inline: auto;
  padding: 0.8rem var(--pad-inline);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  text-decoration: none;
  color: var(--text-primary);
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
}

.brand :is(svg, img) {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  border-radius: var(--radius-xs);
  color: var(--accent-hi);
  box-shadow:
    0 0 0 1px rgb(var(--rim-rgb) / 0.08),
    0 4px 14px -4px color-mix(in srgb, var(--accent-hi) 55%, transparent);
}

.brand__name {
  font-size: var(--fz-md);
}

/* "Ulti·Game·Mate" — highlight the middle so the wordplay reads */
.brand__accent {
  color: var(--accent-hi);
}

.site-header nav.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) clamp(0.75rem, 0.3rem + 1.6vw, 1.75rem);
  margin-inline-start: auto;
}

/* when the header wraps: brand ↔ Sign in on row 1, nav centred on its own row */
@media (max-width: 44rem) {
  .site-header__inner {
    row-gap: var(--space-xs);
    padding-block: var(--space-xs);
  }

  .site-header nav.site-nav {
    order: 1;
    flex-basis: 100%;
    margin-inline-start: 0;
    justify-content: center;
  }

  .site-header .btn {
    margin-inline-start: auto;
  }
}

.site-header .site-nav a {
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 600;
}

/* current-page indicator */
.site-header .site-nav a[aria-current='page'] {
  color: var(--accent-hi);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.4em;
  text-decoration-color: currentColor;
}

.site-header .btn {
  flex: none;
}

/* Bottom rule under any non-site-header <header> — in practice just .page-head
   on app/admin screens now that the legacy admin header (nav-app.liquid) is
   gone. */
header:not(.site-header) {
  border-block-end: var(--border-hairline) solid var(--line);

  /* fades out towards the end instead of a hard full-width rule */
  border-image: linear-gradient(90deg, var(--line-strong), var(--line) 45%, transparent) 1;
}

/* --- 9. footers ------------------------------------------------ */
.site-footer {
  margin-block-start: var(--space-3xl);
  border: 0;
  border-block-start: var(--border-hairline) solid var(--line);
  border-image: linear-gradient(
      90deg,
      transparent,
      var(--line-strong) 30%,
      var(--line-strong) 70%,
      transparent
    )
    1;
  background: var(--bg-sunken);
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  inline-size: min(var(--w-wide), 100%);
  margin-inline: auto;
  padding: var(--space-2xl) var(--pad-inline);
}

.site-footer .brand__name {
  font-size: var(--fz-sm);
}

.site-footer__brand {
  margin-block-end: var(--flow-xs);
}

.site-footer__tag {
  margin: var(--flow-3xs) 0 var(--flow-2xs);
  color: var(--text-secondary);
  font-size: var(--fz-sm);
}

.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  margin-block: var(--gap-2xs) var(--flow-2xs);
}

.site-footer__nav a {
  font-family: var(--font-body);
  font-size: var(--fz-sm);
}

.site-footer__legal {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fz-xs);
}

.site-footer__legal a {
  color: var(--text-secondary);
}

/* shared footer markup (<footer> > <div> > logo + text columns), used by
   both footer-app.liquid and footer-public.liquid. */
footer:not(.site-footer) {
  border-block-start: var(--border-hairline) solid var(--line);
  border-image: linear-gradient(
      90deg,
      transparent,
      var(--line-strong) 30%,
      var(--line-strong) 70%,
      transparent
    )
    1;
  background: var(--bg-sunken);
}

footer:not(.site-footer) > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md) var(--space-lg);
  inline-size: 100%;
  max-inline-size: var(--w-wide);
  margin-inline: auto;
  padding: var(--space-xl) var(--pad-inline);

  /* The mobile tab bar is position:fixed and floats over whatever's beneath
     it, including this footer once the page is scrolled all the way down —
     without this, its last row of links sits underneath the bar and can't
     be tapped. Reset below alongside the bar itself disappearing. */
  padding-block-end: calc(var(--tabbar-h) + var(--space-xl));
}

/* on desktop, signed in, the app footer runs the full width below the
   sidebar (sticky inside .app-shell, so it ends before the footer) and its
   content is anchored to the far left edge instead of centred. Public pages
   have no sidebar, so .app-footer scopes this to footer-app.liquid only —
   otherwise the public footer stopped being centred. `.app-shell ~` limits it
   further to pages that actually render the sidebar (the 404 page uses
   footer-app.liquid without one) and lifts specificity to (0,2,1) so it beats
   the base `footer:not(.site-footer) > div` (0,1,2) above. */
@media (min-width: 64rem) {
  .app-shell ~ .app-footer > div {
    max-inline-size: none;
    margin-inline: 0;
    padding-inline: var(--space-lg);
    padding-block-end: var(--space-xl);
  }
}

footer:not(.site-footer) img {
  inline-size: clamp(4rem, 3rem + 4vw, 5.5rem);
  block-size: auto;
  opacity: 0.9;
}

footer:not(.site-footer) div div {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

footer:not(.site-footer) p {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fz-sm);
}

/* onboarding tour clip — framed like a card so it reads as a screen capture,
   not a stray image. Only ever inside the walkthrough dialog
   (src/_includes/partials/walkthrough-dialog.liquid); the dashboard and the two
   How-to-play pages open that dialog rather than embedding the clip inline. */
.tour-clip {
  overflow: hidden;
  max-inline-size: 40rem;
  border-radius: var(--radius-md);
  background: var(--bg-card);
}

.tour-clip :is(img, video) {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

/* --- 10. results / calculator vocabulary (.picks/.pick/.game/...) --- */
.picks,
.cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-block: var(--space-md) 0;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-sm);
  align-items: start;
}

.pick,
.select {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  margin: 0;
  padding: var(--pad-card);
  border-radius: var(--radius-card);
}

.select {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.game,
.pick .game,
.select .game {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-sm);
  inline-size: auto;
  margin-inline: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 600;
  text-align: start;
}

/* inside a table, .game shrink-wraps to its own row's content width and is
   then centered within the cell — since content width varies row to row
   (team name length), each row's vs/score ends up centered on a different
   point instead of lining up down the column. Stretch it to the full cell
   width so the flexible name spans absorb that difference instead, keeping
   vs/score at one fixed horizontal position for every row. */
.twrap .game {
  inline-size: 100%;
  margin-inline: 0;
}

.game:has(span:nth-child(5)) {
  display: grid;
  grid-template-columns: 1fr auto auto auto 1fr;
  align-items: center;
}

.game.round {
  grid-column: 1 / -1;
  inline-size: 100%;
  min-block-size: 3.5rem;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: var(--bg-elevated);
}

.game:is(button) {
  cursor: pointer;
}

.game:is(button):is(:hover, :focus-visible) {
  color: var(--accent-hi);
}

.game span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* the two team names are the only elements guaranteed to always be present
   (crest icons are conditional on clubIcon() finding a match) — giving them
   equal flex so the vs/score group between them stays centered regardless
   of name length or whether a crest rendered on either side */
.game span:first-of-type,
.game span:last-of-type {
  flex: 1 1 0;
  min-inline-size: 0;
}

.game span:first-of-type {
  text-align: end;
}

.game span:last-of-type {
  text-align: start;
}

.game img {
  flex: none;
  inline-size: 1.4em;
  block-size: 1.4em;
}

.game small {
  color: var(--text-muted);
  font-family: var(--font-body);
  font-weight: 400;
}

.game :is(strong) {
  color: var(--text-primary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.score {
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* --- result card (home "example scores", results page) -------------------- */
.result-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-block: var(--space-md) 0;
}

.result-card {
  display: grid;
  gap: var(--space-sm);
  inline-size: 100%;
  margin: 0;
  padding: var(--pad-card) var(--space-lg);
  border-radius: var(--radius-card);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.result-card:is(:hover, :focus-visible) {
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-clickable-hover);
}

.result-card__head {
  text-align: center;
}

.result-card__stage {
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-secondary);
  text-align: center;
}

.result-card__date {
  font-weight: 400;
  letter-spacing: 0;
  color: var(--text-muted);
}

.result-card__match {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-sm) var(--space-md);
}

.result-card__team {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
  font-family: var(--font-body);
  font-size: var(--fz-md);
  font-weight: 600;
  color: var(--text-primary);
}

.result-card__team:first-child {
  flex-direction: row-reverse;
}

/* team crest. No size here on purpose: window.clubIcon() (base.liquid) stamps
   this class onto every crest it renders, across many contexts that each
   already size their own icons (.game img, .pick-option__team img,
   .twrap td img, etc.) via HTML width/height or their own CSS. */
.crest {
  flex: none;
  object-fit: contain;
}

/* Dark theme only: a soft light halo (not a solid disc) so a dark-on-transparent
   logo (e.g. Spurs' navy crest) still separates from the dark page, without
   painting a flat chip behind every icon. On a light page the same blend
   inverts the crest's own colours instead, which is why it's scoped here.
   `:not([data-theme='light'])` rather than `[data-theme='dark']` so the
   no-JS fallback (no attribute set, dark tokens apply) keeps the halo too. */
:root:not([data-theme='light']) .crest {
  mix-blend-mode: exclusion;
  filter: brightness(1.5);
}

/* crest inside a .game row (match-detail dialog title) — beat the generic
   `.game img` size rule and give it a touch more presence */
.game .crest {
  inline-size: 1.75rem;
  block-size: 1.75rem;
}

/* Crest sizes are stamped as width/height attributes by clubIcon(), and each
   caller picks its own: 18 in a score card, 20 on the picks page, 24 in the
   admin tables, 28 in a match dialog. Those are fixed pixels, so on a phone
   they sit next to 16px text looking far smaller than they do on a desktop
   row. Raise the floor to the dialog's 1.75rem everywhere below the header's
   wrap point — a floor rather than a fixed size, so anything already larger
   keeps its size, and both axes so a non-square crest can't be stretched. */
@media (max-width: 44rem) {
  .crest {
    min-inline-size: 1.75rem;
    min-block-size: 1.75rem;
  }
}

.result-card__score {
  padding: var(--pad-3xs) var(--pad-md);
  border: var(--border-hairline) solid rgb(var(--rim-rgb) / 0.05);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  box-shadow: var(--shadow-well);
  font-family: var(--font-mono);
  font-size: var(--fz-lg);
  font-weight: 700;
  color: var(--text-heading);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.result-card:is(:hover, :focus-visible) .result-card__score {
  background: var(--bg-page);
}

/* points badge fills in solid + pops on hover (transform only — no reflow) */
.result-card:is(:hover, :focus-visible) .points {
  background: linear-gradient(135deg, var(--accent-bright), var(--pos));
  color: var(--ink-on-bright);
  transform: scale(1.08);
}

.result-card__note {
  margin: 0;
  font-size: var(--fz-sm);
  color: var(--text-secondary);
  line-height: var(--lh-ui);
  text-align: center;
}

.points {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2xs);
  margin: 0;
  padding: var(--pad-2xs) var(--pad-md);
  border-radius: var(--radius-pill);
  background: var(--pos-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pos) 24%, transparent);
  color: var(--pos);
  font-family: var(--font-mono);
  font-size: var(--fz-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition:
    background var(--dur) var(--ease),
    color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.points::before {
  content: none;
}

.pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block: 0 var(--space-md);
}

.pill,
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  margin: 0;
  padding: var(--pad-xs) var(--pad-lg);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--sheen), var(--bg-card);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  cursor: pointer;
}

.pill:has(input:checked),
.tag.active {
  background: var(--sheen), var(--accent-wash);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  border-block-start-color: color-mix(in srgb, var(--accent-bright) 70%, transparent);
  color: var(--accent-hi);
  box-shadow: var(--glow-accent);
}

.pill input,
.select input[type='checkbox'],
.cards input[type='checkbox'] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

:is(.select, .pill):has(input[disabled]) {
  opacity: 0.55;
  filter: grayscale(0.5);
}

.card {
  border-radius: var(--radius-card);
  padding: var(--pad-card);
}

.card:has(input:checked) {
  --_tint: var(--accent-wash);
  --_edge: var(--edge-accent);
}

.card time {
  font-size: var(--fz-xs);
  color: var(--text-muted);
}

.boost {
  margin: 0;
  padding: var(--pad-xs) var(--pad-lg);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--accent-hi);
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  cursor: pointer;
}

.boost:is(:hover, :focus-visible) {
  background: var(--accent-wash);
}

.boost.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

details {
  border-radius: var(--radius-card);
  padding: var(--pad-lg) var(--pad-xl);
  font-family: var(--font-body);
}

details[open] {
  padding-block-end: var(--flow-sm);
}

summary {
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--text-primary);
  cursor: pointer;
}

.twrap:has(.saving) {
  position: relative;
  opacity: 0.7;
  pointer-events: none;
}

.twrap:has(.saving)::after {
  content: 'Saving…';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg-card) 88%, transparent);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  animation: ugm-pulse 1.2s ease-in-out infinite;
}

@keyframes ugm-pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.55;
  }
}

/* --- 12. form status ------------------------------------------- */

/* Inline feedback line (ui.js setStatus): sits under the form or button the
   user acted on and stays until their next attempt — no floating toasts. */
.form-status {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fz-sm);
}

.form-status[data-type='error'] {
  color: var(--neg);
}

.form-status[hidden] {
  display: none;
}

/* --- 13. generic dialogs (score-dialog lives in components.css) --- */

/* Pinned near the top rather than centred: a centred modal grows in both
   directions when an error line or extra fields appear, so its buttons jump.
   The height cap + overflow matter on short viewports (a laptop in landscape,
   a phone keyboard open): without them the box runs past the bottom of the
   screen with `overflow: visible`, so the last row of fields and the action
   buttons are painted outside the viewport and can be neither seen nor
   clicked. `dvh` follows mobile browser chrome as it collapses. */
dialog:not(.score-dialog) {
  margin: min(10vh, 5rem) auto var(--space-md);
  max-block-size: calc(100dvh - min(10vh, 5rem) - var(--space-md));
  overflow: auto;
  overscroll-behavior: contain;
  border-radius: var(--radius-lg);
  --_edge: var(--edge-hover);
  color: var(--text-body);
  box-shadow: var(--shadow-lg);
  padding: var(--space-lg);
  inline-size: min(34rem, calc(100vw - 2 * var(--space-md)));
}

/* The match-stats edit dialog packs several fieldsets and six action buttons
   in — the generic 34rem width is cramped for it specifically. */
#edit-dialog {
  inline-size: min(46rem, calc(100vw - 2 * var(--space-md)));
}

/* Five stacked fieldsets (Details, Shots, Saves, Cards & Penalties, Momentum)
   make a very tall single column on a desktop viewport — tall enough that the
   action buttons sit below the fold and the whole dialog has to be scrolled to
   reach them. Two columns from the same 64rem the rest of the app treats as
   desktop, with the wide free-text momentum field and the two footer rows
   spanning both. Matches the `fieldset:has(.column)` pattern used for forms
   elsewhere, one level up. */
@media (min-width: 64rem) {
  #edit-dialog {
    inline-size: min(60rem, calc(100vw - 2 * var(--space-md)));
  }

  #edit-match-form {
    display: grid;
    grid-template-columns: 1fr 1fr;

    /* row rhythm stays with the fieldsets' own bottom margin */
    column-gap: var(--space-lg);
    align-items: start;
  }

  /* grid items default to min-content sizing, which a long label would push
     past its share of the row */
  #edit-match-form > fieldset {
    min-inline-size: 0;
  }

  #edit-match-form > :is(.edit-dialog__wide, #edit-points, .dialog-actions) {
    grid-column: 1 / -1;
  }
}

/* The walkthrough clip is a portrait phone screen-capture, so size the dialog
   to the video — capped to the viewport — rather than forcing a wide box, and
   let it scroll if head + clip + foot still can't fit. Selector carries a type
   so it matches the specificity of `dialog:not(.score-dialog)` above and wins
   on source order. */
dialog.walkthrough-dialog {
  inline-size: fit-content;
  max-inline-size: calc(100vw - 2 * var(--space-md));
  max-block-size: calc(100dvh - 2 * var(--space-md));
  overflow: auto;
}

.walkthrough-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
  font-weight: 600;
}

/* `dialog form[method='dialog']` adds a top margin (for footer close-forms that
   sit below content) — cancel it here so the × lines up with the title. Selector
   out-specifies that rule (class + type + attr). */
.walkthrough-dialog__head form[method='dialog'] {
  margin-block-start: 0;
}

/* wrap the portrait video tightly (no card-coloured gutters) and centre it */
.walkthrough-dialog .tour-clip {
  inline-size: fit-content;
  max-inline-size: 100%;
  margin-inline: auto;
}

/* drive the size off height so the dialog never outgrows the viewport; width
   follows from the video's intrinsic aspect ratio (its width/height attrs) */
.walkthrough-dialog .tour-clip video {
  block-size: min(80dvh, 50rem);
  inline-size: auto;
  max-inline-size: 100%;
  margin-inline: auto;
}

.walkthrough-dialog__foot {
  margin-block-start: var(--space-md);
  margin-block-end: 0;
  font-size: var(--fz-sm);
  color: var(--text-muted);
}

dialog::backdrop {
  background: rgb(5 5 8 / 0.6);
  backdrop-filter: blur(4px);
}

main:not(.app-main) dialog h2 {
  font-size: var(--fz-lg);
  margin-block-start: var(--space-sm);
  margin-block-end: var(--space-sm);
}

dialog form[method='dialog'] {
  margin-block-start: var(--space-md);
}

/* button row in a dialog footer (a whole method='dialog' form for simple
   confirm dialogs, or a wrapper div for forms with fields above the buttons) */
dialog form[method='dialog']:not(:has(fieldset, label)),
dialog .dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--gap-sm);
}

.dialog-actions
  :is(input, select, textarea, .cta, .btn, button)
  + :is(input, select, textarea, .cta, .btn, button) {
  margin-block-start: unset;
}

dialog .twrap tbody td:last-of-type {
  text-align: right;
}

/* 5-column match-detail row inside a dialog collapses on narrow screens */
@media (max-width: 30rem) {
  dialog .game:has(span:nth-child(5)) {
    grid-template-columns: 1fr auto 1fr;
  }

  dialog .game:has(span:nth-child(5)) span:is(:nth-child(1), :nth-child(5)) {
    display: none;
  }
}

/* --- 14. blog ------------------------------------------------- */
section.blog {
  padding-block-start: var(--space-xl);
}

section.blog img {
  margin-block: var(--space-lg);
  border-radius: var(--radius-md);
  border: var(--border-hairline) solid var(--line);
}

/* --- 15. page layout (non-app pages) ------------------------- */
main {
  flex: 1;
  display: flex;
  flex-direction: column;
}

main:not(.app-main) {
  inline-size: 100%;
  max-inline-size: var(--w-wide);
  margin-inline: auto;
  padding: clamp(1.75rem, 1rem + 3vw, 3.5rem) var(--pad-inline) var(--space-3xl);
}

/* sections fill the frame so their left edge lines up with the header, footer
   and each other; only the running-text measure (p / li) is held narrower */
main:not(.app-main) > section {
  inline-size: 100%;
}

main:not(.app-main) > section + section {
  margin-block-start: var(--gap-section);
}

/* --- 16. utilities ----------------------------------------- */
.hidden,
[hidden] {
  display: none !important;
}

.visually-hidden {
  position: absolute !important;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: calc(var(--border-hairline) * -1);
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.text-muted {
  color: var(--text-muted);
}

.text-center {
  text-align: center;
}
