/* Pundown — design system v3 (warm palette, revised layout) */

/* ====== Tokens ====== */
:root {
  --peach: #DE6B48;
  --peach-deep: #C2532F;
  --clay: #E5B181;
  --blush: #F4B9B2;
  --tea: #DAEDBD;
  --sky: #7DBBC3;
  --sky-deep: #5FA3AC;

  --bg: #FBF7F1;
  --bg-elev: #FFFFFF;
  --bg-sunk: #F4EFE6;
  --ink: #2A211B;
  --ink-2: #4D423B;
  --ink-3: #7A6E66;
  --ink-4: #A8998E;
  --line: #E8DFD0;
  --line-strong: #D6C9B5;
  --danger: #C2532F;
  /* Readable danger TEXT colour (the per-theme overrides in themes.css set the real values; this is the
     no-theme fallback). Routed from a hardcoded #8a2b2b at the .is-bad / .btn-danger call sites. */
  --danger-ink: #8A2B2B;
  --live: #D62F2F;
  --good: #5C7F3F;
  /* Admin status hues — amber warn + danger alias (were referenced as undefined --warn/--bad). */
  --warn: #B5701F;
  --bad: var(--danger);
  /* The one star gold — the prompt-bank star, the Results dogear fold, and the stats-line star icon/count
     all share this single hue so "star" reads identically everywhere. */
  --star-gold: #FFC53D;

  --accent: var(--peach);
  --accent-soft: color-mix(in oklab, var(--peach) 18%, transparent);
  --accent-line: color-mix(in oklab, var(--peach) 70%, var(--ink) 5%);

  --radius: 8px;
  --radius-sm: 6px;
  --radius-md: 9px;
  --radius-lg: 10px;

  /* Elevation — soft, clearly-perceptible layered shadows (modernized 2026-06: the first pass at
     0.06–0.08 alpha was invisible on white cards over the near-white page bg). Per-theme overrides in
     themes.css tint these to the theme's ink hue (cool in light/Kingfisher, black in dark/Quest).
     --shadow-pop is for popovers/menus/modals. Strength lives entirely in these tokens — tune here. */
  --shadow-card: 0 1px 3px rgba(42,33,27,0.12), 0 8px 20px -4px rgba(42,33,27,0.20);
  --shadow-lift: 0 4px 10px -2px rgba(42,33,27,0.16), 0 20px 40px -8px rgba(42,33,27,0.28);
  --shadow-pop:  0 12px 28px -6px rgba(42,33,27,0.24), 0 32px 60px -16px rgba(42,33,27,0.34);

  /* One consistent keyboard-focus ring (was a mix of outline widths + box-shadow rings). */
  --focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent) 40%, transparent);

  /* Named stacking layers (was scattered magic z-index values 5 … 2500). */
  --z-sticky: 30;
  --z-topbar: 50;
  --z-popover: 60;
  --z-overlay: 2000;
  --z-modal: 2500;
  --z-fly: 3000;

  --display-font: "Newsreader", Georgia, serif;
  --ui-font: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono-font: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  --density-pad: 16px;
  --column-w: 720px;

  /* Type scale (additive — for incremental adoption; body stays 15px). Replaces the ad-hoc
     spread of ~20 one-off px sizes; new/refactored rules should pull from these steps. */
  --fs-2xs: 11px;
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 15px;
  --fs-md: 17px;
  --fs-lg: 20px;
  --fs-xl: 24px;
  --fs-2xl: 30px;
  --fs-3xl: 36px;

  /* Spacing scale (4px base; additive). */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  /* Sticky-topbar height. JS (pundown.topbar.fit) overwrites this on <html> with the measured
     header height; this base keeps the sticky sub-headers (.detail-sticky / .vote-head / .feed-group__head)
     and scroll-padding-top correct before the measure runs (and if JS never does). */
  --topbar-h: 96px;
  /* ONE consistent (tight) gap for the header rhythm AND each page's first content gap: wordmark→nav, the
     topbar's bottom padding, and topbar→top-of-content (`.main` padding-top) are all this value. The topbar
     has NO bottom border line anymore — the frosted/blurred background is the only separator from content. */
  --header-gap: 8px;
}
@media (min-width: 720px) { :root { --header-gap: 16px; } }

* { box-sizing: border-box; }
/* Any programmatic scroll-into-view / focus / #anchor jump lands BELOW the sticky topbar, not hidden under
   it: FocusOnNavigate focuses each page's <h1> on navigation, and the Results board scrolls a shared pun
   into view — without this the target would tuck under the fixed-height header. */
html { scroll-padding-top: var(--topbar-h); }
/* Never allow horizontal scrolling on a phone — clip the x-axis at the root (keeps vertical scroll +
   the sticky topbar working). Individual layouts below also guard their flex/grid children with
   min-width:0 so content shrinks to fit rather than forcing the page wider than the viewport. */
html, body { margin: 0; padding: 0; overflow-x: clip; max-width: 100%; }

/* <FocusOnNavigate Selector="h1"> (App.razor) moves focus to each page's <h1> on
   navigation — including the initial load — so screen readers announce the new view.
   The framework gives that <h1> tabindex="-1", so it is never keyboard-focusable; the
   only focus it ever receives is this programmatic one. Suppress the UA focus ring it
   would otherwise draw (the box around the Submit prompt on first visit). */
h1:focus { outline: none; }
body {
  font-family: var(--ui-font);
  background: var(--bg);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ===== App shell ===== */
/* Sticky footer: the column is at least a full (dynamic) viewport tall and `.main` GROWS to fill, so the
   footer always sits at the BOTTOM of the page — on a short page the slack becomes the space ABOVE the footer,
   never a footer floating mid-screen with a void beneath it. `100dvh` (with a `100vh` fallback for old
   browsers) tracks the mobile address bar so the footer lands on the true visible bottom, not below the fold. */
.app { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
/* `flex: 1 0 auto` — grow to absorb the slack (pinning the footer down), never shrink below the content.
   padding-top = --header-gap = the gap below the header's line to the top of every page's content. */
.main { flex: 1 0 auto; padding: var(--header-gap) 16px 24px; }
.main__inner { max-width: var(--column-w); margin: 0 auto; min-width: 0; }
@media (min-width: 720px) {
  .main { padding: var(--header-gap) 28px 56px; }
}
/* Pages whose FIRST item below the top bar IS a frozen (sticky `top: var(--topbar-h)`) header — Leaderboard
   (filter row), My Feed (first contest header), the User profile (identity card) — cancel `.main`'s top gap so
   that item starts flush at the stick point. Without this it begins --header-gap lower and visibly travels up
   that gap before sticking on the first scroll. Contest Results is deliberately NOT here: its date/share row
   scrolls away above the frozen prompt box, so that header is meant to travel up as the meta row collapses. */
.page-leaders,
.page-feed,
.page-user {
  margin-top: calc(-1 * var(--header-gap));
}

/* ===== Card surface (shared resting surface) =====
   One source of truth for the card family's bg + border + radius (was the identical trio re-declared
   in ~11 blocks). Declared HERE, early, on purpose: component rules and state modifiers later in the
   file (.result-row.is-top tint, .result-row--provisional dash, .result-row.is-shared ring, the
   .day-card / .user-pun :hover border-colour, the Admin .ops-card status border-colours) all layer ON
   TOP of this base and keep winning. `.card` is the explicit hook used by the Admin tiles
   (ops-card / pun-item / mod-item) that previously referenced an undefined `.card` and so had no
   surface at all — this gives them one. The companion resting/hover SHADOW lives in the Phase-1
   elevation block at the very end of this file. Tune the card edge here (--line) + the page contrast
   in themes.css (--bg). */
.card,
.result-row, .leader-row, .user-pun, .group-card, .day-card, .my-pun-card,
.profile-field, .profile-hero, .group-join-card, .superlative {
  background: var(--bg-elev);
  /* Softened hairline (was a full-strength 1px var(--line)): the resting shadow + the deeper page bg
     now carry the card edge, so the border recedes to a whisper and the surface reads *lifted* rather
     than *outlined*. State modifiers (.is-top / .is-shared / provisional / ops status) still set their
     own full-strength border-colour and win. Tune the whisper here. */
  border: 1px solid color-mix(in oklab, var(--line) 50%, transparent);
  border-radius: var(--radius);
}

/* ===== Topbar — compact, two rows ===== */
.topbar {
  position: sticky; top: 0; z-index: var(--z-topbar);
  background: color-mix(in oklab, var(--bg) 92%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  /* No bottom border line (removed per request) — the frosted/blurred bg is the only separator from content. */
}
.topbar__row {
  /* +56px = the 28px horizontal padding on each side, so the row's *content box*
     is a full --column-w (720px) and its right edge lines up with .main__inner
     (whose padding sits on the outer .main, outside its 720px). */
  max-width: calc(var(--column-w) + 56px); margin: 0 auto;
  /* Symmetric --header-gap top/bottom. Both the wordmark and the right cluster are absolutely centered on
     this row, so they contribute no height — min-height supplies it. With box-sizing:border-box, min-height
     INCLUDES the padding, so it's `2·gap + wordmark-height` (26px mobile / 32px desktop = the .brand font
     size): that leaves the content box exactly the wordmark's height, so the centered wordmark sits with
     --header-gap clear above (→ nav... wait, top) and below it → top→wordmark and wordmark→nav both = gap. */
  padding: var(--header-gap) 16px;
  display: flex; align-items: center; gap: 12px;
  position: relative;
  min-height: calc(2 * var(--header-gap) + 26px);
}
@media (min-width: 720px) {
  .topbar__row { padding: var(--header-gap) 28px; min-height: calc(2 * var(--header-gap) + 32px); }
}

.brand {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: baseline;
  color: var(--ink); text-decoration: none;
  font-family: var(--display-font);
  font-size: 26px; line-height: 1;
  font-weight: 500;
  letter-spacing: -0.02em;
  font-style: italic;
  pointer-events: auto;
}
/* Trademark superscript. The .brand is an inline-flex baseline row, so a <sup>'s default
   vertical-align is ignored — ride the top of the wordmark via align-self instead, then nudge
   down a touch so it sits as a superscript over the cap height rather than floating above it. */
.brand__tm {
  align-self: flex-start;
  margin-left: 1px;
  margin-top: 0.12em;
  font-size: 0.42em;
  font-style: normal;
  font-weight: 500;
  line-height: 1;
  opacity: 0.65;
}
@media (min-width: 720px) {
  .brand { font-size: 32px; }
}
/* Narrow screens: when the centered wordmark would slide under the upper-right icon cluster, JS
   (pundown.topbar.fit) adds .is-shifted and sets --brand-shift-right = cluster width + gap, anchoring the
   wordmark's right edge just left of the icons instead of centering it. Removed again once there's room. */
.brand.is-shifted {
  left: auto;
  right: var(--brand-shift-right, 0px);
  transform: translateY(-50%);
}

/* Absolutely centered on the row's vertical midline — the SAME reference the wordmark uses (top:50% of the
   row) — so the top-right buttons sit vertically aligned with the "Pundown." wordmark regardless of the row
   padding. right = the row's horizontal padding, so the cluster keeps its prior x-position. (Absolute still
   establishes the containing block for the profile popover inside it.) */
.topbar__right {
  position: absolute;
  top: 50%; right: 16px; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 8px;
  /* The translateY centering makes this a STACKING CONTEXT, which would otherwise trap the profile
     dropdown's z-index inside it so the (later-in-DOM) nav row paints over the open menu. A z-index here
     lifts the whole cluster — and the popover it contains — above the nav within the top bar. */
  z-index: 1;
}
@media (min-width: 720px) { .topbar__right { right: 28px; } }
.icon-btn {
  appearance: none; border: 0; background: transparent;
  width: 36px; height: 36px; border-radius: var(--radius);
  color: var(--ink-2); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms;
  position: relative;
}
.icon-btn:hover, .icon-btn.is-open { background: var(--bg-sunk); color: var(--ink); }

/* Top-bar controls are one consistent size: the theme toggle, the (signed-out)
   Help button, and the avatar are equal-diameter bordered circles, and the
   Register button matches their height. --topbar-ctl is that shared height. */
.topbar__right { --topbar-ctl: 32px; }
.topbar__right .icon-btn {
  width: var(--topbar-ctl); height: var(--topbar-ctl);
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--bg-elev);
}
.topbar__right .icon-btn:hover, .topbar__right .icon-btn.is-open {
  background: var(--bg-sunk); color: var(--ink);
}
.topbar__register { height: var(--topbar-ctl); }
/* Avatar: the disc itself is the bordered circle, so the button is borderless
   and exactly disc-sized (the disc is rendered at Size=32 to match). The extra
   class keeps this ahead of the `.topbar__right .icon-btn` border above. */
.topbar__right .topbar__avatar-btn {
  width: var(--topbar-ctl); height: var(--topbar-ctl);
  border: 0; background: transparent; border-radius: 50%;
}

/* ===== Nav row ===== */
.nav {
  display: flex;
  gap: 2px;
  /* No top padding (the wordmark→nav gap is fully owned by the row's bottom padding); bottom = --header-gap
     so nav→line equals the rest of the header rhythm. */
  padding: 0 12px var(--header-gap);
  max-width: calc(var(--column-w) + 56px);
  margin: 0 auto;
  justify-content: center;
  /* The nav must NEVER wrap to a second line (esp. on narrow mobile) — it shrinks to fit instead (smaller
     padding/font below), with horizontal scroll as the only fallback on an ultra-narrow screen so a line is
     always preserved rather than wrapping. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
@media (min-width: 720px) {
  .nav { padding: 0 28px var(--header-gap); }
}
.nav__item {
  position: relative;
  appearance: none; background: transparent; border: 0;
  font: inherit; color: var(--ink-3);
  padding: 6px 9px; border-radius: 999px;
  cursor: pointer; font-weight: 500; font-size: var(--fs-sm);
  transition: color 120ms;
  white-space: nowrap;
  flex-shrink: 0;
  text-decoration: none;   /* NavLink renders <a>; kill the default underline */
}
/* Tighten further on the narrowest phones so all five/six tabs still fit one line. */
@media (max-width: 480px) {
  .nav { gap: 1px; padding-left: 8px; padding-right: 8px; }
  .nav__item { padding: 6px 7px; font-size: var(--fs-xs); }
}
@media (min-width: 720px) {
  .nav__item { padding: 7px 14px; font-size: 14px; }
}
.nav__item:hover { color: var(--ink); }
/* Active tab is a quiet bold ink label — no pill, no underline. */
.nav__item.is-active {
  background: transparent;
  color: var(--ink);
  font-weight: 700;
}

/* ===== Profile popover ===== */
.profile-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: var(--z-popover);
  min-width: 196px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px -10px rgba(42,33,27,0.22), 0 2px 4px rgba(42,33,27,0.04);
  padding: 6px;
  display: flex; flex-direction: column;
  animation: pop-in 160ms cubic-bezier(.2,.7,.2,1);
  transform-origin: top right;
}
@keyframes pop-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
}
.profile-pop__item {
  appearance: none; background: transparent; border: 0;
  font: inherit; color: var(--ink-2);
  text-align: left;
  padding: 9px 12px; border-radius: var(--radius-sm);
  cursor: pointer; font-size: 14px;
  display: flex; align-items: center; gap: 10px;
  transition: background 120ms, color 120ms;
}
.profile-pop__item:hover { background: var(--bg-sunk); color: var(--ink); }
.profile-pop__item svg { color: var(--ink-3); flex-shrink: 0; }
.profile-pop__item:hover svg { color: var(--ink-2); }
.profile-pop__sep {
  height: 1px;
  background: var(--line);
  margin: 4px 6px;
}
.profile-pop__head {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}
.profile-pop__head-text { line-height: 1.2; min-width: 0; }
.profile-pop__name {
  font-weight: 600; font-size: 14px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.profile-pop__city { color: var(--ink-3); font-size: var(--fs-xs); }

/* ===== Page heads ===== */
.page-head { margin-bottom: 14px; }
.page-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink-3); font-size: var(--fs-xs);
  letter-spacing: 0.06em; text-transform: uppercase; font-weight: 500;
  margin-bottom: 6px;
  /* Left padding leaves room for the live-dot's 3px box-shadow ring, which
     overflow:hidden (added to truncate long dates) would otherwise clip. */
  padding-left: 4px;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dot-live, .dot-revealed {
  flex-shrink: 0;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--peach);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--peach) 22%, transparent);
}
.dot-revealed { background: var(--good); box-shadow: 0 0 0 3px color-mix(in oklab, var(--good) 22%, transparent); }
.page-title {
  font-family: var(--display-font);
  font-weight: 500;
  font-size: clamp(26px, 4.6vw, 36px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 6px;
  overflow-wrap: anywhere;
}
.page-sub { color: var(--ink-3); margin: 0; font-size: 14px; overflow-wrap: anywhere; }

/* Prerender-only SEO copy (home page / Vote.razor): present in the DOM + accessibility tree for crawlers
   and screen readers, but painted off-screen so sighted users only see the Loading card during hydration
   — no marketing-text flash before the voting UI swaps in. Standard "visually hidden" / sr-only pattern. */
.seo-intro {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===== Submit screen ===== */
.submit-prompt {
  font-family: var(--display-font);
  font-weight: 600;
  font-size: clamp(20px, 4.4vw, 24px);
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 4px 0 14px;
  text-wrap: pretty;
  overflow-wrap: anywhere;
}

/* ===== Compose ===== */
.compose { margin-bottom: 10px; }
.compose__box {
  border: 1px solid var(--line-strong);
  background: var(--bg-elev);
  border-radius: var(--radius);
  transition: border-color 140ms, box-shadow 140ms;
}
.compose__box:focus-within {
  border-color: var(--peach);
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--peach) 22%, transparent);
}
.compose__textarea {
  width: 100%; resize: none;
  border: 0; background: transparent;
  font: inherit; padding: 10px 14px 6px;
  color: var(--ink);
  font-size: 16px; line-height: 1.4;
  outline: none;
  font-family: var(--display-font);
  height: 64px;
}
.compose__textarea::placeholder { color: var(--ink-4); font-style: italic; }
.compose__footer {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 10px 8px;
  border-top: 1px solid var(--line);
}
.compose__count {
  font-size: 11.5px; color: var(--ink-3); font-feature-settings: "tnum";
  font-family: var(--mono-font);
}
.compose__count.is-warn { color: var(--danger); }
.compose__actions { display: flex; gap: 8px; }

