/* Pass the Button — see docs/design/DESIGN.md
 *
 * Dark is the app default: poker happens at night, at a table, on phones at low
 * brightness. Light is fully built, not a fallback.
 *
 * The colour rule that holds the system together: the four suits encode RSVP
 * state and nothing else, and gold NEVER encodes a state — it is the mark,
 * money, eyebrows and hairlines. Break that and gold lands on the ~72 people
 * who haven't replied, pointing the loudest colour on screen at the least
 * actionable thing on it.
 */

:root {
  --bg: #0b0b0a;
  --surface: #141413;
  --surface-2: #1c1c1a;
  --line: #292927;
  --line-strong: #363632;
  --ink: #f2f0eb;
  --muted: #918d86;

  --gold: #e8c67c;
  --club: #34c07b;      /* IN */
  --heart: #ff5f6b;     /* OUT */
  --diamond: #84b4ff;   /* MAYBE */
  --spade: #b0a698;     /* NO ANSWER */
  --on-accent: #06170e;

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .35);

  --f-display: ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Charter, Georgia, serif;
  --f-ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --f-data: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  color-scheme: dark;

  --radius: 12px;
  --topbar-h: calc(6.4rem + env(safe-area-inset-top));
  --tap: 44px; /* minimum comfortable touch target */
}

/* Dark is the default, so the light palette lives in overrides.
 *
 * Three states, not two: no attribute means "follow the OS", and an explicit
 * choice from the toggle must beat the OS in both directions. The :not() guard
 * is what stops a light OS from overriding a deliberate dark choice.
 */
/* HIDDEN 2026-08-12 — dark is the only theme users can reach.
 *
 * Commented out rather than deleted: the light palette below under
 * :root[data-theme="light"] is still maintained, and stamping that attribute
 * by hand in devtools is how to work on it. Restoring auto-switching means
 * uncommenting this block and the toggle in src/layout.js.
 */
/*
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #ffffff;
    --surface: #f6f5f2;
    --surface-2: #eceae5;
    --line: #e3e0da;
    --line-strong: #cfcbc3;
    --ink: #161513;
    --muted: #6c675f;

    --gold: #8a6b1e;
    --club: #1a6f45;
    --heart: #c9333d;
    --diamond: #2960bd;
    --spade: #7d7466;
    --on-accent: #ffffff;

    --shadow: 0 1px 2px rgba(20, 20, 30, .06), 0 12px 32px rgba(20, 20, 30, .07);
  }
}
*/

:root[data-theme="light"] {
    color-scheme: light;
    --bg: #ffffff;
    --surface: #f6f5f2;
    --surface-2: #eceae5;
    --line: #e3e0da;
    --line-strong: #cfcbc3;
    --ink: #161513;
    --muted: #6c675f;

    --gold: #8a6b1e;
    --club: #1a6f45;
    --heart: #c9333d;
    --diamond: #2960bd;
    --spade: #7d7466;
    --on-accent: #ffffff;

    --shadow: 0 1px 2px rgba(20, 20, 30, .06), 0 12px 32px rgba(20, 20, 30, .07);
  }

*, *::before, *::after { box-sizing: border-box; }

/* Rubber-band scrolling lifts the whole page — fixed elements included — and
   exposes whatever is beneath, which in a standalone web app reads as a dead
   strip under the tab bar. Killing the bounce is what the other apps on this
   box do, and it is the difference between this feeling like an app and a page.
   overscroll-behavior-x also stops a horizontal swipe triggering back/forward. */
html, body {
  overscroll-behavior: none;
}
html {
  /* The fix for the dead band under the tab bar in an installed app.
   *
   * viewport-fit=cover plus a black-translucent status bar expands the viewport
   * UPWARD, under the status bar — but the document's 100% height is still
   * measured against the pre-expansion viewport. The document therefore ends
   * short of the physical bottom by exactly safe-area-inset-top, and that
   * shortfall renders as a band beneath anything pinned to bottom: 0.
   *
   * It only shows on pages too short to overflow; longer ones scroll past it,
   * which is why Settings looked right while the empty Games/Players/Lists
   * screens did not. Adding the top inset back to the minimum height closes it.
   * See https://dev.to/karmasakshi/make-your-pwas-look-handsome-on-ios-1o08
   */
  min-height: calc(100% + env(safe-area-inset-top));
  overscroll-behavior-x: none;
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  /* Pinch/double-tap zoom off so this reads as an app rather than a web page.
     Matches the cmws setup. Worth knowing: Safari has ignored user-scalable=no
     in the browser since iOS 10, but honours it once the app is on the home
     screen — which is the target anyway. touch-action kills the 300ms
     double-tap-to-zoom delay in every engine. */
  touch-action: manipulation;
}