.compose-meta {
  text-align: center;
  font-size: 12.5px;
  color: var(--ink-3);
  margin: 8px 0 14px;
}
.compose-meta strong {
  color: var(--ink);
  font-family: var(--mono-font);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
}

.flash {
  margin-top: 8px;
  padding: 6px 10px; border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--tea) 50%, var(--bg-elev));
  font-size: 12.5px; color: var(--ink);
  border: 1px solid color-mix(in oklab, var(--tea) 70%, var(--ink) 10%);
  animation: flash-in 200ms ease-out;
  text-align: center;
}
@keyframes flash-in { from { opacity: 0; transform: translateY(-2px); } }

/* ===== Buttons ===== */
.btn {
  appearance: none; border: 1px solid transparent;
  font: inherit; font-weight: 500;
  padding: 8px 14px; border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 120ms, border-color 120ms, transform 80ms, color 120ms;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  line-height: 1;
  white-space: nowrap;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:not(:disabled):active { transform: translateY(1px); }
.btn-primary { background: var(--peach); color: #fff; }
.btn-primary:not(:disabled):hover { background: var(--peach-deep); }
.btn-ghost { background: transparent; color: var(--ink-2); border-color: var(--line-strong); }
.btn-ghost:not(:disabled):hover { background: var(--bg-sunk); color: var(--ink); }
.btn-sm { padding: 6px 11px; font-size: var(--fs-sm); }
.btn-xs { padding: 5px 10px; font-size: 12.5px; }
.btn-block { width: 100%; }
/* One consistent keyboard-focus ring (via the --focus-ring token) on the controls that can render a
   box-shadow ring — i.e. NOT clipped by a card's overflow:hidden. Card buttons + the icon-button family
   keep a clip-safe outline instead (see the icon-button base + the .result-row--btn outlines). */
.btn:focus-visible,
.icon-btn:focus-visible,
.nav__item:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
/* The icon-button family (flag / share / delete / edit / back, across Vote / Results / Submit / Groups /
   Profile) shares one keyboard-focus treatment. Outline (not the box-shadow ring) so it is never clipped
   by a card's overflow:hidden. These buttons still own their size / colour / position individually. */
.flag-btn:focus-visible, .choice__share:focus-visible, .result-flag-btn:focus-visible,
.my-pun-card__icon-btn:focus-visible, .result-row__share:focus-visible, .group-card__share:focus-visible,
.back-btn:focus-visible, .profile-hero__edit-btn:focus-visible, .detail-head__share:focus-visible,
.user-head__share:focus-visible, .result-del:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}

/* ===== My puns list (newest on top) ===== */
.my-puns { margin-top: 6px; }
.my-puns__head {
  text-align: center;
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-2);
  margin: 0 0 8px;
}
/* Heading + the "view and vote" shortcut at the top of the submissions list (easy access). Both are
   centered: a centered row on a wide column, stacking to centered lines when the column is narrow. */
.my-puns__top {
  display: flex; align-items: baseline; justify-content: center;
  gap: 12px; flex-wrap: wrap; margin-bottom: 8px;
}
.my-puns__top .my-puns__head { margin: 0; text-align: center; }
.my-puns__vote-link {
  font-size: var(--fs-sm); font-weight: 600; color: var(--accent-line); text-decoration: none;
}
.my-puns__vote-link:hover { text-decoration: underline; }
.my-pun-list {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-direction: column;
  gap: 8px;
}
.my-pun-card {
  padding: 14px 12px;
  min-height: 88px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px;
  position: relative;
  overflow: hidden;
  text-align: center;
}
.my-pun-card__quote {
  font-family: var(--display-font);
  font-size: 16px;
  line-height: 1.3;
  color: var(--ink);
  margin: 0;
  text-wrap: pretty;
  overflow-wrap: anywhere;
  letter-spacing: -0.005em;
  text-align: center;
}
.my-pun-card__meta {
  display: flex; gap: 8px; align-items: center; justify-content: center;
  font-size: 11.5px; color: var(--ink-3);
  text-align: center;
}
.status-chip {
  padding: 2px 8px; border-radius: 999px;
  background: color-mix(in oklab, var(--tea) 60%, var(--bg-elev));
  color: var(--good); font-weight: 500; font-size: var(--fs-2xs);
  margin-left: auto;
  border: 1px solid color-mix(in oklab, var(--tea) 70%, transparent);
}
.my-pun-list__empty {
  background: var(--bg-sunk);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 18px 14px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13.5px;
  min-height: 88px;
  display: flex; align-items: center; justify-content: center;
}
.my-pun-list-foot {
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin-top: 8px;
  font-feature-settings: "tnum";
  font-family: var(--mono-font);
  letter-spacing: 0.04em;
}
.my-pun-list-foot strong {
  color: var(--ink);
  font-weight: 600;
}

/* ===== Avatar ===== */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-weight: 600;
  letter-spacing: 0.02em;
  flex-shrink: 0;
}

/* ===== Pun quote ===== */
.pun-quote {
  font-family: var(--display-font);
  font-size: 19px; line-height: 1.35;
  letter-spacing: -0.005em;
  margin: 0;
  color: var(--ink);
  text-wrap: pretty;
  overflow-wrap: anywhere;
}
.muted { color: var(--ink-3); }

/* ===== Vote page ===== */
.vote-prompt {
  text-align: center;
  font-family: var(--display-font);
  font-weight: 500;
  font-size: clamp(20px, 4.4vw, 24px);
  letter-spacing: -0.015em;
  line-height: 1.2;
  margin: 6px 0 12px;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.vote-stack {
  display: flex; flex-direction: column; gap: 4px;
  align-items: stretch;
}
.choice {
  position: relative;
  appearance: none; text-align: center;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 40px var(--density-pad);
  font: inherit;
  cursor: pointer;
  box-shadow: var(--shadow-card);
  transition: border-color 160ms, box-shadow 200ms, transform 220ms, opacity 220ms, background 160ms;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 116px;
}
/* On hover, outline the whole card in the contest accent (the same colour as its left stripe) rather than
   a grey — so the box just gains a coloured outline, matching the prompt/stripe/picked colour. Scoped to
   real pointers: on touch devices :hover sticks to the last-tapped card after a vote, which would leave a
   stray accent outline + lift shadow as a "remnant" of the previous pick. Gating on (hover: hover) keeps
   the cards identical at rest on mobile. */
@media (hover: hover) {
  .choice:hover {
    border-color: var(--vote-accent);
    box-shadow: var(--shadow-lift);
  }
}
.choice--a { border-left: 4px solid var(--vote-accent); }
.choice--b { border-left: 4px solid var(--vote-accent); }
.choice__label {
  position: absolute; top: 12px; right: 14px;
  font-family: var(--mono-font);
  font-size: 11.5px; font-weight: 600;
  color: var(--ink-3);
  letter-spacing: 0.1em;
}
.choice__quote {
  font-size: 19px; line-height: 1.32;
  text-align: center;
  margin: 0;
  overflow-wrap: anywhere;
}
@media (min-width: 720px) {
  .choice__quote { font-size: 23px; }
  .choice { min-height: 140px; }
}
/* On pick, the whole box inverts to a bold fill in the per-contest accent (--vote-accent, same colour as
   the prompt box + the card's left stripe) with contrasting text (--vote-on-accent) — an unmistakable,
   contest-coloured confirmation. */
.choice--a.is-picked,
.choice--b.is-picked {
  border-color: transparent;
  background: var(--vote-accent);
  color: var(--vote-on-accent);
  box-shadow: 0 10px 26px -8px color-mix(in oklab, var(--vote-accent) 55%, transparent);
  transform: translateY(-2px) scale(1.01);
}
/* The pun text sets its own color (.pun-quote { color: var(--ink) }), so the inverted fill's contrasting
   text wouldn't reach it without this — keep it legible on the accent fill. */
.choice.is-picked .choice__quote { color: var(--vote-on-accent); }
/* Keep the action icons legible on the inverted fill. */
.choice.is-picked .choice__act { color: color-mix(in oklab, var(--vote-on-accent) 88%, transparent); }
.choice.is-loser {
  opacity: 0.34;
  transform: scale(0.985);
}
.choice:disabled { cursor: default; }
/* When a flag popover is open on a vote card, lift the card to the popover layer (`--z-popover`, ABOVE the top
   bar) so the popover — which extends UP from the bottom-left flag, and on a SHORT card reaches past the frozen
   prompt banner right into the header — always shows in FRONT of everything. Only while open (`:has`); the card
   is not `overflow:hidden`, so there's no clip to undo. */
.choice:has(.flag-pop) { z-index: var(--z-popover); }


/* Flag button at bottom-right of vote card (default) — left variant moves to bottom-left */
.flag-btn {
  position: absolute;
  right: 10px; bottom: 10px;
  appearance: none;
  width: 34px; height: 34px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid transparent;
  /* Neutral — the flag now matches the share icon on the same card (.choice__share), not the old report-red. */
  color: var(--ink-3);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.flag-btn--left { right: auto; left: 10px; }
@media (hover: hover) {
  .flag-btn:hover {
    background: var(--bg-sunk);
    color: var(--ink);
    border-color: var(--line-strong);
  }
}
.flag-btn.is-flagged {
  color: var(--ink);
  background: var(--bg-sunk);
}

/* Share button on each Vote card — bottom-right */
.choice__share {
  position: absolute;
  right: 10px; bottom: 10px;
  appearance: none;
  width: 34px; height: 34px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--ink-3);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
/* Match the Results-row share glyph (18px); the Vote ShareIcon's inline SVG is 15px. The flag keeps its
   inline 16px — the same glyph size as the Results-row flag. */
.choice__share svg { width: 18px; height: 18px; }
@media (hover: hover) {
  .choice__share:hover {
    background: var(--bg-sunk);
    color: var(--ink);
    border-color: var(--line-strong);
  }
}

/* Action strip pinned to the bottom of each vote card: the flag sits in the bottom-LEFT corner and the
   share in the bottom-RIGHT corner (the star is the separate top-right dogear). The strip is absolutely
   pinned to the bottom so the pun text stays vertically centered in the card above it. */
.choice__actions {
  position: absolute;
  /* Span the card's full bottom band and pin the flag + share to the bottom corners (both absolutely
     positioned below) — mirroring the top-right dogear star so all three icons share the same corner
     inset. Sit flush to the bottom-inner edge (bottom:0) and stand one button tall (34px) so the icons
     hug the bottom corners exactly like the Results row. */
  left: 0; right: 0; bottom: 0;
  height: 34px;
  /* The strip spans the card's full bottom band, but only the flag + share buttons should be
     non-votable. Without this, the empty center between them swallows clicks (the strip carries
     @onclick:stopPropagation) while still showing the card's hand cursor — a dead zone. Let the center
     pass clicks through to the card (vote registers); re-enable hits on the buttons themselves. The
     container's stopPropagation still fires while a button click bubbles through, so taps on the icons
     don't also cast a vote. */
  pointer-events: none;
}
.choice__actions .choice__act { pointer-events: auto; }
/* Flag pinned to the bottom-LEFT corner, share to the bottom-RIGHT corner — same 34px button size and
   corner placement as the Results row (where the share lines up under the top-right dogear and the flag
   mirrors it on the left). */
.choice__actions .flag-btn {
  position: absolute; left: 0; bottom: 0; top: auto; right: auto;
}
/* Share absolutely pinned to the bottom-right corner. `position: absolute` (NOT static) also keeps it a
   POSITIONING CONTEXT for its coarse-pointer touch-target `::after` (below): when it was static, that
   `::after` anchored to the full-width strip, so its invisible -7px hit area covered the WHOLE strip —
   including the flag — and a touch tap on the flag fired SHARE. */
.choice__actions .choice__share {
  position: absolute; right: 0; bottom: 0; top: auto; left: auto;
}

/* Flag popover — default anchors to bottom-right of card; --left variant anchors to bottom-left */
.flag-pop {
  position: absolute;
  right: 6px; bottom: 44px;
  width: 232px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 16px 36px -12px rgba(42,33,27,0.28), 0 2px 4px rgba(42,33,27,0.06);
  padding: 12px 12px 10px;
  z-index: 20;
  animation: pop-in 140ms cubic-bezier(.2,.7,.2,1);
  transform-origin: bottom right;
}
.flag-pop--left {
  right: auto;
  left: 6px;
  transform-origin: bottom left;
}
.flag-pop__title {
  font-size: var(--fs-sm); font-weight: 500;
  color: var(--ink); margin: 0 0 10px;
  line-height: 1.3;
}
.flag-pop__actions {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.flag-pop::after {
  /* small arrow */
  content: ""; position: absolute;
  right: 12px; bottom: -6px;
  width: 10px; height: 10px;
  background: var(--bg-elev);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  transform: rotate(45deg);
}
.flag-pop--left::after {
  right: auto;
  left: 12px;
}

/* All caught up */
.all-done-card {
  margin-top: 24px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px var(--density-pad);
  text-align: center;
}
.all-done-card__num {
  font-family: var(--display-font);
  font-size: 56px; line-height: 1;
  letter-spacing: -0.03em;
  color: var(--peach);
}
.all-done-card__label {
  color: var(--ink-3); font-size: var(--fs-sm); margin: 4px 0 18px;
  letter-spacing: 0.04em; text-transform: uppercase;
}

/* ===== Results — daily list ===== */
.day-list {
  list-style: none; padding: 0; margin: 6px 0 0;
  display: grid; gap: 10px;
}
.day-card {
  width: 100%;
  padding: 12px 14px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  display: grid;
  grid-template-columns: 1fr 132px auto;
  grid-template-rows: auto auto auto;
  column-gap: 14px;
  row-gap: 6px;
  transition: border-color 140ms, box-shadow 200ms, transform 100ms;
  position: relative;
}
/* Finalized days carry a podium on the right. Give that column ~1/3 of the card (2fr content : 1fr
   podium) so its left-border divider sits a third in from the edge and the winners' names have room.
   Live/pending cards have no podium, so they keep the slim fixed slot above. The narrow breakpoint below
   collapses both to a single column (the podium stacks underneath). */
.day-card--has-podium {
  grid-template-columns: 2fr 1fr auto;
}
.day-card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--shadow-card);
}
.day-card:active { transform: translateY(1px); }
.day-card__date {
  grid-column: 1; grid-row: 1;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600;
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
  flex-wrap: wrap;
}
.day-card__live {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--mono-font); font-weight: 700;
  font-size: 10.5px; letter-spacing: 0.08em;
  color: var(--live);
  padding: 2px 7px; border-radius: 3px;
  border: 1px solid color-mix(in oklab, var(--live) 40%, transparent);
  background: color-mix(in oklab, var(--live) 8%, var(--bg-elev));
}
.day-card__live::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--live);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--live) 22%, transparent);
  animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in oklab, var(--live) 24%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in oklab, var(--live) 6%,  transparent); }
}
.day-card__count {
  grid-column: 1; grid-row: 3;
  font-size: 12.5px;
  color: var(--ink-3);
}
.day-card__count strong {
  color: var(--ink); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.day-card__chev {
  grid-column: 3; grid-row: 1 / span 3;
  align-self: center;
  color: var(--ink-4);
  display: flex; align-items: center; justify-content: center;
  transition: color 120ms, transform 120ms;
}
.day-card:hover .day-card__chev { color: var(--ink-2); transform: translateX(2px); }

/* ===== Day card podium (1st/2nd/3rd on the right) ===== */
.day-card__podium {
  grid-column: 2; grid-row: 1 / span 3;
  align-self: center;
  list-style: none; padding: 0; margin: 0;
  display: grid;
  gap: 3px;
  padding-left: 12px;
  border-left: 1px solid var(--line);
  min-width: 0;
}
.day-card__podium-row {
  display: grid;
  grid-template-columns: 20px 1fr;
  align-items: center;
  column-gap: 8px;
  font-size: var(--fs-xs);
  line-height: 1.2;
  min-width: 0;
}
.day-card__medal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px; height: 16px;
  font-size: 14px;
  line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.day-card__podium-name {
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* Give the winner extra weight so the top finisher reads at a glance. */
.day-card__podium-row:first-child .day-card__podium-name { font-weight: 800; }
.day-card__podium-row:first-child .day-card__medal { font-size: var(--fs-md); width: 22px; }
.day-card__podium-city {
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 110px;
  font-size: var(--fs-2xs);
}

@media (max-width: 520px) {
  /* Both variants collapse to one content column on narrow screens; the podium re-flows to a full-width
     row beneath, divided by a top border instead of the left border (vertical stack). The --has-podium
     selector must appear here too — it's more specific than .day-card, so without it the wide 1/3 grid
     would persist on mobile. */
  .day-card,
  .day-card--has-podium {
    grid-template-columns: 1fr auto;
  }
  .day-card__podium {
    grid-column: 1 / -1;
    grid-row: 4;
    padding-left: 0;
    border-left: 0;
    padding-top: 6px;
    margin-top: 2px;
    border-top: 1px solid var(--line);
  }
  .day-card__podium-name { max-width: none; }
  .day-card__chev { grid-column: 2; }
}

/* ===== Results detail (slides in over list) ===== */
.detail-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  background: var(--bg);
  z-index: 5;
  animation: detail-slide 280ms cubic-bezier(.2,.7,.2,1);
}
@keyframes detail-slide {
  from { transform: translateY(4px); opacity: 0; }
}
@media (min-width: 720px) {
  @keyframes detail-slide {
    from { transform: translateX(24px); opacity: 0; }
  }
}
.detail-head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px;
  min-width: 0;
}
.detail-head > div { min-width: 0; }
.back-btn {
  appearance: none; border: 1px solid var(--line-strong);
  background: var(--bg-elev);
  width: 36px; height: 36px; border-radius: var(--radius);
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
  flex-shrink: 0;
}
.back-btn:hover { background: var(--bg-sunk); color: var(--ink); }
.detail-head__title {
  font-family: var(--display-font);
  font-weight: 500;
  font-size: 22px;
  letter-spacing: -0.02em;
  margin: 0;
  line-height: 1.1;
  overflow-wrap: anywhere;
}
.detail-head__meta {
  font-size: 12.5px;
  color: var(--ink-3);
  margin: 2px 0 0;
  letter-spacing: 0.02em;
  overflow-wrap: anywhere;
}
/* Result list rows */
.result-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; }
.result-row {
  display: block;
  padding: 6px 14px;           /* one common 6px gap top + bottom (≈ the dogear star's ~6px inset) → short, balanced box */
  position: relative;
  overflow: hidden;            /* clip the folded "dogear" star corner to the card's rounded corner */
  min-height: 72px;
  transition: border-color 140ms, background 140ms;
}
/* A flag popover lives INSIDE the row, but the row clips its overflow (for the dogear corner) and is a plain
   sibling in the stacking order — so the popover gets clipped at the row's top edge and reads as "slipping
   behind" the row above (and, near the top, behind the frozen prompt banner + filters, or even the header).
   While a popover is open, drop the clip + lift the row to the popover layer (`--z-popover`, ABOVE the top bar)
   so the popover always shows in full and in front. (`:has` is supported in all current browsers; older ones
   simply keep the prior clipped behavior.) */
.result-row:has(.flag-pop) { overflow: visible; z-index: var(--z-popover); }
/* On the Results board, tuck the rank circle in so its gap to the card's LEFT edge matches its 6px gap to the
   TOP edge (was 14px left / 6px top) — a squarer inset that also hands the pun ~8px more width. The bottom
   stats bar's negative-margin full-bleed is measured from this same (now-shifted) content box, so its left-most
   control (trash / flag) slides left by the same 8px and stays centred under the circle for free. Scoped to the
   detail board so the shared .result-row layout on My Feed / the User page (whose absolutely-pinned
   del-strip / actions assume the 14px inset) is left alone. */
.page-results-detail .result-row { padding-left: 6px; }
/* Rank badge pinned upper-left, inline with the pun text (the quote flows to its right and wraps under
   the dogear when the pun is starrable). Author + stats stack below. */
.result-row__head { display: flex; align-items: flex-start; gap: 10px; }
.result-row__head .result-row__quote { flex: 1 1 auto; min-width: 0; }
.result-row--starrable .result-row__head { padding-right: 34px; }   /* keep the first line clear of the dogear */
/* Author attribution (finalized boards only): avatar + name + location, below the pun, aligned under the
   quote (past the rank badge). */
/* Author attribution row (finalized only): avatar | name+location | follow "+" | points-earned. The outer
   row never wraps; the name+location id-block wraps INTERNALLY so, when there isn't room for everything,
   the location drops to a second line left-aligned under the username while the "+" and points stay on the
   first line. Points are pinned to the far right (the end of the author line). */