body {
  margin: 0;
  /* Clears the fixed header. */
  padding-top: var(--topbar-h);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--f-ui);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, figure, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

/* Long-press callout and link-drag make a web page feel like a web page. */
* { -webkit-touch-callout: none; }
a, button { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
a { -webkit-user-drag: none; }

:focus-visible {
  outline: 2px solid var(--diamond);
  outline-offset: 2px;
  border-radius: 4px;
}

button, input, select, textarea { font: inherit; color: inherit; }
/* Anything under 16px makes iOS zoom in on focus and never zoom back out. */
input, select, textarea { font-size: max(1rem, 16px); }

.num { font-family: var(--f-data); font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.is-club    { color: var(--club); }
.is-heart   { color: var(--heart); }
.is-diamond { color: var(--diamond); }
.is-spade   { color: var(--spade); }
.is-gold    { color: var(--gold); }

/* ------------------------------------------------------------- chrome */

.eyebrow {
  font-family: var(--f-data);
  font-size: .68rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--gold);
}

.wrap {
  width: 100%;
  max-width: 30rem;
  margin: 0 auto;
  padding: 1.25rem 1.15rem 4rem;
  padding-left: max(1.15rem, env(safe-area-inset-left));
  padding-right: max(1.15rem, env(safe-area-inset-right));
}
.wrap--wide { max-width: 68rem; }

.topbar {
  /* Fixed, not sticky. A sticky header sits in the document flow, so iOS
     rubber-band scrolling drags it along with the page. The bottom tab bar is
     fixed and stays put, and the header should behave the same way. */
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: env(safe-area-inset-top) calc(1rem + env(safe-area-inset-right)) 0 calc(1rem + env(safe-area-inset-left));
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.topbar .brand {
  display: flex;
  align-items: center;
  gap: .7rem;
  min-width: 0;          /* lets the wordmark shrink instead of pushing the actions off */
  text-decoration: none;
}
.topbar .brand svg { width: 64px; height: 64px; }
/* Clamped rather than fixed: at 64px of mark plus two 44px buttons there is not
   much width left on a 375px phone, so the wordmark gives way first. */
/* A stacked lockup rather than a line of running text: splitting it lets the
   type be far larger in the same width, and the size/tracking contrast between
   the two lines is what makes it read as designed instead of wrapped.
   The cap line takes the gold, so the lockup is two-tone and the brand colour
   appears twice in the header — the disc and the word. */
.topbar .wordmark {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.topbar .wordmark-a {
  font-family: var(--f-display);
  /* Sized so the tracked cap line optically matches the width of "Button"
     below it — a lockup reads as deliberate when the two lines align. */
  font-size: clamp(.9rem, 3.62vw, 1.1rem);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold);
  line-height: 1.35;
  /* Staggered indent, in em rather than px so it scales with the fluid type —
     a fixed offset tuned at 414px would drift out of proportion on a 320px
     phone where the wordmark is ~23% smaller. */
  margin-left: .34em;    /* ~5px at the XR size */
  margin-bottom: .2em;   /* ~3px — opens the stack without moving it down */
}
.topbar .wordmark-b {
  font-family: var(--f-display);
  font-size: clamp(1.9rem, 7.6vw, 2.7rem);
  letter-spacing: -.015em;
  line-height: .98;
  margin-left: .48em;   /* ~15px at the XR size */
}
.topbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .15rem;
  flex: none;
}
.topbar-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 52px;
  padding: .3rem .25rem;
  min-height: var(--tap);
  border: 0;
  background: none;
  border-radius: 10px;
  color: var(--muted);
  text-decoration: none;
  font-size: .62rem;
  letter-spacing: .01em;
  cursor: pointer;
}
.topbar-action:hover { color: var(--ink); background: var(--surface-2); }
.topbar-action:active { transform: scale(.93); }
.topbar-action svg { width: 21px; height: 21px; }

/* Testing aid: cycles auto -> light -> dark. Only one glyph shows at a time,
   picked by the state the script writes onto the button. */
[data-theme-toggle] > [data-when] { display: none; }
[data-theme-toggle][data-theme-state="light"] > [data-when="light"],
[data-theme-toggle][data-theme-state="dark"] > [data-when="dark"],
[data-theme-toggle][data-theme-state="auto"] > [data-when="auto"],
[data-theme-toggle]:not([data-theme-state]) > [data-when="auto"] { display: block; }
[data-theme-toggle][data-theme-state="light"],
[data-theme-toggle][data-theme-state="dark"] { color: var(--gold); }

/* Four text links across the top wrapped onto two lines on a phone. A fixed
   bottom bar fits any width and puts the targets in thumb reach. */
.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  background: var(--surface);
  border-top: 1px solid var(--line);
  /* In the browser this inset is 0 and the bar is compact. Installed to the
     home screen it becomes ~34px for the home indicator, which stacked on top
     of the tabs' own padding left the icons floating well clear of the bottom.
     Trim ~10px off the inset: still clear of the indicator, but the content
     sits where it does in the browser rather than riding high. */
  padding-bottom: max(0px, calc(env(safe-area-inset-bottom) - 8px));
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
.tabbar a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: calc(.2rem + min(8px, env(safe-area-inset-bottom))) .25rem .25rem;
  min-height: 45px;
  color: var(--muted);
  text-decoration: none;
  font-size: .68rem;
  letter-spacing: .01em;
  transition: transform .08s ease, color .08s ease;
}
.tabbar a[aria-current="page"] { color: var(--gold); }
.tabbar a:active { transform: scale(.93); }
.tab-icon { width: 23px; height: 23px; display: block; }

/* Clear the fixed bar — derived from its real height rather than guessed, so
   the blank strip under the last row of content matches the bar instead of
   overshooting it by ~40px once the home-screen inset is added in. */
:root { --tabbar-h: calc(46px + env(safe-area-inset-bottom)); }
body.has-tabs main { padding-bottom: calc(var(--tabbar-h) + 1rem); }

@media (prefers-reduced-motion: reduce) {
  .tabbar a { transition: none; }
  .tabbar a:active { transform: none; }
}
/* The way back. Scrolls with the page on purpose: back is a once-per-task
   action, the tab bar is always there as a coarse escape, and a second fixed
   band under the topbar would be the browser chrome this is meant to avoid.
   position: sticky here is a one-line change if long lists prove otherwise. */
/* Back on the left, this page's actions on the right, one line above the
   heading. Actions that used to sit at the foot of a long form are reachable
   without scrolling to find them — which is the whole reason this row exists. */
.pagebar {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: .55rem;
}
.pagebar .backto { margin-bottom: 0; margin-right: auto; }
.pagebar form { display: contents; }

/* One construct for all three, so nothing rides higher than anything else. */
.pagebar .backto,
.pagebar .barlink {
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
}

/* An action set as text, not as a button. A page bar full of filled buttons
   would outweigh the page under it, and these are confirmations of intent
   rather than the thing you came to look at. */
.barlink {
  background: none;
  border: 0;
  padding: 0;
  min-height: 0;
  font: inherit;
  font-size: .86rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
.barlink:hover { text-decoration: underline; }
.barlink.is-club  { color: var(--club); }
.barlink.is-heart { color: var(--heart); }

.backto {
  display: inline-block;
  margin-bottom: .55rem;
  font-size: .86rem;
  color: var(--muted);
  text-decoration: none;
}
.backto span { margin-right: .15rem; font-size: 1.05em; }
.backto:hover { color: var(--ink); }

.page-head {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  margin-bottom: 1.4rem;
}
/* The event heading is the hero of the RSVP page and wants room to breathe.
   Scoped, because .page-head carries every other heading in the app too. */
.page-head--event { gap: .8rem; }
.page-head--event h1 { white-space: nowrap; }

/* Date, table and money each on their own line rather than one wrapping run,
   where the breaks landed wherever the width happened to fall. */
.event-meta { display: flex; flex-direction: column; gap: .1rem; }

/* A separator, not a word. Muted and lighter so it reads as punctuation rather
   than competing with the date either side of it. The margins are em-based so
   the spacing tracks the heading as it scales. */
.page-head--event .h1-sep {
  color: var(--muted);
  font-weight: 400;
  margin: 0 .34em;
}

.page-head h1 {
  font-size: clamp(1.5rem, 5vw, 2rem);
  font-weight: 680;
  letter-spacing: -.026em;
  line-height: 1.12;
  text-wrap: balance;
}

/* ------------------------------------------------------------- cards */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.05rem;
}
.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--gap, 1rem); }
.row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.row--tight { gap: .4rem; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }

.meta { display: flex; flex-direction: column; gap: .4rem; }
.meta div { display: flex; justify-content: space-between; gap: 1rem; font-size: .9rem; }
.meta dt { color: var(--muted); }
.meta dd { font-family: var(--f-data); font-variant-numeric: tabular-nums; text-align: right; }
.meta .money { color: var(--gold); }

/* ------------------------------------------------------- game cards */

/* The index card. Same information the game page opens with, at a glance, so a
   host with one game a month does not land on a page that looks empty. */
.game-card {
  display: block;
  padding: .95rem 1rem 1.05rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
}
.game-card + .game-card { margin-top: .7rem; }
.game-card:hover { border-color: var(--line-strong); }

.game-card--live {
  background: color-mix(in srgb, var(--gold) 7%, var(--surface));
  border-color: color-mix(in srgb, var(--gold) 28%, var(--line));
  border-left: 3px solid var(--gold);
  /* keeps the text on the same left edge as every other card */
  padding-left: calc(1rem - 2px);
}
/* The seat meter's track is --surface-2, which against this wash is 1.05:1 —
   invisible. Stepped up so the unfilled seats still read. */
.game-card--live .bar { background: var(--line-strong); }

.game-card-head { display: flex; align-items: flex-start; gap: 1rem; }
.game-card-head .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.game-title {
  font-size: 1.15rem;
  font-weight: 620;
  letter-spacing: -.015em;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

/* Seats read as a figure, not a sentence — the one number a host checks first. */
.game-seats { flex: none; text-align: right; }
.game-seats .n {
  display: block;
  font-family: var(--f-data);
  font-variant-numeric: tabular-nums;
  font-size: 1.3rem;
  letter-spacing: -.02em;
  line-height: 1.1;
}
.game-seats .l { display: block; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }

.game-when { margin: .5rem 0 .7rem; font-size: .84rem; color: var(--muted); }
.game-where { display: block; }

/* The four suits, each with its count. Decoration that happens to be the
   fastest read on the card: which way the room is leaning, without opening it.
   A zero stays in place so the four positions never shift between cards, but
   recedes so only the live numbers carry weight. */
.game-tally {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  margin-top: .85rem;
}
.game-tally-item {
  display: inline-flex;
  align-items: baseline;
  gap: .35rem;
  font-size: .85rem;
  white-space: nowrap;
}
.game-tally-label { color: var(--muted); }
.game-tally-item .num { font-variant-numeric: tabular-nums; }
/* No dimming for a zero. Fading the whole item dropped its label from 5.6:1 to
   2.1:1, which read as one word being a different colour rather than as a count
   being quiet — and a zero is worth reading. */

/* ------------------------------------------------------------- seats */

.seatline { display: flex; align-items: baseline; gap: .45rem; }
.seatnum {
  font-family: var(--f-data);
  font-size: 2.6rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.045em;
  line-height: .95;
}
.seatnum--in { color: var(--club); }
.seatof { font-family: var(--f-data); font-size: 1rem; color: var(--muted); }
.seatlab {
  margin-left: auto;
  align-self: center;
  text-align: right;
  font-size: 1.05rem;
  color: var(--muted);
  line-height: 1.3;
}

/* The confirmation. A card, like everything else that asks something. */
dialog.confirm {
  max-width: 22rem;
  width: calc(100% - 2.5rem);
  padding: 1.15rem 1.2rem 1.1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}
dialog.confirm::backdrop { background: rgba(0, 0, 0, .55); }
.confirm-message { margin: 0 0 1.1rem; font-size: .96rem; line-height: 1.45; }
/* Cancel first, and the destructive one last where the thumb is least likely to
   land by accident. */
.confirm-actions { display: flex; justify-content: flex-end; gap: .6rem; }

/* ------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  min-height: var(--tap);
  padding: .6rem 1rem;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font-size: .95rem;
  font-weight: 550;
  text-decoration: none;
  cursor: pointer;
}
.btn:hover { border-color: var(--muted); }
.btn--primary {
  background: color-mix(in srgb, var(--club) 65%, #000);
  border-color: color-mix(in srgb, var(--club) 65%, #000);
  color: #fff;
  font-weight: 650;
}
.btn--ghost { background: none; }
.btn--small { min-height: 34px; padding: .3rem .6rem; font-size: .82rem; border-radius: 8px; }
.btn--block { width: 100%; }
.btn--danger { color: var(--heart); border-color: var(--heart); background: none; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }

/* A button that reads as a link. For the handful of places where the action
   has to be a POST — resending a code makes a phone buzz, so it must not sit
   behind a URL that a scanner or link preview can fetch — but should carry no
   more visual weight than the "Different number" link beside it. */
.linklike {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The three RSVP states. Unanswered they sit at equal weight — nothing is
 * pre-selected, so there is never a "wait, did I already reply?" moment. */
.states { display: flex; flex-direction: column; gap: .5rem; }
.state-btn {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  min-height: 52px;
  padding: .75rem .9rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 1rem;
  font-weight: 550;
  text-align: left;
  cursor: pointer;
}
.state-btn .glyph { font-size: 1.1rem; line-height: 1; }
.state-btn .tapnote {
  margin-left: auto;
  font-family: var(--f-data);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
}
.state-btn[aria-pressed="true"] { color: var(--on-accent); font-weight: 650; }
.state-btn[aria-pressed="true"] .tapnote { color: var(--on-accent); }
.state-btn[aria-pressed="true"][data-state="in"]    { background: var(--club);    border-color: var(--club); }
.state-btn[aria-pressed="true"][data-state="out"]   { background: var(--heart);   border-color: var(--heart); }
.state-btn[aria-pressed="true"][data-state="maybe"] { background: var(--diamond); border-color: var(--diamond); }

/* ------------------------------------------------------------- forms */

.field { display: flex; flex-direction: column; gap: .3rem; }
.field > label {
  font-family: var(--f-data);
  font-size: .62rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--muted);
}
.field input, .field select, .field textarea {
  min-height: var(--tap);
  padding: .6rem .75rem;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  width: 100%;
}
.field textarea { min-height: 8rem; resize: vertical; font-family: var(--f-data); font-size: 1rem; }

.field input::placeholder,
.field textarea::placeholder {
  color: var(--muted);
  opacity: .72;
}

/* Date and time inputs are native controls that size themselves to their
   content, so they sat narrower than every other field. Suppressing the
   platform appearance lets width:100% apply; the ::-webkit- rules pull the
   value back to the left edge and push the picker icon out to the right, which
   is where iOS otherwise centres and crowds them. */
.field input[type="datetime-local"],
.field input[type="date"],
.field input[type="time"] {
  appearance: none;
  -webkit-appearance: none;
  min-width: 0;
  font-family: var(--f-data);
  text-align: left;
}
.field input[type="datetime-local"]::-webkit-date-and-time-value,
.field input[type="date"]::-webkit-date-and-time-value,
.field input[type="time"]::-webkit-date-and-time-value {
  text-align: left;
  margin: 0;
}
.field input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  margin-left: auto;
  opacity: .7;
  cursor: pointer;
}
.field input[inputmode="tel"] { font-family: var(--f-data); font-size: 1.15rem; letter-spacing: .02em; }
.field .help { font-size: .8rem; color: var(--muted); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
@media (max-width: 30rem) { .grid-2 { grid-template-columns: 1fr; } }

.notice {
  padding: .7rem .85rem;
  border-radius: 10px;
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  font-size: .9rem;
}
.notice--bad { border-color: var(--heart); color: var(--heart); }
.notice--good { border-color: var(--club); color: var(--club); }

/* ------------------------------------------------------------- roster */

.bar {
  display: flex;
  height: 9px;
  border-radius: 5px;
  overflow: hidden;
  gap: 2px;
  background: var(--surface-2);
}
.bar span { display: block; min-width: 0; }
.bar .b-in   { background: var(--club); }
.bar .b-wait { background: color-mix(in srgb, var(--heart) 62%, #fff); }

/* A wrapping grid of chips rather than a scrolling strip. Five tabs with a
   glyph, a label and a count cannot fit across a phone, and a scroll strip
   hides whatever is off the right edge. Three per row with the last row growing
   to fill means five reads as 3 + 2 and six as 3 + 3, with nothing hidden. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 1.25rem 0 1rem;
}
.tabs a {
  flex: 1 1 27%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 46px;
  padding: .4rem .5rem;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  text-decoration: none;
  font-size: .85rem;
  line-height: 1.15;
  text-align: center;
}
.tabs a .t-label { white-space: nowrap; }
.tabs a[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 8%, var(--surface));
}
.tabs .n {
  font-family: var(--f-data);
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  padding: .05rem .4rem;
  border-radius: 999px;
  background: var(--surface-2);
}
.tabs a[aria-current="page"] .n { background: var(--gold); color: var(--bg); }

/* Long labels shed the glyph before they wrap. */
@media (max-width: 22rem) {
  .tabs a { font-size: .8rem; padding: .35rem .35rem; }
}

.headnums { display: flex; gap: 1.6rem; flex-wrap: wrap; }
.headnum { display: flex; flex-direction: column; gap: .1rem; }
.headnum .n {
  font-family: var(--f-data);
  font-size: 1.7rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.04em;
  line-height: 1;
}
.headnum .l {
  font-family: var(--f-data);
  font-size: .6rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--muted);
}

.table-scroll { overflow-x: auto; }
table.roster { width: 100%; border-collapse: collapse; }
table.roster th {
  text-align: left;
  padding: .45rem .55rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--f-data);
  font-size: .6rem;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
table.roster td {
  padding: .55rem .55rem;
  border-bottom: 1px solid var(--line);
  font-size: .9rem;
  vertical-align: middle;
}
table.roster tr:last-child td { border-bottom: none; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .12rem .5rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-family: var(--f-data);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.empty {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
}
.empty--art {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  padding: 2.25rem 1.25rem 2.4rem;
}
/* Slightly held back so the artwork sets a mood rather than shouting; it comes
   up to full strength on hover/focus within, which makes an empty screen feel
   alive rather than broken. */
.empty--art .art { opacity: .92; margin-bottom: .9rem; }
.empty-title { color: var(--ink); font-size: 1.05rem; font-weight: 600; letter-spacing: -.012em; }
.empty-body { max-width: 26rem; font-size: .92rem; }
.empty-body a { color: var(--gold); }


.list-item {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .7rem .9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
}
.list-item + .list-item { margin-top: .5rem; }
/* Column, not row: the name and the number below it are two lines, and without
   a direction they sit side by side and run together on a narrow screen. */
.list-item .grow {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.list-item .sub { font-size: .82rem; color: var(--muted); }

/* A count that is also the way through to what it counts. Muted like the text
   it replaces rather than styled as a link — the row already has two buttons
   and a third loud thing would make the list name the quietest part of its own
   row. Sized to its text and no wider, for the same reason .li-facts links are:
   a full-width tap target in a list you scroll with a thumb is a mis-tap. */
.list-item a.sub { width: fit-content; max-width: 100%; text-decoration: none; }
.list-item a.sub:hover { text-decoration: underline; }

/* ------------------------------------------- responsive person rows

   One line when the row is wide enough to hold one, stacked when it is not.

   The query is on the ROW, not the viewport. These rows appear both on a
   full-width page and inside narrow cards, and a viewport query cannot tell
   those apart — it would stack a row that had plenty of room, or crush one
   that had none. Asking the row about its own width is the only thing that
   answers correctly in both places.

   Browsers without container query support never match the block below and
   keep the stacked layout, which is exactly what this app did before. */
.list-item { container-type: inline-size; }

.li-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.li-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A name carries its own answer: green in, red out, blue maybe, ordinary ink
   for anyone yet to say. No separate label to align, and the row reads in one
   glance instead of two. */
.li-name.is-club { color: var(--club); }
.li-name.is-heart { color: var(--heart); }
.li-name.is-diamond { color: var(--diamond); }
.li-facts { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* Truncate rather than wrap: a wrapped email turns one row into three and the
   column of names stops scanning as a column. */
.li-facts .sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@container (min-width: 40rem) {
  .li-body {
    display: grid;
    grid-template-columns: minmax(0, 13rem) minmax(0, 10rem) minmax(0, 1fr);
    align-items: baseline;
    column-gap: 1.1rem;
  }
  .li-facts { display: contents; }
  /* Empty cells hold their column open so later ones do not slide left. */
  .li-facts .sub:empty { display: block; }
}

@container (min-width: 58rem) {
  .li-body { grid-template-columns: minmax(0, 14rem) minmax(0, 10rem) minmax(0, 18rem) minmax(0, 1fr); }
}

/* Stacked, an empty cell would be a blank line. */
.li-facts .sub:empty { display: none; }

/* Contactable facts. Underlined only on hover so a dense list stays calm, but
   the pointer and focus ring make it clear they do something. */
.li-facts a.sub { color: var(--muted); text-decoration: none; cursor: pointer; }
.li-facts a.sub:hover { text-decoration: underline; }

/* Interactive parts of a row are as wide as their text and no wider. Left to
   stretch they fill the whole column, and in a list you scroll with your thumb
   that is a mis-tap waiting to happen — one that opens Mail or Messages and
   takes you out of the app. */
.li-facts a.sub,
label.li-name { width: fit-content; max-width: 100%; }

/* Marked-optional facts drop out when the row is genuinely cramped. Rare by
   design: a row on a phone is roughly 22rem, so a handset stacks and keeps
   everything. This is for narrow cards, not for phones. */
@container (max-width: 20rem) {
  .li-opt { display: none; }
}

footer.foot {
  margin-top: 3rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  font-size: .78rem;
  color: var(--muted);
}


/* List membership rows — the whole row is the tap target, not just the box. */
.member-row {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .6rem .55rem;
  border-radius: 8px;
}
label.member-row,
.member-row label,
.member-row input[type="checkbox"] { cursor: pointer; }
.member-row + .member-row { border-top: 1px solid var(--line); }

/* Remove, sitting in the row's top-right corner where a dismiss belongs, rather
   than floating beside the middle of a three-line row. Borderless: a bordered
   button repeated down a list reads as the point of the screen, and the point of
   this screen is the names. */
.member-row form { flex: none; margin: 0; align-self: flex-start; }
/* The roster's rows are both a selection and a way in: the box picks, the
   button at the end opens. Held at its own width so the names keep the room. */
.member-row > .btn { flex: none; }
/* A short note at the right edge of a row — what an action would do, beside the
   thing it would do it to. Sibling of .li-body rather than inside .li-facts, so
   it right-aligns instead of trailing the facts; and its own class, because in
   there `.member-row .sub` outranks a bare .is-club and paints it muted. */
.row-note {
  flex: none;
  align-self: center;
  font-size: .82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.row-note.is-club { color: var(--club); font-weight: 600; }

.row-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  /* pulls it into the corner without shrinking the tap target */
  margin: -.35rem -.35rem 0 0;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.row-remove:hover,
.row-remove:focus-visible {
  color: var(--heart);
  background: color-mix(in srgb, var(--heart) 12%, transparent);
}

/* The action row above a long list, so nothing depends on scrolling to the end
   of several hundred players to act on them. */
.actionbar {
  display: flex;
  align-items: center;
  gap: .8rem;
  margin-bottom: .5rem;
}
.backlink {
  flex: none;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--muted);
  text-decoration: none;
  padding: .35rem .1rem;
}
.backlink:hover { color: var(--ink); }

/* Select all is a control, not a destination, so it is set as text rather than
   as a second button competing with the one that actually does something. Gold
   because it is neither an RSVP state nor a suit — the same reason a selected
   member row is gold. Sized with the addresses beside it. */
.toggle-link {
  flex: 1;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  min-height: 0;
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  color: var(--gold);
  cursor: pointer;
}
.toggle-link:hover { text-decoration: underline; }

/* An eyebrow link that should read as text, not as a link. */
a.plain { text-decoration: none; }
a.plain:hover { text-decoration: underline; }
.member-row input[type="checkbox"] {
  width: 22px;
  height: 22px;
  flex: none;
  accent-color: var(--gold);
}
.member-row .grow { display: flex; flex-direction: column; min-width: 0; }
/* Same responsive row as the lists elsewhere — see .li-body above. */
.member-row { container-type: inline-size; }
.member-row .sub { font-size: .8rem; color: var(--muted); }
/* Selected rows get a gold rail and the faintest wash, not a filled block.
 *
 * A solid --surface-2 fill turned a long list into stripes and made the names
 * harder to scan, which is the actual job of this screen. Gold rather than a
 * suit colour on purpose: this is a selection, not an RSVP state, and the four
 * suits are reserved for answers. Same reason the tab bar's active item is gold.
 */
.member-row {
  border-left: 3px solid transparent;
  padding-left: calc(.55rem - 3px);
}
.member-row:has(input:checked) {
  border-left-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 7%, transparent);
}

/* Search and its button share a row; the list filter sits on its own line
   beneath, where it has the width to show a long list name. */
.filters { margin-bottom: 1rem; width: 100%; }
.filters-row {
  display: flex;
  width: 100%;
  gap: .5rem;
  margin-bottom: .5rem;
}
/* width:100% as well as flex-grow — an <input> carries an intrinsic size from
   its `size` attribute, which can otherwise stop it filling the row. */
.filters-row input[name="q"] { flex: 1 1 auto; width: 100%; min-width: 0; }
.filters-row .btn { flex: 0 0 auto; }
.filters input,
.filters select {
  min-height: var(--tap);
  padding: .6rem .75rem;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
}

/* A native select on a dark ground gives almost no cue that it opens, so the
   arrow is drawn explicitly and the native one suppressed. */
/* The select and the arrow share one grid cell rather than the arrow being
   absolutely positioned over it. A positioned pseudo-element can lose the paint
   order against a native form control; a later grid item in the same cell
   cannot. Keeps var(--muted) so it follows the theme. */
.select-wrap {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  width: 100%;
}
.select-wrap select {
  grid-area: 1 / 1;
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.75rem;
}
.select-wrap::after {
  content: "";
  grid-area: 1 / 1;
  justify-self: end;
  margin-right: 1.1rem;
  width: 10px;
  height: 10px;
  transform: translateY(-3px) rotate(45deg);
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  pointer-events: none;
}

/* Checkbox rows inside a form field — same shape as the membership screen so
   the two read as one pattern. */
/* Capped at five rows (~49px each) so the Save button stays reachable. Below
   five lists nothing changes; past it the box scrolls instead of pushing the
   form off the screen — twelve lists would otherwise be taller than a phone.
   overscroll-behavior stops a flick at the end of the box carrying on into the
   page, which is the usual annoyance with a scroll area inside a form. */
.check-rows {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  padding: .2rem .35rem;
  max-height: calc(5 * 49px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* A row ending flush at the edge reads as the end of the list. Fading the last
   few pixels says there is more without stealing a row's worth of height. */
.check-rows {
  --fade: 14px;
  mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - var(--fade)), transparent 100%);
}
.check-rows:not(:hover) { scrollbar-width: thin; }

.btn--lg {
  min-height: 54px;
  padding: .85rem 2.2rem;
  font-size: 1.05rem;
  font-weight: 650;
  border-radius: 12px;
}

/* Seat position, in place of the avatar on the In and Waitlist rows. */
/* A row holding a form needs the form to stay its natural width rather than
   being stretched by the flex container. */
.list-item form { flex: none; margin: 0; }

/* Row actions stack. Side by side they crowded the facts beside them, and on a
   narrow row the second one was the first thing to get squeezed. */
.mark-actions {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .35rem;
}
.mark-actions form { margin: 0; }
.mark-actions .btn { width: 100%; }


/* The message exactly as it will arrive. Without pre-wrap the browser folds the
   blank lines away and the preview disagrees with what the Copy button carries. */
.msg-preview {
  margin-top: .4rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: .86rem;
  line-height: 1.5;
}

/* ------------------------------------------------- the question */

/* Sits one step below the page h1 on the same scale, so it reads as the second
   most important thing on the page — which is exactly what it is. */
.ask {
  margin: 1.15rem 0 -.1rem;
  color: var(--gold);
  font-size: clamp(1.15rem, 4.2vw, 1.4rem);
  font-weight: 640;
  letter-spacing: -.018em;
  line-height: 1.2;
  text-wrap: balance;
}

/* The already-answered counterpart: deliberately quiet. */
.said {
  margin: 1.1rem 0 -.15rem;
  font-size: .92rem;
}