.result-row__author {
  display: flex; align-items: flex-start; flex-wrap: nowrap;
  column-gap: 7px; row-gap: 2px;
  margin: 6px 0 0 36px; font-size: 14px; color: var(--ink-2);   /* common 6px gap below the pun */
}
.result-row__author-id {
  display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 0 1 auto;
}
/* Username + the "+" on one line, vertically centered together. */
.result-row__author-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.result-row__author-name { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-row__author-loc { font-size: var(--fs-sm); }   /* always on its own line beneath the username */
.result-row__author .result-row__follow { flex: 0 0 auto; }
.result-row__pts {
  margin-left: auto; flex: 0 0 auto; align-self: flex-start;
  white-space: nowrap; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* User-profile pun: contest prompt + date on the left, points pinned to the right (mirrors the Results
   author line); indented under the quote, past the rank badge. */
.result-row__contest { display: flex; align-items: flex-start; gap: 10px; margin: 6px 0 0 36px; }
.result-row__contest-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.result-row__contest .result-row__pts { margin-left: auto; align-self: flex-start; }
/* The contest prompt is a link-styled button → that contest's Results day. */
.user-pun__prompt-link { appearance: none; border: 0; background: transparent; padding: 0; cursor: pointer; text-align: left; }
.user-pun__prompt-link:hover { text-decoration: underline; text-underline-offset: 2px; }
/* The bottom stats bar: own-delete / flag (unfinalized) at the LEFT, then the shared
   rating·wins·win-rate·stars line + the larger share button grouped at the RIGHT. The stats sit right up
   against the share (no longer box-centred), so the star-count→share gap reads the same as the win%→star gap
   inside the line; the whole line — basic or advanced — moves as one unit, vertically centred with the share. */
.result-row__statsbar {
  display: flex;
  align-items: center;          /* stats line + share share one vertical centre */
  /* Tight vertical rhythm: the gap above (6px) = the gap below (the card's 6px bottom padding) = the dogear
     star's ~6px top inset, so the box is short and balanced. Negative side margins full-bleed the bar past
     the card's 14px h-padding so the corner controls line up with the dogear corner — the share icon sits
     directly under the top-right star, with the trash/flag mirrored at the same inset on the left. The ~2px
     overflow is clipped by the card's overflow:hidden. */
  margin: 6px -16px 0;
}
/* Push the stats (and the share after them) hard right; the auto margin absorbs the slack to their left (after
   any delete/flag control). The share then sits ~one stat-gap to the right of the star count — 3px of box
   margin lands the share GLYPH ≈11px from the count (the glyph is centred in its 34px button), matching the
   win%→star spacing inside the line. */
.result-row__statsbar > .pun-stats { margin-left: auto; justify-content: flex-end; min-width: 0; }
.result-row__statsbar > .result-row__share { margin-left: 3px; flex-shrink: 0; }
.result-own-del { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* Align the bottom-left delete/flag icon under the rank circle above it: the statsbar full-bleeds to the
   card's left edge, so nudge the left control in ~12px so its 34px icon's center lands on the 26px rank
   badge's center (instead of hugging the card edge under the dogear's mirror). */
.result-row__statsbar > .result-own-del,
.result-row__statsbar > .result-flag { margin-left: 12px; }
.result-row__statsbar .result-row__share,
.result-row__statsbar .result-del { width: 34px; height: 34px; }
.result-row__statsbar .result-row__share svg,
.result-row__statsbar .result-del svg { width: 18px; height: 18px; }
/* Flag control on an unfinalized Results row (someone else's pun): same icon + "why flag?" popover as the
   voting flag, anchored at the left of the stats bar. Unlike voting, it leaves the pun on the board. */
.result-flag { position: relative; display: inline-flex; flex-shrink: 0; }
.result-flag-btn {
  appearance: none; background: transparent; border: 1px solid transparent;
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  /* Neutral — the flag matches the share icon on the same row (.result-row__share), not the old report-red. */
  color: var(--ink-4); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.result-flag-btn:hover {
  background: var(--bg-sunk);
  color: var(--ink-2);
  border-color: var(--line-strong);
}
.result-flag-btn.is-flagged {
  color: var(--ink-2);
  background: var(--bg-sunk);
}
/* Anchor the shared .flag-pop just above the flag button (its default offsets assume the big vote card as
   the positioned parent; here the parent is the small button wrapper). */
.result-flag .flag-pop { bottom: calc(100% + 8px); left: 0; right: auto; }
/* The clickable top-right star (replaces the inline results star for puns that aren't the viewer's own). The
   button is a transparent triangle (clip-path) acting as a generous corner tap target; only the STAR glyph in
   its corner is ever visible — the folded "dogear" background was removed per request. The star fills gold
   once starred. */
.result-dogear {
  position: absolute; top: 0; right: 0;
  width: 50px; height: 50px;
  padding: 0; border: 0; cursor: pointer; appearance: none;
  /* No visible fold in EITHER state — the triangle is transparent, so only the star shows. The clip-path
     stays purely as the invisible click target; the star sits in its corner. */
  background: transparent;
  clip-path: polygon(100% 0, 0 0, 100% 100%);
  transition: background 140ms;
  z-index: 2;
}
.result-dogear__star {
  position: absolute; top: 6px; right: 6px;
  display: inline-flex; color: var(--ink-4); pointer-events: none;   /* faint glyph while unstarred */
  transition: color 140ms, transform 140ms;
}
.result-dogear__star svg { width: 18px; height: 18px; display: block; }   /* match the prompt-bank star size */
/* Hover feedback only on real pointer devices — gated behind @media (hover: hover) so a TOUCH tap doesn't
   leave a "sticky" :hover on the dogear that then carries onto the next ballot's fresh dogear at the same
   screen position (the Vote screen re-renders a new dogear there right after a star is cast). */
@media (hover: hover) {
  /* Hover feeds back only through the STAR (a darken + slight scale) — no fold appears, since the dogear
     background stays transparent. */
  .result-dogear:hover:not(:disabled):not(.is-picked) .result-dogear__star { color: var(--ink-2); transform: scale(1.06); }
}
/* STARRED: no fold at all. Once a pun is starred, drop the triangular fold + its gold border entirely — the
   fold is the "tap to star" affordance, so a starred pun keeps only the gold star sitting in the corner
   (the base clip-path stays for the click target; the hover-fold rules above are gated off .is-picked). */
.result-dogear.is-picked { background: transparent; }
.result-dogear.is-picked .result-dogear__star { color: var(--star-gold); }
.result-dogear.is-picked .result-dogear__star .star-glyph { fill: currentColor; }
.result-dogear:disabled { cursor: default; }
/* "MINE" corner badge: the viewer's own puns can't be starred, so in the star dogear's top-right spot they
   carry this small muted tab instead (the cue that the pun is yours). The card's overflow:hidden rounds its
   outer (top-right) corner to match the card; we round the inner (bottom-left) so it reads as a folded
   corner label. The trash control sits separately at the bottom-left of the stats bar (.result-own-del). */
.result-mine-corner {
  position: absolute; top: 0; right: 0; z-index: 2;
  display: inline-flex; align-items: center;
  padding: 5px 9px 4px;   /* +1px top / −1px bottom vs the old 4/5: optically centre the all-caps "MINE" in the gray tab (its caps sat ~1px high) */
  background: var(--dogear-bg);
  color: var(--ink-3);
  font-family: var(--display-font); font-weight: 600;
  font-size: var(--fs-2xs); letter-spacing: 0.06em; text-transform: uppercase;
  white-space: nowrap; user-select: none; pointer-events: none;
  border-bottom-left-radius: var(--radius-sm);
}
/* Keep the pun's first line clear of the corner badge (mirrors the starrable dogear's reserved gutter). */
.result-row--mine .result-row__head { padding-right: 54px; }
/* Vote cards can't clip with overflow:hidden (it would crop the flag popover), so a small corner wrapper
   rounds the dogear to the card radius. Its transparent area passes clicks to the card (= pick); the dogear
   button re-enables its own pointer events (= star). */
.vote-dogear-wrap {
  position: absolute; top: 0; right: 0; width: 50px; height: 50px;
  overflow: hidden; border-top-right-radius: var(--radius);
  pointer-events: none; z-index: 2;
}
.vote-dogear-wrap .result-dogear { pointer-events: auto; }

/* ===== Shared per-pun stats line (Submit "your puns" + Results rows) =====
   Order: current rating · head-to-head wins (trophy) · star-inclusive win rate (pill) · stars (gold star). */
.pun-stats {
  display: inline-flex; align-items: center; gap: 11px; flex-wrap: wrap; row-gap: 4px;
  /* TEXT (win-rate, star count, and the advanced rating/trophy numbers) runs ~20% smaller than the icons —
     the gauge / trophy / star glyphs keep their fixed 13px size (set in px on the SVGs, not in em), so they do
     NOT shrink. 0.8 × the tokenized base keeps it on the --fs scale. */
  font-size: calc(var(--fs-sm) * 0.8); line-height: 1; color: var(--ink-2); font-feature-settings: "tnum";
}
/* Every item shares one midline: align-items:center on the row + on each stat, with a uniform tight
   line-height so each line box hugs its glyphs (icons + numbers all flex-center). All text is one 13px size;
   the rating renders as a gauge icon + its number using the SAME .pun-stats__stat structure as the trophy, so
   it needs no per-item nudge to center. The rating number is the line's headline — bold + the darkest ink,
   but the same size + system font as everything else (no display font, which sat high and read off-midline). */
.pun-stats__stat { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
/* The glyphs (gauge / trophy / star) sit centered in their own box, but the digits ride optically ~1px high in
   a line-height:1 box, so an icon reads a hair LOW beside the text. A 1px upward nudge on the icons re-centers
   them with the numbers (visual only — it doesn't shift the flex layout). */
.pun-stats__stat svg { display: block; transform: translateY(-1px); }
/* Center the count vertically on its icon: flex-center the digit box at the icon's height so the number
   doesn't ride high on the text baseline (applies to the trophy + star counts on every surface). */
.pun-stats__num { display: inline-flex; align-items: center; line-height: 1; font-weight: 600; }
/* The rating number is the line's headline: same 13px size as the rest, set apart only by a heavier weight +
   the darkest ink. Compound selector (both classes) so it outweighs the base .pun-stats__num font-weight no
   matter the source order. */
.pun-stats__num.pun-stats__num--rating { font-weight: 700; color: var(--ink); }
.pun-stats__stat--star { color: var(--star-gold); }   /* same gold as the prompt-bank star + the dogear */
/* The stat-line star reads the VIEWER's state: an OUTLINE when they haven't starred the pun, FILLED once
   they have. (The number beside it is the public star count.) */
.pun-stats__stat--star .star-glyph { width: 13px; height: 13px; fill: none; }
.pun-stats__stat--star.is-filled .star-glyph { fill: currentColor; }
/* The star COUNT reads in the same ink as the win-rate text (darker than the gold glyph) for legibility. */
.pun-stats__stat--star .pun-stats__num { color: var(--ink-2); }
/* The star stat is a real button where the pun can be starred from the stats line (same as the dogear). */
.pun-stats__star-btn { appearance: none; border: 0; background: transparent; padding: 0; margin: 0; font: inherit; cursor: pointer; }
.pun-stats__star-btn:disabled { cursor: default; }
.pun-stats__star-btn:not(:disabled):hover { filter: brightness(0.88); transform: scale(1.06); }
/* Win rate is plain inline text (the pill bubble — bg / border / radius / padding — was removed), the same
   13px as every other item: it inherits .pun-stats' size with no per-item override, so the whole line reads at
   one size and only the rating number is heavier. */
.pun-stats__winrate {
  display: inline-flex; align-items: center;
  font-weight: 600; color: var(--ink-2); white-space: nowrap;
}

/* Submit "your puns" card, voting-pool variant: the trash + share icons go larger and sit inline on the
   stats line — delete at the left, then the stats line pushed hard RIGHT against the share (same treatment as
   the Results card), not box-centred — instead of the edit-window bar. */
/* Compound selector so flex BEATS the base .my-pun-card__bar grid (equal specificity, the base is later in
   source) — the pooled bar is a flex row: delete left, stats pushed hard-right against the share. */
.my-pun-card__bar.my-pun-card__bar--stats { display: flex; align-items: center; }
.my-pun-card__bar--stats .pun-stats { margin-left: auto; min-width: 0; justify-content: flex-end; }
/* The share sits one small gap right of the star count — 3px of box margin lands its glyph ~11px from the
   count (the glyph is centred in its 34px button), matching the win%→star gap inside the line. */
.my-pun-card__bar--stats .my-pun-card__bar-share { margin-left: 3px; }
.my-pun-card__bar--stats .my-pun-card__icon-btn { width: 34px; height: 34px; flex-shrink: 0; }
.my-pun-card__bar--stats .my-pun-card__icon-btn svg { width: 18px; height: 18px; }

/* Stats line on a User-page pun — centered, on its own line above the foot. (My Feed now uses the same
   result-row layout as the board, so its stats line rides inside the statsbar.) */
.user-pun__main > .pun-stats { display: flex; justify-content: center; margin-top: 8px; }

/* Clickable author row (My Feed finalized rows open the author's page, like the board). */
.result-row__author--btn { cursor: pointer; }
.result-row__author--btn:hover .result-row__author-name { text-decoration: underline; text-underline-offset: 2px; }
.result-row__author--btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.result-row > * { min-width: 0; }
.result-row:hover { border-color: var(--line-strong); }
/* Top-3 finishers stand out: a warm tinted fill, stronger border + lift, and a bolder pun line. */
.result-row.is-top {
  /* OPAQUE peach-tinted border (mixed with the card fill, NOT transparent): over the white card it looks the
     same as before, but it stays a crisp line where it sits against the page-bg — notably the FIRST row's TOP
     edge, which abuts the frozen-cluster gap with no card above it to cast a reinforcing shadow. The old
     `…60%, transparent` (= 0.6 alpha) made that top edge read as a missing border. */
  border-color: color-mix(in oklab, var(--peach) 60%, var(--bg-elev));
  background: linear-gradient(180deg, color-mix(in oklab, var(--peach) 12%, var(--bg-elev)), var(--bg-elev));
  box-shadow: var(--shadow-card);
}
.result-row.is-top .result-row__rank { color: var(--peach); }
.result-row.is-top .result-row__quote { font-weight: 600; }
.result-row.is-top .result-row__score { font-weight: 700; }
.result-row__rank {
  font-family: var(--mono-font);
  font-size: var(--fs-sm); color: var(--ink-3);
  font-weight: 600;
  text-align: center;
}
.result-row__quote { font-size: 19px; }

/* Circular numbered rank badge (replaces the ribboned medals) — sits inline in the meta row, same
   diameter as the avatar disc so rank + avatar + name + city share one baseline. Gold/silver/bronze
   for the top three, a plain sunk disc otherwise. */
.result-row__rank-badge {
  /* A 26px circle for 1–2 digit ranks that grows into a pill for 3+ digits (e.g. #172) instead of
     cramming the number — min-width keeps the round shape, padding gives multi-digit ranks room. */
  min-width: 26px; height: 26px; padding: 0 7px; box-sizing: border-box; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  font-family: var(--mono-font); font-weight: 700; font-size: var(--fs-xs); line-height: 1;
  background: var(--bg-sunk); color: var(--ink-2);
}
.result-row__rank-badge--1 { background: #E6B23E; color: #4A3713; }  /* gold   */
.result-row__rank-badge--2 { background: #BFC6D1; color: #2D3640; }  /* silver */
.result-row__rank-badge--3 { background: #CC8E59; color: #ffffff; }  /* bronze */

/* "Still gathering votes" — provisional puns shown below the ranked board (live/pending only). They're not
   yet confident enough to rank, but stay visible and thumbable. Rendered dimmer + unfilled (transparent,
   no card surface) to read as "pending". */
.result-provisional { margin-top: 22px; }
.result-provisional__head {
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 0 0 8px; padding-top: 14px;
  border-top: 1px solid var(--line);
}
.result-list--provisional { opacity: 0.92; }
.result-row--provisional { background: transparent; min-height: 64px; }
.result-row__rank--provisional { display: flex; align-items: center; justify-content: center; }
.result-row__rank--provisional::before { content: "•"; color: var(--ink-3); opacity: 0.5; }

.result-row__meta {
  display: flex; align-items: center; gap: 8px;
  margin-top: 6px; font-size: 14px; color: var(--ink-2);
  flex-wrap: wrap;
}
.result-row__score {
  font-family: var(--display-font);
  font-size: 22px; font-weight: 500; letter-spacing: -0.02em;
  color: var(--ink);
  font-feature-settings: "tnum";
}
.result-row__score-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  text-align: right;
  margin-top: -2px;
}

.load-more {
  margin: 14px auto 0;
  display: flex; justify-content: center;
}

/* Subtle grey "load more" link, centered with respect to the list above */
.load-more-link {
  appearance: none; border: 0; background: transparent;
  display: block;
  width: 100%;
  text-align: center;
  margin: 14px 0 0;
  padding: 10px 14px;
  cursor: pointer;
  font: 500 13px/1 var(--ui-font);
  color: var(--ink-3);
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm);
  transition: color 120ms, background 120ms;
}
.load-more-link:hover {
  color: var(--ink-2);
  background: color-mix(in oklab, var(--ink) 4%, transparent);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ===== Leaderboard ===== */
/* Creator board view toggle: Hall of Fame vs Current Standings (spec §10.3/§10.4). */
.lb-views {
  display: inline-flex;
  gap: 2px;
  margin: 12px 0 10px;
  padding: 3px;
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.lb-view {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  border-radius: 999px;
  padding: 7px 16px;
  font-size: var(--fs-sm); font-weight: 600;
  cursor: pointer;
  transition: background 120ms, color 120ms;
  white-space: nowrap;
}
.lb-view:hover { color: var(--ink); }
.lb-view.is-active {
  background: var(--bg-elev);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}

.lb-controls {
  display: flex; flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 14px;
  align-items: center;
}
/* Leaderboard + Results LANDING: freeze the filter row under the top bar so the rows/day-cards scroll
   beneath it. Same masking as the Results frozen cluster — opaque page-bg + an opaque bottom gap
   (`padding-bottom`, not a transparent margin) + a hairline drawn just above it (`::after`) — so a scrolling
   row's border/shadow never leaks into the frozen filter zone and the small gap below the line stays clean.
   (`::after` is absolutely positioned, so it isn't a flex item of this flex row.) The Results filter row is
   shown only when the viewer has groups, so this freezes "if applicable". The Results DETAIL page
   (`.page-results-detail`) freezes its filters inside `.detail-sticky` instead, so this selector — scoped to
   `.page-results`, a different class — doesn't touch it. */
.page-leaders .lb-controls,
.page-results .lb-controls {
  position: sticky;
  top: var(--topbar-h);
  z-index: var(--z-sticky);
  background: var(--bg);
  margin: 0;
  padding-bottom: 9px;
}
/* No divider hairline under the Leaderboard / Results-landing frozen filter row (removed per request) — the
   opaque mask + small gap still separate it from the scrolling rows/day-cards, just with no visible line. */
.lb-pill {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-2);
  border-radius: var(--radius-sm);
  padding: 7px 10px;
  font-size: var(--fs-sm); font-weight: 500;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: background 120ms, color 120ms;
  white-space: nowrap;
}
.lb-pill:hover { background: var(--bg-sunk); color: var(--ink); }
.lb-pill.is-active {
  background: var(--bg-sunk);
  color: var(--ink);
  font-weight: 600;
}
/* inline-flex so the SVG caret centers on the pill's text line instead of sitting on the baseline */
.lb-pill__caret { display: inline-flex; align-items: center; color: currentColor; opacity: 0.6; margin-left: -2px; }

.lb-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 180px;
  max-width: calc(100vw - 32px);
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 14px 32px -12px rgba(42,33,27,0.24);
  padding: 4px;
  z-index: 10;
  animation: pop-in 140ms cubic-bezier(.2,.7,.2,1);
  transform-origin: top left;
}
.lb-menu__item {
  appearance: none; border: 0; background: transparent;
  font: inherit; color: var(--ink-2);
  text-align: left; width: 100%;
  padding: 8px 12px; border-radius: var(--radius-sm);
  cursor: pointer; font-size: 13.5px;
  transition: background 120ms, color 120ms;
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.lb-menu__item:hover { background: var(--bg-sunk); color: var(--ink); }
.lb-menu__item.is-checked {
  background: color-mix(in oklab, var(--peach) 10%, var(--bg-elev));
  color: var(--peach-deep); font-weight: 500;
}
.lb-menu__item.is-checked::after {
  content: "✓";
  color: var(--peach);
  font-size: var(--fs-sm);
}
.lb-pill-wrap { position: relative; max-width: 100%; }
/* Right-anchor the menu only for the rightmost of several *dropdown* pills (so it doesn't overflow the
   page's right edge), keyed on it having a preceding pill-wrap sibling. A lone dropdown sitting at the left
   (the Results group filter — even when a bare "All" pill precedes it, which is a .lb-pill, not a wrap) has
   no preceding wrap, so it stays left-anchored; right-anchoring it would push the 180px menu off the left
   edge. (Was :last-child:not(:first-child), which the "All" pill broke by making the lone wrap non-first.) */
.lb-pill-wrap ~ .lb-pill-wrap:last-child .lb-menu {
  left: auto;
  right: 0;
  transform-origin: top right;
}

.leader-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; }
.leader-row {
  display: grid;
  grid-template-columns: 32px 32px minmax(0, 1fr) auto;
  column-gap: 10px;
  row-gap: 0;
  align-items: start;
  padding: 10px 14px 12px;
  transition: border-color 140ms;
}
.leader-row.is-top { border-color: var(--line-strong); }
.leader-row.is-top:first-child {
  border-color: color-mix(in oklab, var(--peach) 50%, transparent);
  background: linear-gradient(90deg, color-mix(in oklab, var(--peach) 10%, var(--bg-elev)) 0%, var(--bg-elev) 80%);
}
.leader-row__rank {
  grid-column: 1; grid-row: 1;
  font-family: var(--mono-font); font-weight: 700;
  font-size: 15px; color: var(--ink-3);
  align-self: center;
  text-align: center;
}
.leader-row.is-top .leader-row__rank { color: var(--peach); }
.leader-row__avatar {
  grid-column: 2; grid-row: 1;
  /* Vertically centered against the whole identity section — one line when there's no location (avatar inline
     with the username/"+"/points), two lines when there is (centered against both). The rank + points use the
     same centering, so all four line up. */
  align-self: center;
}
.leader-row__name-wrap {
  grid-column: 3; grid-row: 1;
  display: flex; flex-direction: column; gap: 0;
  min-width: 0;
}
/* Username + the "+" on one line, vertically centered together. */
.leader-row__name-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.leader-row__name {
  font-weight: 600; font-size: 14px;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 0 1 auto; min-width: 0;
}
/* Username + prompt are the only click targets on a leaderboard row — link-styled buttons (username →
   profile, prompt → that contest's Results). */
.leader-row__name-link { appearance: none; border: 0; background: transparent; padding: 0; cursor: pointer; text-align: left; }
.leader-row__name-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.leader-row__pun-prompt-link {
  appearance: none; border: 0; background: transparent; padding: 0; cursor: pointer; text-align: left;
}
.leader-row__pun-prompt-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.leader-row__city {
  color: var(--ink-3); font-size: var(--fs-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.leader-row__value {
  grid-column: 4; grid-row: 1;
  display: flex; flex-direction: column; align-items: flex-end;
  align-self: center;
}
.leader-row__num {
  font-family: var(--display-font);
  font-size: var(--fs-lg); font-weight: 500; letter-spacing: -0.02em;
  font-feature-settings: "tnum";
  line-height: 1;
}
/* "pts" unit after the score number — small + muted so the number stays the headline. */
.leader-row__num-unit {
  font-family: var(--ui-font, inherit);
  font-size: 11.5px; font-weight: 500; letter-spacing: 0;
  color: var(--ink-3); margin-left: 3px;
}
.leader-row__label {
  font-size: 10.5px; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.06em;
  margin-top: 2px;
}
.leader-row__pun {
  /* Full card width so the (right-aligned) stats line below it sits at the box's right edge, not just the
     name/value columns. The eyebrow + prompt + pun text stay left-aligned. */
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.leader-row__pun-eyebrow {
  font-size: 10px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 3px;
}
.leader-row__pun-text {
  font-family: var(--display-font);
  font-size: 14.5px; line-height: 1.3;
  color: var(--ink);
  text-wrap: pretty;
  overflow-wrap: anywhere;
  margin: 0;
  letter-spacing: -0.005em;
}
/* The top pun's stats line + a share icon, right-aligned under the pun — the universal pun-box bottom pattern
   (stats to the LEFT of the share, both at the box's right edge, landing under the row's points). The negative
   right margin full-bleeds the cluster to the card's right EDGE (= -(leader-row padding-right)) so the share's
   gap from that edge matches the contest-result pun box, whose statsbar full-bleeds past its own 14px padding. */
.leader-row__pun-stats { display: flex; justify-content: flex-end; align-items: center; margin-top: 8px; margin-right: -14px; }
.leader-row__pun-stats .pun-stats { flex: 0 1 auto; min-width: 0; }
/* Match the contest-result pun box's share EXACTLY: a 34px button with an 18px glyph (same as
   .result-row__statsbar's share), sitting 3px from the stats so the glyph lands ≈11px from the star count. */
.leader-row__pun-stats .result-row__share { width: 34px; height: 34px; margin-left: 3px; flex-shrink: 0; }
.leader-row__pun-stats .result-row__share svg { width: 18px; height: 18px; }

@media (max-width: 480px) {
  .leader-row { grid-template-columns: 28px 28px minmax(0, 1fr) auto; padding: 10px 12px 12px; column-gap: 9px; }
  .leader-row__pun { grid-column: 1 / -1; }
  .leader-row__pun-stats { margin-right: -12px; }   /* match the 12px mobile padding-right */
}

/* ===== Page transition ===== */
.page { animation: page-fade 220ms ease-out; position: relative; }
@keyframes page-fade { from { opacity: 0; transform: translateY(4px); } }

/* Results detail slides in from the right. Blazor renders it in-flow (not as an absolute
   overlay), so the slide animation lives on the page wrapper itself. */
.page-results-detail { animation: detail-slide 280ms cubic-bezier(.2, .7, .2, 1); }

/* ============================================================
   Retained from v0.2 — used by the kept Admin page (now off-nav)
   and by Blazor-specific UI. Not part of the v2 visual spec.
   ============================================================ */

/* ----- Utility / shared states used by Blazor pages ----- */
.flash--danger {
  background: color-mix(in oklab, var(--blush) 40%, var(--bg-elev));
  border-color: color-mix(in oklab, var(--blush) 75%, var(--ink) 5%);
  color: var(--ink);
}
.empty {
  padding: 20px; text-align: center;
  background: var(--bg-sunk); border-radius: var(--radius);
  color: var(--ink-3); font-size: 14px;
  border: 1px solid var(--line-strong);
}
.btn-lg { padding: 12px 20px; font-size: 15px; }

/* ----- Admin · Topic queue (kept page) ----- */
.btn-approved {
  background: color-mix(in oklab, var(--tea) 60%, var(--bg-elev));
  color: var(--good);
  border-color: color-mix(in oklab, var(--good) 30%, transparent);
}
.admin-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px;
  padding: 10px 14px;
  background: var(--bg-sunk);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.admin-bar__stats { display: flex; gap: 18px; font-size: var(--fs-sm); color: var(--ink-3); flex-wrap: wrap; }
.admin-bar__stats strong { color: var(--ink); margin-right: 4px; font-weight: 600; }
.admin-table {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
  overflow: hidden;
}
.admin-row {
  display: grid;
  grid-template-columns: 140px 1fr 1fr 120px 150px;
  gap: 12px; align-items: center;
  padding: 12px var(--density-pad);
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.admin-row:last-child { border-bottom: 0; }
/* Prompt queue: Date · Prompt (fills the width) · Action. */
.admin-table--prompts .admin-row { grid-template-columns: 140px 1fr 150px; }
.admin-table--prompts .admin-row .input { width: 100%; }
.admin-row.is-approved { background: color-mix(in oklab, var(--tea) 25%, var(--bg-elev)); }
.admin-row--head {
  background: var(--bg-sunk);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  font-weight: 600;
  padding-top: 9px; padding-bottom: 9px;
}
.admin-row__date { font-weight: 500; color: var(--ink); font-size: 13.5px; }
.admin-row__actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
.select {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  font: inherit; font-size: 13.5px;
  padding: 7px 28px 7px 10px;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--ink);
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237A6E66' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  transition: border-color 120ms;
}
.select:hover { border-color: var(--ink-3); }
.select:focus {
  outline: none; border-color: var(--peach);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--peach) 22%, transparent);
}
.source-chip {
  display: inline-block; padding: 3px 8px;
  font-size: 11.5px; border-radius: 4px;
  background: var(--bg-sunk); border: 1px solid var(--line-strong);
  color: var(--ink-2);
  font-family: var(--mono-font); letter-spacing: 0.01em;
}
.source-chip--user {
  background: color-mix(in oklab, var(--sky) 18%, var(--bg-elev));
  border-color: color-mix(in oklab, var(--sky) 50%, transparent);
  color: var(--sky-deep);
}
/* (Prompt rows now stack via the shared .admin-grid rule at the end of this section.) */

/* ----- Admin · section nav (one NavLink per routed page; replaces the old button tab bar) ----- */
.admin-nav { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.admin-nav__item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: var(--radius-sm);
  font-size: var(--fs-sm); font-weight: 500; line-height: 1;
  color: var(--ink-2); text-decoration: none;
  background: transparent; border: 1px solid var(--line-strong);
  white-space: nowrap; transition: background 120ms, border-color 120ms, color 120ms;
}
.admin-nav__item:hover { background: var(--bg-sunk); color: var(--ink); }
.admin-nav__item.is-active { background: var(--peach); color: #fff; border-color: var(--peach); }
.admin-nav__item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.admin-nav__badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 5px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700;
  background: var(--blush); color: var(--danger-ink);
}
.admin-nav__item.is-active .admin-nav__badge { background: rgba(255, 255, 255, 0.9); color: var(--peach-deep); }
/* On a phone the 16-item strip WRAPS onto multiple rows so every section stays visible without a
   side-swipe (the base `flex-wrap: wrap` does the work); the pills just tighten a touch so the wrapped
   block stays compact. */
@media (max-width: 720px) {
  .admin-nav { gap: 5px; }
  .admin-nav__item { padding: 5px 9px; }
}

/* ----- Admin · health pills (Operations, Puns status) ----- */
.ops-pill, .pun-pill, .vote-pill {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.01em;
  border: 1px solid var(--line-strong); background: var(--bg-sunk); color: var(--ink-2);
}
.is-ok   { color: var(--good); border-color: color-mix(in oklab, var(--good) 35%, transparent);
           background: color-mix(in oklab, var(--good) 14%, var(--bg-elev)); }
.is-warn { color: var(--peach-deep); border-color: color-mix(in oklab, var(--peach) 45%, transparent);
           background: color-mix(in oklab, var(--peach) 12%, var(--bg-elev)); }
.is-bad  { color: var(--danger-ink); border-color: color-mix(in oklab, var(--blush) 70%, transparent);
           background: color-mix(in oklab, var(--blush) 45%, var(--bg-elev)); }

/* ----- Admin · Operations ----- */
.ops-cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px; margin-bottom: 16px;
}
.ops-card { padding: 14px 16px; }
.ops-card.is-ok   { border-color: color-mix(in oklab, var(--good) 35%, var(--line)); }
.ops-card.is-warn { border-color: color-mix(in oklab, var(--peach) 45%, var(--line)); }
.ops-card.is-bad  { border-color: color-mix(in oklab, var(--blush) 70%, var(--line)); }
.ops-card__label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); }
.ops-card__value { font-family: var(--display-font); font-size: var(--fs-lg); font-weight: 500; margin-top: 4px; }
.ops-card__sub { font-size: 12.5px; margin-top: 2px; }
.ops-card__btn { margin-top: 8px; }
.ops-kpis { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.ops-alert { margin-bottom: 14px; }
.ops-subhead { font-family: var(--display-font); font-size: var(--fs-md); font-weight: 500; margin: 20px 0 8px; }
.ops-jobs { margin-bottom: 12px; }
.ops-job {
  display: grid; grid-template-columns: 1.5fr 64px 1fr 90px 170px;
  gap: 12px; align-items: center;
  padding: 11px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 14px;
}
.ops-job:last-child { border-bottom: 0; }
.ops-job.is-disabled { opacity: 0.6; }
.ops-job__name { font-family: var(--mono-font); font-size: var(--fs-sm); color: var(--ink); }
.ops-job__controls { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
.ops-job--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
/* Run history reuses the same grid feel. */
.ops-run {
  display: grid; grid-template-columns: 1.5fr 110px 90px 1fr;
  gap: 12px; align-items: center;
  padding: 9px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.ops-run:last-child { border-bottom: 0; }
.ops-run--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.ops-run__err { margin-left: 6px; }

/* Failed work-item detail: same table feel, but the error column wraps. */
.ops-failed-row {
  display: grid; grid-template-columns: 56px 130px 1.3fr 56px 110px 2fr;
  gap: 12px; align-items: start;
  padding: 9px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.ops-failed-row:last-child { border-bottom: 0; }
.ops-failed-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600; align-items: center;
}
.ops-failed-row__err {
  font-family: var(--mono-font); font-size: var(--fs-xs); color: var(--danger, #b4232a);
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* ----- Admin · Accounts (abuse review) ----- */
.account-row {
  display: grid; grid-template-columns: 1.4fr 1fr 90px 90px 120px 150px;
  gap: 12px; align-items: center;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.account-row:last-child { border-bottom: 0; }
.account-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.account-row__who { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.account-row__admin { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }

/* ----- Admin · Accounts · Latest accounts ----- */
.recent-account-row {
  display: grid; grid-template-columns: 1.4fr 1.6fr 1.2fr 110px 120px;
  gap: 12px; align-items: center;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.recent-account-row:last-child { border-bottom: 0; }
.recent-account-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.recent-account-row__who { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.recent-account-row__email { overflow-wrap: anywhere; min-width: 0; }
.recent-account-row__links { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ----- Admin · Accounts · Vote clusters (anti-Sybil same-IP rings; 6-col, stacks via .admin-grid) ----- */
.cluster-row {
  display: grid; grid-template-columns: 1.4fr 90px 90px 1.3fr 120px 120px;
  gap: 12px; align-items: center;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.cluster-row:last-child { border-bottom: 0; }
.cluster-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.cluster-row__ip { font-family: var(--mono, monospace); overflow-wrap: anywhere; min-width: 0; }

/* ----- Admin · Activity (one user's footprint, by contest) ----- */
/* No-account landing: the most-recently-active accounts (3-col grid; stacks via .admin-grid on a phone). */
.active-acct-row {
  display: grid; grid-template-columns: minmax(0, 1.4fr) 180px minmax(0, 1fr);
  gap: 12px; align-items: center;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.active-acct-row:last-child { border-bottom: 0; }
.active-acct-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.active-acct-row__who { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.active-acct-row__links { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Selected-account identity header (name / email / profile link). */
.activity-acct-head {
  display: flex; flex-direction: column; gap: 4px;
  padding: 12px var(--density-pad); margin-bottom: 14px;
  background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--radius);
}
.activity-acct-head__main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.activity-acct-head__name { font-family: var(--display-font); font-size: var(--fs-md); font-weight: 600; }
.activity-acct-head__email { font-size: 13px; overflow-wrap: anywhere; }
.activity-acct-head__links { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.activity-contests { display: flex; flex-direction: column; gap: 8px; }
.activity-contest { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); overflow: hidden; }
.activity-contest__head {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px var(--density-pad); background: none; border: 0; cursor: pointer;
  text-align: left; font: inherit; color: inherit;
}
.activity-contest__head:hover { background: var(--bg-sunk); }
.activity-contest__caret { width: 1em; color: var(--ink-3); }
.activity-contest__title { font-weight: 600; }
.activity-contest__date { white-space: nowrap; }
.activity-contest__prompt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.activity-contest__counts { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.activity-events { border-top: 1px solid var(--line); }
.activity-events__note { padding: 10px var(--density-pad); margin: 0; }
.activity-event {
  display: grid; grid-template-columns: 110px 130px 1fr; gap: 12px; align-items: start;
  padding: 9px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.activity-event:last-child { border-bottom: 0; }
.activity-event__when { white-space: nowrap; }
.activity-event__kind { font-weight: 600; }
.activity-event__body { min-width: 0; }
.activity-event__pun { overflow-wrap: anywhere; }
.activity-event__meta { font-size: var(--fs-xs); margin-top: 2px; }
.activity-pair { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; align-items: center; }
.activity-pair__pun { overflow-wrap: anywhere; padding: 4px 6px; border: 1px solid var(--line); border-radius: 6px; }
.activity-pair__pun.is-pick { border-color: var(--accent, #4caf50); background: var(--bg-sunk); font-weight: 600; }
.activity-pair__vs { font-size: var(--fs-2xs); }
@media (max-width: 720px) {
  .activity-event { grid-template-columns: 1fr; gap: 2px; }
  .activity-contest__prompt { display: none; }
}

/* ----- Admin · Settings editor ----- */
.setting-row {
  display: grid; grid-template-columns: 1fr 1fr 90px;
  gap: 14px; align-items: center;
  padding: 12px var(--density-pad); border-bottom: 1px solid var(--line);
}
.setting-row:last-child { border-bottom: 0; }
.setting-row__key { font-family: var(--mono-font); font-size: 13.5px; color: var(--ink); }
.setting-row__upd { font-size: 11.5px; margin-top: 2px; }

/* ----- Admin · day picker (Puns + Votes) ----- */
.admin-bar--picker { gap: 18px; }
.admin-picker { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
.admin-picker span { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; }
.admin-picker .input { width: 100%; min-width: 0; max-width: 520px; flex: 1 1 180px; }

/* ----- Admin · Puns list ----- */
.pun-list, .mod-list { display: flex; flex-direction: column; gap: 10px; }
.pun-item, .mod-item {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
  padding: 12px 16px;
}
.pun-item.is-out { opacity: 0.72; }
.pun-item__body, .mod-item__body { min-width: 0; }
.pun-item__text, .mod-item__pun { margin: 0 0 4px; font-size: 15px; overflow-wrap: anywhere; }
.pun-item__meta, .mod-item__meta { margin: 0; font-size: 12.5px; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.pun-item__actions, .mod-item__actions { display: flex; flex-wrap: wrap; gap: 6px; flex-shrink: 0; }
.mod-item__appeal { color: var(--peach-deep); font-weight: 600; }
.btn-danger { color: var(--danger-ink); border-color: color-mix(in oklab, var(--blush) 70%, transparent); }
.btn-danger:not(:disabled):hover { background: color-mix(in oklab, var(--blush) 40%, var(--bg-elev)); }

/* ----- Admin · Votes table ----- */
.vote-row {
  display: grid; grid-template-columns: 150px 130px 1fr 90px 110px;
  gap: 12px; align-items: center;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.vote-row:last-child { border-bottom: 0; }
.vote-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.vote-row__matchup { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* Show the WHOLE pun — wrap rather than clip (there's no hover/click to reveal the rest). */
.vote-row__matchup span { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
.vote-row__matchup .is-winner { color: var(--good); font-weight: 600; }
/* Thumbs reuse the vote table with a 4-column layout. */
.thumb-row { grid-template-columns: 150px 130px 1fr 110px; }
.thumb-row__pun { min-width: 0; white-space: normal; overflow-wrap: anywhere; }

/* ----- Admin · Duplicates (outcomes of duplicate review) ----- */
/* Card layout (not a rigid grid) so the panel reflows cleanly on a phone. Desktop: pun text on the
   left, a fixed meta column (outcome/similarity/when/actions) on the right; mobile: the meta column
   drops below the puns and the actions take their own full-width row. */
.dup-list { display: flex; flex-direction: column; gap: 10px; }
.dup-card {
  display: flex; gap: 16px; align-items: flex-start;
  padding: 12px var(--density-pad); border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-sunk); font-size: 13.5px;
}
.dup-card__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.dup-card__puns { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.dup-card__why { font-size: 12.5px; min-width: 0; overflow-wrap: anywhere; }
.dup-card__meta { flex: 0 0 auto; width: 210px; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.dup-card__tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dup-sim { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-2); }
.dup-card__when { font-size: 12px; }
.dup-card__actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 2px; }
.dup-card__locked { font-size: 12px; font-style: italic; }
.dup-confirm { font-size: 12px; color: var(--warn); font-weight: 600; width: 100%; }
.dup-pun { display: flex; gap: 6px; min-width: 0; cursor: default; align-items: baseline; }
.dup-pun__text { min-width: 0; overflow-wrap: anywhere; }
.dup-author { flex: none; color: var(--ink-3); }
.dup-letter { flex: none; font-size: 11px; font-weight: 700; color: var(--ink-3); width: 16px; text-align: center; }
.dup-tag {
  flex: none; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 1px 7px; border-radius: 999px; align-self: center;
}
.dup-tag--kept { background: color-mix(in srgb, var(--good) 20%, transparent); color: var(--good); }
.dup-tag--merged { background: color-mix(in srgb, var(--ink-3) 20%, transparent); color: var(--ink-3); }
@media (max-width: 720px) {
  .dup-card { flex-direction: column; gap: 10px; }
  .dup-card__meta { width: 100%; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
  .dup-card__when { flex: 1 1 auto; }
  .dup-card__actions { width: 100%; margin-top: 4px; }
}
.dup-pill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
.dup-pill--s1 { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.dup-pill--s2 { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
.dup-pill--s3 { background: color-mix(in srgb, var(--bad) 18%, transparent); color: var(--bad); }
.dup-pill--s4 { background: color-mix(in srgb, var(--ink-3) 16%, transparent); color: var(--ink-3); }
.dup-pill--s5 { background: color-mix(in srgb, var(--good) 18%, transparent); color: var(--good); }
.dup-embed-badge { font-size: 12.5px; color: var(--ink-3); }
.dup-embed-badge.is-semantic strong { color: var(--good); }
.dup-embed-badge.is-lexical strong { color: var(--warn); }
.dup-embed-badge code { font-size: 11.5px; }

/* ----- Admin · Score trace (how a pun's score was derived) ----- */
.trace-period {
  border: 1px solid var(--line); border-radius: 10px; margin-bottom: 8px; overflow: hidden;
}
.trace-period__head {
  display: grid; grid-template-columns: 150px 1fr 120px 90px; gap: 12px; align-items: center;
  width: 100%; padding: 10px var(--density-pad); background: var(--bg-sunk);
  border: 0; cursor: pointer; font-size: 13.5px; text-align: left; color: inherit;
}
.trace-period__delta { font-weight: 600; }
.trace-period__delta.is-up { color: var(--good); }
.trace-period__delta.is-down { color: var(--bad); }
.trace-period__body { padding: 8px var(--density-pad) 12px; }
.trace-item {
  display: grid; grid-template-columns: 70px 1fr 90px 110px; gap: 12px; align-items: center;
  padding: 6px 0; border-bottom: 1px solid var(--line); font-size: var(--fs-sm);
}
.trace-item:last-child { border-bottom: 0; }
.trace-item__pun { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
.trace-backfilled { font-size: 11.5px; color: var(--warn); font-weight: 600; }
.trace-note { margin-top: 10px; font-size: var(--fs-xs); }

/* ----- Admin · Contests overview table ----- */
.contest-row {
  display: grid; grid-template-columns: 56px 104px minmax(150px, 1.5fr) 80px 76px 58px 58px 58px 140px;
  gap: 10px; align-items: start;
  padding: 10px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.contest-row:last-child { border-bottom: 0; }
.contest-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.contest-row__subhead { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 10px; }
.contest-row__ordinal { font-weight: 600; color: var(--ink-2); }
.contest-row__day { font-weight: 500; }
.contest-row__prompt { min-width: 0; overflow-wrap: anywhere; white-space: normal; line-height: 1.35; }
.contest-row__close { display: flex; flex-direction: column; gap: 1px; }
.contest-row__closeabs { font-size: 11.5px; }

/* ----- Admin · AI activity table (past vs scheduled-future) ----- */
.ai-row {
  display: grid; grid-template-columns: 130px 1fr 1fr 1fr;
  gap: 12px; align-items: center;
  padding: 11px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.ai-row:last-child { border-bottom: 0; }
.ai-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.ai-row > div { text-align: right; font-variant-numeric: tabular-nums; font-size: 15px; }
.ai-row > div:first-child { text-align: left; font-weight: 600; color: var(--ink-2); font-size: 13.5px; }
.ai-row--head > div { font-size: var(--fs-2xs); }
/* the "scheduled future" column is the headline of this screen — give it a gentle accent */
.ai-row > div:nth-child(3):not(.muted) { color: var(--accent, var(--ink-1)); }

/* ----- Admin · de-inlined section layouts (Acquisition / Engagement / Activity devices) ----- */
.acq-window { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 8px 0 16px; }
.acq-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
.acq-card { padding: 12px 16px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg-elev); }
.acq-card__value { font-size: 1.6rem; font-weight: 700; }
.acq-card__pct { font-size: 0.9rem; }
.acq-breakdowns { display: flex; flex-wrap: wrap; gap: 24px; }
.acq-breakdown { min-width: 220px; flex: 1; }
.acq-breakdown__dim { font-weight: 400; font-size: 0.8rem; }
.acq-breakdown__tbl { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.acq-breakdown__tbl td { padding: 4px 8px; border-bottom: 1px solid color-mix(in oklab, var(--line) 55%, transparent); }
.acq-breakdown__n { text-align: right; white-space: nowrap; }
.acq-arrivals-head { margin-top: 24px; }
.acq-loadmore { margin-top: 8px; }

/* Acquisition · recent arrivals (8-col grid; stacks via .admin-grid on a phone) */
.landing-row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1.1fr) minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 1.2fr) 64px minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 10px; align-items: start;
  padding: 8px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.landing-row > div { min-width: 0; overflow-wrap: anywhere; }
.landing-row:last-child { border-bottom: 0; }
.landing-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}
.landing-row code { font-size: 11px; }

/* Engagement · date-window controls */
.eng-window { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin: 8px 0 16px; }
.eng-window__field { display: flex; flex-direction: column; gap: 2px; font-size: 0.8rem; }
.eng-presets { display: flex; gap: 6px; }

/* Activity · section spacing + devices table (5-col grid; stacks via .admin-grid) */
.activity-section { margin-top: 24px; }
.device-row {
  display: grid; grid-template-columns: 96px minmax(0, 1.4fr) 110px 150px minmax(0, 1fr);
  gap: 12px; align-items: start;
  padding: 8px var(--density-pad); border-bottom: 1px solid var(--line); font-size: 13px;
}
.device-row > div { min-width: 0; overflow-wrap: anywhere; }
.device-row:last-child { border-bottom: 0; }
.device-row--head {
  background: var(--bg-sunk); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600;
}

/* Settings · value input fills its column / mobile row */
.setting-row__edit { min-width: 0; }
.setting-row__edit .input { width: 100%; }

/* ----- Admin · responsive (≤720px): stacked, LABELLED cards -----
   The old rule collapsed these tables to a label-less 2-column grid (it hid the header row, so cells lost
   their meaning). Instead, any grid "row" tagged .admin-grid becomes a stacked card on a phone: the header
   row (.admin-grid--head) is hidden, and each cell carrying a data-label shows that label inline — so no
   value is left unlabelled. Cells WITHOUT a data-label (the pun/matchup, the identity cell) span full width
   so a whole pun is readable. */
@media (max-width: 720px) {
  .admin-grid { display: block; }
  .admin-grid.admin-grid--head { display: none; }
  .admin-grid > * { padding: 2px 0; min-width: 0; }
  .admin-grid > *[data-label] { display: flex; gap: 8px; align-items: baseline; }
  .admin-grid > *[data-label]::before {
    content: attr(data-label); flex: 0 0 38%; max-width: 38%;
    color: var(--ink-3); font-size: var(--fs-2xs); font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.05em;
  }
  /* Action clusters read left-to-right when stacked, not pinned right. */
  .admin-grid > .ops-job__controls, .admin-grid > .admin-row__actions { justify-content: flex-start; }
  /* Pun / moderation cards stack their body above their actions. */
  .pun-item, .mod-item { flex-direction: column; }
}
.ops-job__name,
.ops-run__err,
.setting-row__key,
.account-row,
.recent-account-row,
.vote-row,
.thumb-row {
  min-width: 0;
}
@media (max-width: 520px) {
  .admin-bar--picker { flex-direction: column; align-items: stretch; }
  .admin-picker { width: 100%; flex-wrap: wrap; align-items: stretch; }
  .admin-picker span { width: 100%; }
  .admin-picker .input { max-width: 100%; flex-basis: auto; }
}

/* ===== Blazor framework UI ===== */
#blazor-error-ui {
  color-scheme: light only;
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  box-sizing: border-box;
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }
.blazor-error-boundary { background: #b32121; padding: 1rem; color: white; border-radius: 6px; }
.blazor-error-boundary::after { content: "An error has occurred."; }

.loading-progress {
  position: relative; display: block; width: 8rem; height: 8rem; margin: 20vh auto 1rem auto;
}
.loading-progress circle {
  fill: none; stroke: #f1d0e2; stroke-width: 0.6rem;
  transform-origin: 50% 50%; transform: rotate(-90deg);
}
.loading-progress circle:last-child {
  stroke: var(--peach);
  stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
  transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
  position: absolute; text-align: center; font-weight: bold;
  inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after { content: var(--blazor-load-percentage-text, "Loading…"); }

/* ============================================================
   Design-handoff update (2026-05): single-prompt card, profile/user
   overlays, emoji avatars, results UiStatus + thumbs, leaderboard prompt.
   ============================================================ */

/* ===== Single free-form prompt card (Submit + Vote) ===== */
.contest-prompt {
  margin: 0 0 14px;
  padding: 8px 18px;
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--peach) 14%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--peach) 45%, transparent);
  box-shadow: var(--shadow-card);
  font-family: var(--display-font);
  font-weight: 500;
  font-size: clamp(22px, 5vw, 30px);
  line-height: 1.16;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-align: center;
  text-wrap: balance;
}

/* ===== Emoji avatar disc — shared by topbar, popover, profile editor ===== */
.avatar-choice__glyph-disc {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  flex-shrink: 0;
}
.topbar__avatar-btn { padding: 0; }
.topbar__avatar-btn .avatar-choice__glyph-disc { width: 32px; height: 32px; }

/* ===== Submit — voting-pool chip + my-pun-card edit/delete actions ===== */
.voting-pool-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  background: color-mix(in oklab, var(--tea) 55%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--good) 30%, transparent);
  color: var(--good); font-weight: 500; font-size: var(--fs-2xs);
}
/* During the 5-min edit window a pun is held out of the vote queue — a muted "not yet voting" chip. */
.voting-pool-chip--pending {
  background: var(--bg-sunk);
  border-color: var(--line);
  color: var(--ink-3);
}
/* Bottom action bar: delete pinned left, share pinned right, the edit pencil + countdown centered
   between them (only rendered while the pun is editable). A 3-column grid keeps the center group truly
   centered regardless of whether it's present. */
.my-pun-card__bar {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  position: relative;   /* anchor the armed delete-confirm pill (.result-del-confirm) below */
}
.my-pun-card__bar-del   { grid-column: 1; justify-self: start; }
.my-pun-card__bar-edit  { grid-column: 2; justify-self: center; display: flex; align-items: center; gap: 4px; }
.my-pun-card__bar-share { grid-column: 3; justify-self: end; }
/* When delete is armed, the SAME opaque confirm pill the Results / User page use floats into the bar's left
   edge, vertically centred on it (the card is centre-justified, so the bar has no fixed bottom offset to hang
   it from) — leaving the rest of the bar (stats / edit + share) in place underneath. */
.my-pun-card__bar .result-del-confirm { left: 0; bottom: auto; top: 50%; transform: translateY(-50%); }
/* The delete / share confirmations replace the whole bar, centered across all three columns. */
.my-pun-card__confirm-row {
  grid-column: 1 / -1; justify-self: center;
  display: flex; align-items: center; justify-content: center;
  gap: 4px; flex-wrap: wrap;
}
.my-pun-card__confirm { font-size: 12.5px; color: var(--ink-2); margin-right: 4px; }
.my-pun-card__edit-timer {
  font-family: var(--mono-font); font-size: 10.5px; color: var(--ink-3);
  letter-spacing: 0.04em; margin-left: 2px; font-variant-numeric: tabular-nums;
}
.my-pun-card .my-pun-card__textarea { width: 100%; text-align: left; }
.my-pun-card .my-pun-card__edit-row { width: 100%; }

/* ===== Shared inline-edit icon buttons + textarea (my-pun-card + profile) ===== */
.my-pun-card__icon-btn {
  appearance: none; border: 1px solid transparent; background: transparent;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  color: var(--ink-3); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.my-pun-card__icon-btn:hover:not(:disabled) {
  background: var(--bg-sunk); color: var(--ink); border-color: var(--line-strong);
}
.my-pun-card__icon-btn:disabled,
.my-pun-card__icon-btn.is-disabled { opacity: 0.32; cursor: not-allowed; }
.my-pun-card__icon-btn--danger:hover:not(:disabled) {
  background: color-mix(in oklab, var(--live) 12%, var(--bg-elev));
  color: var(--live); border-color: color-mix(in oklab, var(--live) 40%, transparent);
}
.my-pun-card__icon-btn--primary { color: var(--peach-deep); }
.my-pun-card__icon-btn--primary:hover:not(:disabled) {
  background: color-mix(in oklab, var(--peach) 14%, var(--bg-elev));
  color: var(--peach-deep); border-color: color-mix(in oklab, var(--peach) 40%, transparent);
}
.my-pun-card__icon-btn--good { color: var(--good); }
.my-pun-card__icon-btn--good:hover:not(:disabled) {
  background: color-mix(in oklab, var(--tea) 50%, var(--bg-elev));
  color: var(--good); border-color: color-mix(in oklab, var(--good) 40%, transparent);
}
.my-pun-card__textarea {
  width: 100%; resize: none;
  border: 1px solid var(--line-strong); background: var(--bg);
  font-family: var(--display-font); font-size: 16px; line-height: 1.3;
  letter-spacing: -0.005em; color: var(--ink);
  border-radius: var(--radius-sm); padding: 8px 10px; height: 64px;
  outline: none; transition: border-color 140ms, box-shadow 140ms;
}
.my-pun-card__textarea:focus {
  border-color: var(--peach);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--peach) 22%, transparent);
}
.my-pun-card__edit-row {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 8px; gap: 8px;
}
.my-pun-card__count {
  font-family: var(--mono-font); font-size: 11.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.my-pun-card__count.is-warn { color: var(--danger); }
.my-pun-card__edit-actions { display: flex; gap: 4px; }

/* ===== Vote — Skip ===== */
.vote-skip {
  display: block; margin: 14px auto 0;
  appearance: none; border: 0; background: transparent;
  color: var(--ink-3); cursor: pointer; font: inherit; font-size: var(--fs-sm); font-weight: 500;
  padding: 6px 12px; border-radius: var(--radius-sm);
  transition: color 120ms, background 120ms;
}
/* Scope hover to real pointers: on touch devices :hover sticks to the last-tapped button after the
   pair is decided, so the just-tapped Skip / "too alike" button would keep its highlight as a
   "remnant" when the next pair renders into the same DOM node (Blazor reuses the element). Same guard
   as the .choice cards above. */
@media (hover: hover) {
  .vote-skip:hover:not(:disabled) { color: var(--ink); background: var(--bg-sunk); }
}
.vote-skip:disabled { opacity: 0.4; cursor: not-allowed; }

/* ===== Results — day-card status badge + prompt (reuses existing live-pulse) ===== */
.day-card__status {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--mono-font); font-weight: 700;
  font-size: 10.5px; letter-spacing: 0.08em;
  padding: 2px 7px; border-radius: 3px;
}
.day-card__status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; }
.day-card__status--live {
  color: var(--live);
  border: 1px solid color-mix(in oklab, var(--live) 40%, transparent);
  background: color-mix(in oklab, var(--live) 8%, var(--bg-elev));
}
.day-card__status--live::before {
  background: var(--live);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--live) 22%, transparent);
  animation: live-pulse 1.6s ease-in-out infinite;
}
.day-card__status--pending {
  color: color-mix(in oklab, var(--peach-deep) 65%, var(--ink) 35%);
  border: 1px solid color-mix(in oklab, var(--clay) 60%, transparent);
  background: color-mix(in oklab, var(--clay) 22%, var(--bg-elev));
}
.day-card__status--pending::before { background: color-mix(in oklab, var(--peach-deep) 55%, var(--clay) 45%); }
.day-card__prompt {
  grid-column: 1; grid-row: 2;
  font-family: var(--display-font); font-weight: 500;
  font-size: 18px; letter-spacing: -0.01em; line-height: 1.2;
  color: var(--ink); text-wrap: pretty; overflow-wrap: anywhere;
}

/* ===== Results detail — prompt, pending, live thumbs, clickable row, score column ===== */
.detail-head__pending { color: color-mix(in oklab, var(--peach-deep) 65%, var(--ink) 35%); font-weight: 600; }
.result-row--btn { cursor: pointer; }
.result-row--btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.result-row__rank--medal { font-size: var(--fs-lg); }
.result-row__end {
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.result-row__share {
  appearance: none; border: 1px solid transparent; background: transparent;
  width: 26px; height: 26px; border-radius: var(--radius-sm);
  color: var(--ink-4); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.result-row__share:hover { background: var(--bg-sunk); color: var(--ink-2); border-color: var(--line-strong); }
.result-row__live-actions {
  display: flex; align-items: center; gap: 10px; margin-top: 6px; margin-left: -2px;
}
/* Star + share pinned to the bottom-right of the row (mirrors the vote card) so the quote + rank
   badge + meta above can sit higher with more breathing room without moving the action icons. The
   row reserves bottom padding (.result-row) for this absolutely-pinned strip. */
.result-row__actions {
  position: absolute;
  right: 14px; bottom: 13px;
  display: flex; align-items: center; justify-content: flex-end;
  gap: 26px;
  min-height: 28px;
}
.result-row__actions .result-row__share { position: static; right: auto; top: auto; transform: none; }
.result-thumb {
  appearance: none; background: transparent; border: 0; padding: 2px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 120ms, transform 120ms, opacity 120ms; opacity: 0.7;
}
.result-thumb:hover:not(:disabled) { opacity: 1; transform: scale(1.1); }
.result-thumb:active:not(:disabled) { transform: translateY(1px) scale(1); }
.result-thumb--up { color: var(--good); }
/* Thumbed: darken the icon by filling it solid — this is the only "you thumbed this" cue. */
.result-thumb.is-picked { opacity: 1; }
.result-thumb--up.is-picked { color: var(--good); }
.result-thumb--up.is-picked svg { fill: currentColor; stroke: currentColor; }

/* The unified "star" affordance (Results / user page). Muted outline by default; turns gold and fills
   solid on hover and once you've starred the pun — same color + fill behavior as the vote screen. */
.result-star {
  appearance: none; background: transparent; border: 0; padding: 2px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; color: var(--ink-3);
  transition: color 120ms, transform 120ms, opacity 120ms; opacity: 0.7;
}
.result-star:hover:not(:disabled) { opacity: 1; transform: scale(1.1); color: var(--star-gold); }
.result-star:active:not(:disabled) { transform: translateY(1px) scale(1); }
.result-star:disabled { cursor: default; opacity: 0.35; }
.result-star.is-picked { opacity: 1; color: var(--star-gold); }

/* "mine" tag in the star's place on the viewer's own puns in My Feed (which renders the shared result-row
   layout). You can't star your own pun, so rather than a dead star we label it as yours — the cue that lets a
   player pick out their own puns in the feed. (The Results board uses the corner badge instead, and the User
   page shows nothing in that slot, per the profile-polish change — so My Feed is now the only user of this.) */
.result-mine {
  display: inline-flex; align-items: center;
  font-family: var(--display-font); font-weight: 600;
  font-size: var(--fs-2xs); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3); opacity: 0.65; user-select: none; white-space: nowrap;
}

/* Deleting your own still-running pun from anywhere you see it (Submit, Results, your User page). The trash
   sits bottom-LEFT — same position as the Submit card (delete left, share right) — while the "mine" tag stays
   in the bottom-right actions strip (in the star's place). On the Results row the strip is absolutely pinned
   into the row's reserved bottom band, mirroring .result-row__actions on the opposite side; on the User-page
   foot (a flex row) .result-own is just an inline cluster at the start. The trash mirrors .result-star but
   turns danger-red. */
.result-row__del-strip { position: absolute; left: 14px; bottom: 13px; display: flex; align-items: center; gap: 6px; }
.result-own { display: inline-flex; align-items: center; gap: 8px; }
.result-del {
  appearance: none; background: transparent; border: 0; padding: 2px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; color: var(--ink-3);
  transition: color 120ms, transform 120ms, opacity 120ms; opacity: 0.7;
}
.result-del:hover:not(:disabled) { opacity: 1; transform: scale(1.1); color: var(--live); }
.result-del:active:not(:disabled) { transform: translateY(1px) scale(1); }
.result-del:disabled { cursor: default; opacity: 0.35; }
.result-del--confirm { color: var(--live); opacity: 1; }

/* Armed delete confirmation: an OPAQUE pill floated into the pun box's bottom-LEFT corner (over the stats
   line) so "Delete?" + the keep/confirm controls read clearly, instead of being lost amid the stats. Anchored
   to the .result-row card (position:relative) — its own solid fill + border carry it over whatever it
   overlaps; z-index lifts it above the centred stats. Same markup on the Results board + the User page. */
.result-del-confirm {
  position: absolute; left: 12px; bottom: 8px; z-index: 6;
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px 5px 3px 12px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.45);
}
.result-del-confirm__label {
  font-family: var(--display-font); font-weight: 600; font-size: 12px;
  letter-spacing: 0.01em; color: var(--ink-2); white-space: nowrap; user-select: none;
}
/* The keep/confirm icons sit tighter inside the pill than the 34px statsbar trash (later source order than the
   .result-row__statsbar .result-del sizing, so these win at equal specificity). */
.result-del-confirm .result-del { width: 28px; height: 28px; }
.result-del-confirm .result-del svg { width: 16px; height: 16px; }

/* The shared <StarGlyph /> fills solid once starred — identical in every pun box (vote / results /
   user); otherwise it shows as an outline. */
.result-star.is-picked .star-glyph { fill: currentColor; }
/* The hover fill is pointer-only: on touch the tapped star keeps :hover after advancing, which would
   leave the outline filled on the next (unstarred) pun. */
@media (hover: hover) {
  .result-star:hover:not(:disabled) .star-glyph { fill: currentColor; }
}

/* Star + share pinned to the bottom-right of a user-profile pun card (share
   rightmost, star to its left) — mirrors the Results pun boxes. */
.user-pun__actions { display: flex; align-items: center; gap: 18px; margin-left: auto; }

/* ===== Leaderboard — clickable row + top-pun prompt line ===== */
.leader-row--btn {
  width: 100%; cursor: pointer; text-align: left; font: inherit; color: inherit;
}
.leader-row--btn:hover { border-color: var(--line-strong); }
.leader-row--btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.leader-row__pun-prompt {
  display: block;
  font-family: var(--display-font); font-weight: 500; font-size: var(--fs-sm);
  letter-spacing: -0.005em; color: var(--ink-3); font-style: italic;
  text-wrap: pretty; overflow-wrap: anywhere; margin-bottom: 2px;
}
/* Eyebrow + prompt share line 1 when they fit; the (nowrap) prompt wraps to its own line otherwise. */
.leader-row__pun-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: 8px; row-gap: 1px; margin-bottom: 4px; min-width: 0;
}
.leader-row__pun-head .leader-row__pun-eyebrow { margin-bottom: 0; white-space: nowrap; }
.leader-row__pun-head .leader-row__pun-prompt { margin-bottom: 0; white-space: nowrap; min-width: 0; }

/* ===== Profile popover — admin row accent ===== */
.profile-pop__item--admin { color: var(--peach-deep); }
.profile-pop__item--admin svg { color: var(--peach-deep); }

/* ===== Profile editor (Screen 6) ===== */
.profile-hero {
  display: flex; align-items: center; gap: 16px;
  padding: 14px 16px; margin-bottom: 14px;
}
.profile-hero__name { min-width: 0; flex: 1; }
.profile-hero__name-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.profile-hero__username {
  font-family: var(--display-font); font-weight: 500; font-size: var(--fs-xl);
  letter-spacing: -0.02em; color: var(--ink); line-height: 1.1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.profile-hero__edit-btn {
  appearance: none; border: 1px solid var(--line-strong); background: var(--bg-elev);
  width: 32px; height: 32px; border-radius: var(--radius-sm); color: var(--ink-3);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; transition: background 120ms, color 120ms, border-color 120ms;
}
.profile-hero__edit-btn:hover { background: var(--bg-sunk); color: var(--ink); border-color: var(--ink-3); }
.profile-hero__name-edit { display: flex; align-items: center; gap: 6px; min-width: 0; }
.profile-hero__name-input {
  flex: 1; font-family: var(--display-font); font-weight: 500; font-size: 22px;
  letter-spacing: -0.02em; padding: 6px 10px; min-width: 0;
}
.profile-hero__name-actions { display: flex; gap: 2px; flex-shrink: 0; }
.profile-hero__name-error { font-size: var(--fs-xs); color: var(--danger); margin-top: 6px; letter-spacing: 0.01em; }
.profile-field {
  padding: 12px 14px 14px; margin-bottom: 10px;
}
.profile-field__label {
  display: block; font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 8px;
}
.profile-select { width: 100%; padding: 9px 32px 9px 12px; font-size: 14.5px; }
.text-input {
  width: 100%; appearance: none; border: 1px solid var(--line-strong); background: var(--bg-elev);
  border-radius: var(--radius-sm); padding: 9px 12px; font: inherit; font-size: 14.5px;
  color: var(--ink); outline: none; transition: border-color 120ms, box-shadow 120ms;
}
.text-input:focus {
  border-color: var(--peach); box-shadow: 0 0 0 3px color-mix(in oklab, var(--peach) 22%, transparent);
}
.avatar-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(52px, 1fr)); gap: 8px; }
@media (min-width: 360px) { .avatar-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@media (min-width: 640px) { .avatar-grid { grid-template-columns: repeat(10, minmax(0, 1fr)); } }
.avatar-choice {
  appearance: none; background: transparent; border: 2px solid transparent;
  border-radius: 50%; padding: 2px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  /* Hug the avatar disc so the round border stays a circle — a grid item otherwise stretches to the
     (wider) cell, turning the 50% radius into an oval selection ring. */
  justify-self: center; align-self: center; aspect-ratio: 1;
  transition: border-color 120ms, transform 120ms, background 120ms;
}
.avatar-choice:hover { transform: scale(1.06); }
.avatar-choice.is-checked {
  border-color: var(--peach); background: color-mix(in oklab, var(--peach) 10%, var(--bg-elev));
}

/* "More…" tile — same footprint as an avatar disc, but a dashed, muted call-to-open. */
.avatar-more {
  flex-direction: column; gap: 1px; width: 48px; height: 48px;
  border: 2px dashed color-mix(in oklab, var(--ink) 22%, transparent);
  background: var(--bg-elev); color: var(--ink-3);
}
.avatar-more:hover { border-color: var(--peach); color: var(--ink); }
.avatar-more__plus { font-size: 18px; line-height: 1; }
.avatar-more__label { font-size: 9px; font-weight: 600; letter-spacing: 0.02em; }

/* Full-catalogue picker overlay (above the Profile overlay and the welcome backdrop). */
.avatar-modal { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; place-items: center; padding: 16px; }
.avatar-modal__backdrop {
  position: absolute; inset: 0; background: color-mix(in oklab, var(--ink) 42%, transparent);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.avatar-modal__card {
  position: relative; width: min(560px, 100%); max-height: min(78vh, 640px);
  display: flex; flex-direction: column; gap: 12px;
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius-md, 14px);
  box-shadow: 0 18px 50px color-mix(in oklab, var(--ink) 28%, transparent); padding: 16px;
}
.avatar-modal__head { display: flex; align-items: center; justify-content: space-between; }
.avatar-modal__title {
  margin: 0; font-family: var(--display-font); font-weight: 500; font-size: 18px; letter-spacing: -0.02em;
}
.avatar-modal__close {
  appearance: none; border: none; background: transparent; cursor: pointer;
  font-size: 18px; line-height: 1; color: var(--ink-3); padding: 4px 8px; border-radius: var(--radius-sm);
}
.avatar-modal__close:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 8%, transparent); }
.avatar-modal__search { width: 100%; }
.avatar-modal__tabs { display: flex; flex-wrap: wrap; gap: 6px; }
.avatar-tab {
  appearance: none; cursor: pointer; font-size: 12.5px; font-weight: 500;
  padding: 4px 10px; border-radius: 999px; color: var(--ink-3);
  border: 1px solid var(--line); background: transparent;
}
.avatar-tab:hover { color: var(--ink); }
.avatar-tab.is-active {
  color: var(--ink); border-color: transparent;
  background: color-mix(in oklab, var(--peach) 22%, var(--bg-elev)); font-weight: 600;
}
.avatar-modal__grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); gap: 8px;
  overflow-y: auto; padding: 4px 2px; min-height: 0;
}
.avatar-modal__empty { grid-column: 1 / -1; margin: 12px 2px; color: var(--ink-3); font-size: var(--fs-sm); }
.profile-save {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
.profile-save__flash {
  font-size: 12.5px; color: var(--good); padding: 4px 10px; border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--tea) 55%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--good) 30%, transparent); font-weight: 500;
}

/* ===== User page (Screen 7) ===== */
.user-head { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
.user-head__name {
  font-family: var(--display-font); font-weight: 500; font-size: 22px;
  letter-spacing: -0.02em; margin: 0; line-height: 1.1;
  overflow-wrap: anywhere;
}
.user-head__city { margin: 2px 0 0; font-size: var(--fs-sm); color: var(--ink-3); overflow-wrap: anywhere; }
/* Username + the inline "+" follow control on one line (matches the Results/Leaderboard identity line). */
.user-head__name-line { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.user-head__name-line .user-head__name { flex: 0 1 auto; min-width: 0; }
/* Creator score shown as "169 pts" beside the share button — same styling as the per-pun points below it. */
.user-head__pts {
  white-space: nowrap; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums; font-size: 14px;
}

/* Loading skeleton for the User-page identity head — shown until the target profile loads, so the avatar /
   name / score don't flash in from an "anonymous" / 0 placeholder. Reuses the .user-head flex row, so the
   layout matches what replaces it. */
.user-head__lines { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.skel { background: var(--bg-sunk); border-radius: 6px; display: inline-block; animation: skel-pulse 1.4s ease-in-out infinite; }
.skel--avatar { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; }
.skel--line { height: 12px; }
.skel--name { width: 150px; height: 19px; }
.skel--sub { width: 92px; }
/* Block skeletons for first-paint loading on Vote / Results / Leaderboard / Feed (was a bare "Loading…").
   They reuse the .skel shimmer; a visually-hidden "Loading…" stays for screen readers. */
.skel-list { display: grid; gap: 8px; }
.skel--card { display: block; width: 100%; height: 76px; border-radius: var(--radius); }
.skel--choice { display: block; width: 100%; height: 120px; border-radius: var(--radius); margin-bottom: 6px; }
.skel--prompt { display: block; width: 72%; max-width: 460px; height: 26px; border-radius: 8px; margin: 8px auto 18px; }
.skel--leader { height: 64px; }
@keyframes skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

.user-sort {
  display: inline-flex; background: var(--bg-sunk); padding: 4px;
  border-radius: var(--radius); border: 1px solid var(--line); margin: 8px 0 14px; gap: 2px;
}
.user-sort__btn {
  appearance: none; border: 0; background: transparent; font: inherit;
  padding: 7px 14px; border-radius: 5px; color: var(--ink-3); cursor: pointer;
  font-weight: 500; font-size: var(--fs-sm); transition: background 120ms, color 120ms, box-shadow 120ms;
}
.user-sort__btn:hover { color: var(--ink); }
.user-sort__btn.is-active { background: var(--bg-elev); color: var(--ink); box-shadow: 0 1px 0 rgba(42,33,27,0.06); }
.user-puns { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }
.user-pun {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 14px; transition: border-color 140ms, box-shadow 140ms;
}
.user-pun:hover { border-color: var(--line-strong); }
.user-pun__main { flex: 1; min-width: 0; }
.user-pun__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding-bottom: 8px; border-bottom: 1px solid var(--line); margin-bottom: 8px;
}
.user-pun__prompt {
  font-family: var(--display-font); font-weight: 500; font-size: 13.5px;
  letter-spacing: -0.005em; color: var(--ink-2); font-style: italic; min-width: 0;
  white-space: normal; text-wrap: pretty; line-height: 1.25; overflow-wrap: anywhere;
}
.user-pun__date {
  font-family: var(--mono-font); font-size: var(--fs-2xs); color: var(--ink-3);
  letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap; flex-shrink: 0;
}
.user-pun__text {
  font-family: var(--display-font); font-size: 16px; line-height: 1.32;
  letter-spacing: -0.005em; color: var(--ink); margin: 0; text-wrap: pretty; overflow-wrap: anywhere;
}
/* Points sit on the left; the auto margin on the actions pins star + share to
   the right corner (matching the pun boxes on Results). */
.user-pun__foot { display: flex; align-items: center; gap: 3px; margin-top: 8px; }
.user-pun__score {
  font-family: var(--display-font); font-size: 18px; font-weight: 500;
  letter-spacing: -0.02em; color: var(--ink); font-feature-settings: "tnum";
}
.user-pun__score-label { font-size: var(--fs-xs); color: var(--ink-3); }

/* ===== First-run welcome overlay (Components/Welcome.razor) ===== */
.welcome-backdrop {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: color-mix(in oklab, var(--ink) 42%, transparent);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: welcome-fade 200ms ease-out;
}
@keyframes welcome-fade { from { opacity: 0; } }
.welcome-card {
  position: relative;
  /* Fixed, identical footprint on every step (and at every breakpoint) so the
     lightbox never resizes as you page through — the 30ch body keeps wrapping
     constant, and the content area flexes to absorb shorter steps. Height is
     sized to the tallest panel (the logo welcome, ~372px) plus a little slack
     so the longest text still sits comfortably. */
  width: 100%; max-width: 380px; height: 392px;
  display: flex; flex-direction: column;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  padding: 30px 26px 22px; text-align: center;
  animation: pop-in 180ms cubic-bezier(.2,.7,.2,1);
}
.welcome-card__content {
  flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0;
}
.welcome-card__close {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: transparent; border: none; border-radius: 50%;
  color: var(--ink-3); cursor: pointer; transition: background 120ms, color 120ms;
}
.welcome-card__close:hover { background: var(--bg-sunk); color: var(--ink); }
.welcome-card__art {
  font-size: 44px; line-height: 1; margin-bottom: 12px;
}
/* Step 1 hero logo — the source PNG is 512×512, so it must be constrained or it
   overflows the card. */
.welcome-card__logo-img { margin-bottom: 14px; }
.welcome-card__logo-img img {
  display: block; width: 104px; height: auto; max-width: 100%; margin: 0 auto;
}
/* Steps 2–7 line-icon badge — a tinted circle with the glyph sized to 30px. */
.welcome-card__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 60px; height: 60px; margin: 0 auto 14px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--peach) 16%, transparent);
  color: var(--peach);
}
.welcome-card__icon svg { width: 30px; height: 30px; }
.welcome-card__nav { display: flex; align-items: center; gap: 8px; }
.welcome-card__title {
  font-family: var(--display-font); font-weight: 600;
  font-size: clamp(20px, 5vw, 24px); letter-spacing: -0.01em;
  color: var(--ink); margin: 0 0 8px;
}
.welcome-card__body {
  color: var(--ink-2); font-size: 14.5px; line-height: 1.5;
  margin: 0 auto 18px; max-width: 30ch; text-wrap: pretty;
}
.welcome-card__dots {
  display: flex; justify-content: center; gap: 7px;
  margin-top: auto; margin-bottom: 18px;
}
.welcome-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--line-strong); transition: background 160ms, transform 160ms;
}
.welcome-dot.is-active { background: var(--peach); transform: scale(1.25); }
.welcome-card__actions {
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.welcome-card__actions .btn { min-width: 76px; }

/* ===== Register / sign-in (Register.razor) ===== */
.reg-block { width: 100%; justify-content: center; margin-top: 4px; }
.reg-or {
  display: flex; align-items: center; gap: 10px; margin: 16px 0 6px; color: var(--ink-4);
  font-size: var(--fs-xs); letter-spacing: 0.1em; text-transform: uppercase;
}
.reg-or::before, .reg-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.btn-oauth {
  background: var(--bg-elev); color: var(--ink); border-color: var(--line-strong); font-weight: 500;
}
.btn-oauth:not(:disabled):hover { background: var(--bg-sunk); }
.reg-foot { margin: 14px 2px 0; font-size: 12.5px; color: var(--ink-3); text-align: center; }
.reg-optional { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink-4); }
.reg-hint { margin: -2px 2px 8px; font-size: 12.5px; line-height: 1.45; color: var(--ink-3); }
.reg-hint em { font-style: normal; font-weight: 600; color: var(--ink-2); }
.reg-age {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 14px; line-height: 1.4; color: var(--ink-2); cursor: pointer;
}
.reg-age input[type="checkbox"] { margin-top: 2px; width: 16px; height: 16px; flex: 0 0 auto; cursor: pointer; }
.reg-sent {
  display: grid; gap: 12px; color: var(--ink-2); font-size: 14.5px; line-height: 1.5;
  min-width: 0;
  max-width: 100%;
}
.reg-sent p {
  margin: 0;
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.reg-sent strong { overflow-wrap: anywhere; }
.reg-sent__junk { color: var(--ink-3); font-size: var(--fs-sm); }
.reg-sent .btn-sm { justify-self: start; }
.reg-verified {
  display: flex; align-items: center; gap: 8px; margin-bottom: 12px; padding: 9px 12px;
  border-radius: var(--radius-sm); font-size: 13.5px; color: var(--ink-2);
  background: color-mix(in oklab, var(--tea) 45%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--good) 28%, transparent);
  min-width: 0;
}
.reg-verified span:last-child { min-width: 0; overflow-wrap: anywhere; }
.reg-verified strong { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
.reg-verified__check { display: inline-flex; color: var(--good); }
.reg-name-msg { font-size: var(--fs-xs); margin-top: 6px; letter-spacing: 0.01em; }
.reg-name-msg.is-ok { color: var(--good); }
.reg-name-msg.is-bad { color: var(--danger); }
/* Two equal 50% columns — the first/last name pair. */
.reg-name-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Grid items default to min-width:auto, which lets the inner inputs force the row past the viewport
   on a narrow phone — pin to 0 so the paired fields always shrink to fit. */
.reg-name-row > .profile-field { min-width: 0; }
.reg-private-note { font-size: var(--fs-xs); margin-top: 6px; color: var(--ink-4); }

/* ===== Sharing — landing, contest/user share buttons, vote CTA, results spotlight ===== */

/* "Share this contest" (Results detail head) / "Share this pundit" (User page head). Pushed to the
   right edge of the flex header row. */
.detail-head__share,
.user-head__share {
  appearance: none; margin-left: auto; flex-shrink: 0;
  width: 36px; height: 36px; border-radius: var(--radius);
  background: var(--bg-elev); border: 1px solid var(--line-strong);
  color: var(--ink-3); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.detail-head__share:hover,
.user-head__share:hover { background: var(--bg-sunk); color: var(--ink); }

/* "Submit your own pun" call-to-action under the vote stack — converts a shared-vote arrival. */
.vote-submit-cta {
  margin-top: 14px; text-align: center;
  font-size: var(--fs-sm); color: var(--ink-3);
}
.vote-submit-cta a { color: var(--accent-line); font-weight: 600; text-decoration: none; }
.vote-submit-cta a:hover { text-decoration: underline; }

/* Prominent variant: a full-width, bordered call-to-action card so "submit your own" stands out. */
.vote-submit-cta--prominent {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-top: 18px; padding: 14px 16px;
  border: 1.5px solid color-mix(in oklab, var(--accent-line) 55%, transparent);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--accent-line) 7%, var(--bg-elev));
  text-decoration: none;
  transition: background 140ms, border-color 140ms, transform 140ms;
}
.vote-submit-cta--prominent:hover {
  background: color-mix(in oklab, var(--accent-line) 13%, var(--bg-elev));
  transform: translateY(-1px);
}
.vote-submit-cta__lead { font-size: var(--fs-sm); color: var(--ink-3); }
.vote-submit-cta__action { font-size: 16px; font-weight: 700; color: var(--accent-line); }

/* "Wanna give it a try?" nudge shown to a non-submitter after 10 votes. */
.try-it-card {
  margin-top: 28px; text-align: center;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: 28px 22px;
}
.try-it-card__title { font-family: var(--display-font); font-size: clamp(22px, 4vw, 28px); margin: 0 0 6px; }
.try-it-card__sub { color: var(--ink-3); font-size: 14.5px; margin: 0 0 18px; }
.try-it-card__actions { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; }
.try-it-card__note { color: var(--ink-3); font-size: 13px; margin: 16px 0 0; }

/* Results spotlight: the shared pun ranks #11+, so we show top 5, this ellipsis, then the pun. */
.result-ellipsis {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 6px 0 2px; color: var(--ink-4);
}
.result-ellipsis__dots { font-size: 18px; letter-spacing: 0.15em; line-height: 1; }
.result-ellipsis__count {
  font-family: var(--mono-font); font-size: 10.5px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-4);
}

/* Highlighted shared pun row on the Results board. */
.result-row.is-shared {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line);
}

/* User-page career "superlatives" — a row of compact stat chips. */
.superlatives {
  list-style: none; margin: 4px 0 14px; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.superlative {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 8px 12px; min-width: 64px;
}
.superlative__num {
  font-weight: 700; font-size: 16px; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.superlative__label { font-size: 10.5px; color: var(--ink-3); letter-spacing: 0.01em; }

/* Final rank shown in the left gutter of a User-page pun (mirrors the Results board); blank while live. */
.user-pun__rank {
  flex-shrink: 0; min-width: 34px; padding-top: 2px;
  font-family: var(--mono-font); font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink-3); text-align: center;
}

/* ===== Groups ("leagues") ===== */
.group-section { margin: 18px 0; }
.group-section__title {
  font-size: var(--fs-xs); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin: 0 0 8px;
}
.group-section--danger { display: flex; gap: 10px; margin-top: 26px; }
.group-delete { color: var(--danger, #c0392b); }

.group-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.group-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
}
.group-card--btn { cursor: pointer; width: 100%; text-align: left; font: inherit; color: inherit;
  transition: border-color 140ms, box-shadow 200ms, transform 100ms; }
.group-card--btn:hover { border-color: var(--line-strong); box-shadow: var(--shadow-card); }
.group-card--btn:active { transform: translateY(1px); }
/* Avatar + name/location as one block: align-items:center vertically centers the 32px avatar on the name
   line (no location) or the name+location pair (with location), independent of the taller actions column. */
.group-card__person { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
.group-card__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.group-card__name { font-weight: 600; display: flex; align-items: center; gap: 8px; min-width: 0; }
/* Location always on ONE line below the name (ellipsis if it can't fit), never wrapping to 2–3 lines. */
.group-card__loc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.group-card__chev { color: var(--ink-3); font-size: var(--fs-lg); line-height: 1; }
/* The two member actions STACK vertically (equal width) on the right so the row fits on mobile without
   squeezing the name/location. flex-shrink:0 keeps them at their natural width; the person block yields. */
.group-card__actions { display: flex; flex-direction: column; align-items: stretch; gap: 6px; flex-shrink: 0; }
.group-card__actions .btn { width: 100%; }

/* Share-invite button on a group list row — same glyph as elsewhere, sized between the inline pun-card
   share (28px) and the detail-head share (36px). Sits just left of the › chevron. */
.group-card__share {
  appearance: none; flex-shrink: 0;
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent;
  color: var(--ink-3); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.group-card__share:hover {
  background: var(--bg-sunk); color: var(--ink); border-color: var(--line-strong);
}

.group-tag {
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700;
  color: var(--peach-deep, var(--peach)); padding: 1px 6px; border-radius: 3px;
  border: 1px solid color-mix(in oklab, var(--peach) 40%, transparent);
  background: color-mix(in oklab, var(--peach) 8%, var(--bg-elev));
}

.group-create { display: flex; flex-direction: column; gap: 10px; max-width: 520px; }
/* The name field + action button sit on one row: the button is only as wide as its label ("Create group" /
   "Save group", nowrap) and the name field flexes to fill the rest. align-items: stretch keeps the button
   the same height as the field. */
.group-create__row { display: flex; align-items: stretch; gap: 10px; }
.group-create__row .btn { flex: 0 0 auto; }
.group-create__name {
  flex: 1; min-width: 0;
  appearance: none; border: 1px solid var(--line-strong); background: var(--bg-elev);
  border-radius: var(--radius-sm); padding: 10px 12px; font: inherit; font-size: 14.5px;
  color: var(--ink); outline: none; transition: border-color 120ms, box-shadow 120ms;
}
.group-create__name:focus {
  border-color: var(--peach); box-shadow: 0 0 0 3px color-mix(in oklab, var(--peach) 22%, transparent);
}
.group-create__policy { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--ink-2); }
.group-create__error { color: var(--danger, #c0392b); font-size: var(--fs-sm); }

.group-join-card {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 28px 20px; max-width: 420px; margin: 0 auto; text-align: center;
}
.group-join-card__name { font-size: 22px; font-weight: 700; }

/* ============================================================
   Design v4 — light/dark redesign tweaks (load themes.css after this file)
   ============================================================ */

/* Center the leaderboard intro copy under its centered title. The width is constrained HERE (not on the
   base .page-sub, whose global 56ch cap was removed 2026-06-05 because it narrowed every page's
   sub-header) so only this centered subtitle reads as a tidy block. */
.page-leaders .page-sub {
  max-width: 56ch;
  margin-left: auto; margin-right: auto;
  text-align: center;
}

/* The bold, contest-coloured prompt banner — IDENTICAL on the Vote screen and on a Contest Result for the
   same contest. Coloured per contest via --vote-accent / --vote-on-accent (set in themes.css off the
   .contest-today / .contest-yesterday / .contest-prior class on the page), so it matches the Vote cards'
   accent stripe + picked-card fill. Self-contained (doesn't lean on .contest-prompt) so the two pages can
   render the exact same box. The SUBMIT prompt keeps the single brand accent (.contest-prompt below). */
.prompt-banner {
  margin: 0 0 12px;
  padding: 10px 18px 7px;
  border-radius: var(--radius-lg);
  background: var(--vote-accent);
  border: 1px solid transparent;
  box-shadow: 0 8px 22px -12px color-mix(in oklab, var(--vote-accent) 80%, transparent);
  font-family: var(--display-font);
  font-weight: 600;
  font-size: clamp(22px, 5vw, 30px);
  line-height: 1.16;
  letter-spacing: -0.015em;
  color: var(--vote-on-accent);
  /* The star bank is pinned to the right edge. These symmetric gutters (each wide enough to clear the bank:
     Cap=3 × 18px glyphs + 2×5px gaps ≈ 64px, held at right:14px → ~78px in) are the pre-JS / prerender
     DEFAULT, centring the prompt in the WHOLE box. At runtime `pundown.promptFit` overrides padding-left/right
     per box to centre the prompt in the box MINUS the bank (a small left pad, the bank's width reserved on the
     right) and SHRINKS the font so the prompt never wraps past two lines. Re-fits on resize (adapts to the
     display width). */
  --prompt-banner-gutter: 84px;
  position: relative;
  padding-left: var(--prompt-banner-gutter);
  padding-right: var(--prompt-banner-gutter);
  text-align: center;
}
.prompt-banner__text { display: block; min-width: 0; text-align: center; text-wrap: balance; }
/* Star bank: <Cap> slots, the first <Remaining> filled gold, the rest faint outlines on the accent fill.
   The glyph is the shared <StarGlyph/> sized to match the puns' star buttons exactly. Absolutely pinned to
   the right edge and vertically centred in the box, so it stays centred no matter how many lines the prompt
   wraps to. */
.prompt-banner__stars { position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 5px; cursor: help; }
.prompt-banner__stars:focus-visible { outline: 2px solid var(--vote-on-accent); outline-offset: 3px; border-radius: 4px; }
/* Hover (desktop) / tap (mobile) note: how many stars are left this contest. A normal card below the
   bank — readable regardless of the prompt's accent fill — that doesn't shift layout. */
.prompt-star-tip {
  position: absolute; top: calc(100% + 9px); right: 0;
  width: max-content; max-width: 230px;
  background: var(--bg-elev); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: 0 12px 28px -10px rgba(0,0,0,0.32);
  padding: 8px 11px;
  font-family: var(--ui-font); font-size: var(--fs-sm); font-weight: 600; letter-spacing: 0;
  line-height: 1.32; text-align: left; text-wrap: pretty;
  z-index: 40; pointer-events: none;
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity 130ms ease, transform 130ms ease, visibility 130ms;
}
/* A little caret pointing up at the stars. */
.prompt-star-tip::before {
  content: ""; position: absolute; top: -5px; right: 12px;
  width: 9px; height: 9px; background: var(--bg-elev);
  border-left: 1px solid var(--line); border-top: 1px solid var(--line);
  transform: rotate(45deg);
}
.prompt-banner__stars.is-tip-open .prompt-star-tip { opacity: 1; visibility: visible; transform: none; }
@media (hover: hover) {
  .prompt-banner__stars:hover .prompt-star-tip { opacity: 1; visibility: visible; transform: none; }
}
.prompt-star { display: inline-flex; align-items: center; justify-content: center; }
/* Pin every star glyph to one explicit size — the prompt bank, the Vote star button, and the Results
   star button — so the prompt stars and the puns' star buttons are exactly the same size (the SVG would
   otherwise flex-shrink slightly inside its button). flex:0 0 auto stops the flex squish. */
.prompt-star .star-glyph,
.result-star .star-glyph { width: 18px; height: 18px; flex: 0 0 auto; }
.prompt-star.is-filled { color: var(--star-gold); }
.prompt-star.is-filled .star-glyph { fill: currentColor; }
.prompt-star:not(.is-filled) { color: color-mix(in oklab, var(--vote-on-accent) 50%, transparent); }
/* The star that's mid-flight to a pun is hidden in the bank (the flying clone is its only copy) — but its
   slot keeps its space (visibility, not display) so nothing shifts. */
.prompt-star.is-flying { visibility: hidden; }

/* The star that flies from the prompt bank to a pun's star button when you star it. A fixed-position
   clone so it crosses the viewport regardless of scroll (the bank is frozen at the top). */
.star-fly {
  position: fixed; z-index: var(--z-fly); pointer-events: none;
  color: var(--star-gold);
  display: inline-flex; align-items: center; justify-content: center;
  filter: drop-shadow(0 3px 7px rgba(0,0,0,0.28));
}
.star-fly .star-glyph { width: 100%; height: 100%; fill: currentColor; }
.page-submit .contest-prompt {
  background: var(--peach);
  border-color: transparent;
  color: #fff;
  font-weight: 600;
  /* Positioning context for the in-banner share icon; the wider horizontal padding reserves space on
     both sides so the centered prompt text never collides with the top-right share button. */
  position: relative;
  padding: 10px 40px 8px;
  box-shadow: 0 8px 22px -12px color-mix(in oklab, var(--peach) 80%, transparent);
}

/* "Share today's prompt" — a plain icon vertically centred on the right of the Submit prompt banner. Like
   the share icon in the pun boxes it carries NO border or background shade at rest (just the glyph); it
   inherits the banner's text colour (white on the light/blue surface, dark on the dark/gold one) so it
   reads on both themes, and only tints a faint hover background from that currentColor for feedback. */
.contest-prompt__share {
  appearance: none;
  position: absolute; top: 50%; right: 8px;
  transform: translateY(-50%);
  width: 30px; height: 30px; padding: 0; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: inherit; cursor: pointer;
  background: transparent;
  border: 1px solid transparent;
  transition: background 120ms, border-color 120ms;
}
.contest-prompt__share:hover {
  background: color-mix(in oklab, currentColor 18%, transparent);
}
/* Match the pun-box share glyph size (the statsbar share renders its SVG at 18px) so the two read the same. */
.contest-prompt__share svg { width: 18px; height: 18px; }

/* Freeze the contest header below the sticky top bar as the board scrolls. Vote freezes just the prompt
   banner; a Contest Result freezes the whole cluster (banner → date/stats/share row → group filter).
   `top: var(--topbar-h)` parks it right under the header (height kept live by pundown.topbar.fit). The
   opaque page-bg + flow-root (which contains the children's margins) keep scrolling rows from peeking
   through the gaps between the banner and the meta/filter. */
.vote-head,
.detail-sticky {
  position: sticky;
  top: var(--topbar-h);
  z-index: var(--z-sticky);
  background: var(--bg);
  display: flow-root;
}
/* Under the frozen cluster: a hairline divider, then a SMALL clean gap before the scrolling rows. Both the
   divider and the gap belong to the frozen element — an opaque `padding-bottom` (NOT a transparent margin,
   which would let a row's top border/shadow peek through) with the line drawn by `::after` just ABOVE that
   padding. So a row scrolling up is masked by the opaque page-bg the whole way; its border/shadow never
   leaks into the frozen zone or the gap, and the gap shows clean page-bg. */
.detail-sticky {
  padding-bottom: 9px;
  /* A 2px clear gap BELOW the opaque mask (which spans the padding box only, not the margin) so the first pun
     row's top border always sits clear of the frozen cluster. Without it the row is flush against the mask's
     bottom edge, and once the cluster has stuck the sticky background covers that 1px border line at a
     sub-pixel boundary — so it reads fine on first paint but vanishes after a scroll down-and-back. */
  margin-bottom: 2px;
}
/* No divider hairline between the frozen prompt cluster and the puns (removed per request) — the opaque mask
   + the small bottom gap still separate them, just with no visible line. */
/* Inside the frozen cluster the banner's bottom margin is the gap to the filter row. */
.detail-sticky .lb-controls { margin-top: 2px; margin-bottom: 8px; }

/* Full-bleed mask behind EVERY frozen header. The header sits in the centered content column
   (`.main__inner`), and its own background only covers that column — but the scrolling cards' box-shadow
   reaches ~16px PAST the column into `.main`'s horizontal padding, so without this the card shadows leak in
   those side strips inside the frozen zone (visible as faint lines beside the prompt banner / filter row).
   A `::before` opaque-bg layer extends the mask 24px past the column on both sides (clipped at the viewport
   by `html{overflow-x:clip}`, and harmlessly invisible in the desktop centering margin since it's the page
   colour). z-index:-1 keeps it within the header's sticky stacking context (z-index 30) — so it paints ABOVE
   the scrolling cards but BEHIND the header's own content + its `::after` hairline. */
.detail-sticky::before,
.page-leaders .lb-controls::before,
.page-results .lb-controls::before,
.feed-group__head::before,
.page-user .detail-head::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: -24px; right: -24px;
  background: var(--bg);
  z-index: -1;
  pointer-events: none;
}

/* Results day header — date + pun stats + closing time + share, ABOVE the prompt box. It sits in normal
   flow (NOT in .detail-sticky), so it scrolls away ("collapses") as the board scrolls while the prompt box
   and filters below stay pinned. The closing-time remark rides on the same row as the date. */
.results-day-head { margin-bottom: 12px; }
.results-day-head__titlerow {
  display: flex; align-items: baseline; gap: 4px 8px; flex-wrap: wrap; min-width: 0;
}
.results-day-head__closing { font-size: var(--fs-sm); font-weight: 600; color: var(--live); white-space: nowrap; }
.results-day-head__pending {
  font-size: var(--fs-sm); font-weight: 600;
  color: color-mix(in oklab, var(--peach-deep) 65%, var(--ink) 35%);
}

/* ===== Site footer — copyright + legal links on every page ===== */
.site-footer {
  margin-top: 36px;
  padding: 26px 16px 34px;
  border-top: 1px solid var(--line);
  text-align: center;
  flex-shrink: 0;   /* never squeeze the footer in the sticky-footer column */
}
.site-footer__copy {
  margin: 0;
  font-size: 12.5px;
  color: var(--ink-3);
  letter-spacing: 0.01em;
}
/* Footer copy is ordinary inline text, so the <sup> default raise works — just rein in its size. */
.site-footer__tm { font-size: 0.78em; }
.site-footer__links {
  margin-top: 14px;
  display: flex; justify-content: center; align-items: center;
  gap: 44px;
  flex-wrap: wrap;
}
.site-footer__link {
  font-size: var(--fs-sm); font-weight: 500;
  color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 120ms, border-color 120ms;
}
.site-footer__link:hover { color: var(--ink); border-color: var(--line-strong); }

/* On pages where a detail-overlay IS the whole screen (no list beneath it), let it flow in normal
   document flow so the page has real height and the footer sits below it rather than overlapping the
   absolutely-positioned content. */
.page-user .detail-overlay,
.page-profile .detail-overlay,
.page-register .detail-overlay {
  position: static;
  inset: auto;
}

/* Public player page: freeze the identity header (avatar / name / location / points / share) under the top
   bar while the superlatives + pun list scroll beneath it. Same masking as the other frozen headers — opaque
   page-bg + an opaque bottom gap (`padding-bottom`, not a transparent margin) + a hairline drawn just above
   it (`::after`) — so a scrolling pun's border/shadow never leaks into the frozen zone and the small gap
   below the line stays clean. (The detail-overlay is `position: static` here, so the page scroll drives it.) */
.page-user .detail-head {
  position: sticky;
  top: var(--topbar-h);
  z-index: var(--z-sticky);
  background: var(--bg);
  margin-bottom: 0;
  padding-bottom: 9px;
}
/* No divider hairline under the frozen identity header on the player page (intentionally line-less — the
   opaque mask + gap still separate it from the scrolling puns). The other frozen headers keep their `::after`. */

/* ===== Legal pages (Terms / Privacy) ===== */
.page-legal .detail-head { margin-bottom: 18px; }
.page-legal .detail-head > div { flex: 1; min-width: 0; }
.legal__updated {
  font-family: var(--mono-font); font-size: 11.5px;
  color: var(--ink-3); letter-spacing: 0.04em;
  text-transform: uppercase; margin: 2px 0 20px;
}
.legal__note {
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-line);
  border-radius: var(--radius-sm);
  padding: 12px 14px; margin: 0 0 18px;
  font-size: 14px; color: var(--ink-2); line-height: 1.55;
}
.legal__h2 {
  font-family: var(--display-font); font-weight: 500;
  font-size: 22px; letter-spacing: -0.015em;
  color: var(--ink); margin: 30px 0 8px; line-height: 1.15;
}
.legal__h3 {
  font-family: var(--ui-font); font-weight: 600;
  font-size: 15px; color: var(--ink);
  margin: 20px 0 6px;
}
.legal p { font-size: 14.5px; line-height: 1.62; color: var(--ink-2); margin: 0 0 12px; max-width: 70ch; }
.legal__list { margin: 0 0 16px; padding-left: 22px; display: grid; gap: 8px; max-width: 70ch; }
.legal__list li { font-size: 14.5px; line-height: 1.55; color: var(--ink-2); }
.legal__list li strong { color: var(--ink); font-weight: 600; }

/* ===== Follow / My Feed ===== */
/* The "+" Follow control, shown wherever a user is displayed (boards, profile header, search results). */
.follow-btn {
  appearance: none;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line-strong);
  background: var(--bg-elev);
  color: var(--ink-2);
  border-radius: 999px;
  padding: 4px 10px;
  font: 600 12px/1 var(--ui-font);
  cursor: pointer;
  transition: background 140ms, border-color 140ms, color 140ms;
}
.follow-btn:hover { border-color: var(--ink-3); color: var(--ink); }
.follow-btn svg { display: block; }
.follow-btn__label { white-space: nowrap; }
.follow-btn.is-following {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--ink);
}
/* Non-interactive inline copy of the "+" control, shown in the empty-feed CTA sentence. */
.follow-btn--demo { padding: 3px; vertical-align: middle; cursor: default; }
/* Icon-only "+" on the boards: a circle the SAME diameter as the avatar it sits beside, so the two read as
   a matched pair on the identity line. */
.leader-row__follow, .result-row__follow, .user-head__follow {
  padding: 0; border-radius: 50%; justify-content: center; flex-shrink: 0;
}
/* Both the Results author "+" and the Leaderboard "+" render the same small circle (the Leaderboard one
   was previously avatar-sized; matched DOWN to the Results size per request). */
.result-row__follow, .leader-row__follow { width: 20px; height: 20px; }
.result-row__follow svg, .leader-row__follow svg { width: 12px; height: 12px; }
/* On the profile header the username is large (22px), so its inline "+" is a touch bigger to balance it. */
.user-head__follow { width: 22px; height: 22px; }
.user-head__follow svg { width: 13px; height: 13px; }

.user-head__actions { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }

/* Following management (Account screen) + the followee/search rows it shares with elsewhere. */
.profile-following__hint { margin: 0 0 10px; font-size: var(--fs-sm); color: var(--ink-3); }
.profile-following__hint a { color: var(--accent); }
.profile-following__status { margin: 8px 0 0; }
.profile-following__current { margin-top: 14px; }
.follow-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 6px; }
.follow-list--search {
  margin-top: 8px; padding: 6px;
  background: var(--bg-sunk); border-radius: var(--radius);
}
.follow-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-radius: var(--radius-sm);
}
.follow-row__id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.follow-row__name { font-weight: 600; font-size: 14px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.follow-row__name--link { text-decoration: none; }
.follow-row__name--link:hover { text-decoration: underline; }
.follow-row__sub { font-size: var(--fs-xs); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.follow-remove {
  appearance: none; display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--line-strong); background: transparent; color: var(--ink-3);
  border-radius: 999px; padding: 4px 10px; font: 600 12px/1 var(--ui-font); cursor: pointer;
  transition: color 140ms, border-color 140ms;
}
.follow-remove:hover { color: var(--peach); border-color: var(--peach); }
.follow-remove svg { display: block; }

/* My Feed page: contest groups (a header per contest, then its pun boxes). */
.feed-group { margin-bottom: 22px; }
.feed-group__head {
  appearance: none; width: 100%; text-align: left; border: 0;
  display: block; padding: 6px 0 6px; cursor: pointer; margin-bottom: 0;
  /* Freeze each contest's prompt at the top while scrolling through its puns; the next section's prompt
     pushes it up. Opaque page-bg + an opaque bottom padding (the small gap below the dashed rule) mask the
     puns scrolling under it, so no border/shadow leaks into the frozen header. Same sticky layer as the
     other frozen headers. */
  position: sticky; top: var(--topbar-h); z-index: var(--z-sticky);
  background: var(--bg);
}
/* The dashed rule hugs the puns: drop the shared header's bottom margin so the only gap below the line is
   the head's small opaque padding-bottom — much narrower than the profile's, and clean (masked). */
.feed-group__head .user-contest__head { margin-bottom: 0; }
/* The feed's per-contest section header reuses the user-profile "By contest" header (.user-contest__head):
   prompt left, relative date right, hairline rule. The whole header is a button → that contest's results, so a
   subtle hover cue on the prompt signals it's clickable. */
.feed-group__head:hover .user-contest__prompt { text-decoration: underline; text-underline-offset: 2px; }
.feed-group .result-list { display: grid; gap: 8px; }
.result-row__meta--btn { cursor: pointer; }

/* "By contest" grouping on the public profile. */
.user-contest { margin-bottom: 18px; }
.user-contest__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 2px 2px 6px; border-bottom: 1px solid var(--line); margin-bottom: 8px;
}
.user-contest__prompt { font-family: var(--display-font); font-size: 15px; font-weight: 500; color: var(--ink); min-width: 0; }
.user-contest__date { font-size: var(--fs-xs); color: var(--ink-3); white-space: nowrap; flex-shrink: 0; }

/* ============================================================
   Design refresh 2026-06 — Phase 1 elevation pass
   Card surfaces that previously read flat (hairline border, no shadow) get a soft, theme-correct
   resting shadow so the UI reads as layered rather than outlined; the clickable ones lift on hover
   (pointer-only, so a tap doesn't strand the lift). Popover/menu/tooltip/modal elevation is unified
   on --shadow-pop. One block, source-order-wins, so it's trivial to tune or revert. (.choice and the
   prompt banner already carry their own elevation and are intentionally excluded; provisional + shared
   result rows keep their own transparent/ring treatment.)
   ============================================================ */
.result-row:not(.result-row--provisional):not(.is-shared),
.leader-row,
.user-pun,
.group-card,
.day-card,
.my-pun-card,
.profile-field,
.profile-hero,
.group-join-card,
.superlative,
.card {
  box-shadow: var(--shadow-card);
}
@media (hover: hover) {
  .result-row--btn:hover,
  .leader-row--btn:hover,
  .day-card:hover,
  .group-card--btn:hover,
  .user-pun:hover {
    box-shadow: var(--shadow-lift);
  }
}
.profile-pop,
.flag-pop,
.lb-menu,
.prompt-star-tip,
.avatar-modal__card {
  box-shadow: var(--shadow-pop);
}

/* ============================================================
   Mobile refinements (Phase 2) — narrow-phone fit + touch targets
   ============================================================ */
/* Prompt banner: the symmetric side gutter (reserved so the centred prompt clears the star bank) was a
   flat 84px even on a ~360px phone, squeezing long prompts into a thin centre channel. Shrink it on
   small screens — still clears the star cluster on the right, gives the text far more room. */
@media (max-width: 640px) { .prompt-banner { --prompt-banner-gutter: 52px; } }
@media (max-width: 400px) { .prompt-banner { --prompt-banner-gutter: 40px; } }

/* Touch targets: the small (26–28px) icon buttons get an invisible ~42px tap area on coarse pointers
   via a transparent ::after — the visible button is unchanged, so nothing shifts. On the vote card the flag is
   absolutely positioned and the share is `position: relative` (so each ::after anchors to its OWN button, NOT
   the full-width strip — otherwise the share's hit area would swallow the flag); the other in-flow ones become
   positioning contexts first. */
@media (pointer: coarse) {
  .result-flag-btn, .result-row__share, .group-card__share, .my-pun-card__icon-btn, .result-del,
  .detail-head__share, .user-head__share { position: relative; }
  .flag-btn::after, .choice__share::after, .result-flag-btn::after, .result-row__share::after,
  .group-card__share::after, .my-pun-card__icon-btn::after, .result-del::after,
  .detail-head__share::after, .user-head__share::after {
    content: ''; position: absolute; inset: -7px;
  }
}

/* Nav: squeeze the five/six tabs a touch more on the narrowest phones so they fit one line before the
   horizontal-scroll fallback engages. */
@media (max-width: 380px) {
  .nav { gap: 0; padding-left: 6px; padding-right: 6px; }
  .nav__item { padding: 6px 6px; font-size: 11.5px; }
}
