/* base.css — the design system every activity imports. Tokens, type, buttons, cards, pills, code tiles,
   modals/sheets/toasts, HUD, reactions, cursors, the "AI is thinking" orb, and the shared scene layouts
   (START / ROOM / PROFILE / PACK / PLAY / REVEAL / RESULT). Two registers: cool paper (default) and warm cream (.warm). */

@import url('/fonts/fonts.css');

:root {
  --paper: #F8F9FB;
  --paper-raised: #FFFFFF;
  --ink: #17181C;
  /* What sits ON an ink-filled surface. Must flip with the theme: in dark, --ink is near-white,
     so a hardcoded white label on it would be invisible. */
  --on-ink: #FFFFFF;
  --on-red: #FFFFFF;   /* a label on a solid --red fill; --red lifts in dark, so this darkens */
  /* What sits ON a tinted surface. Same reasoning as --on-ink: the tint flips with the theme, so a
     literal dark hue that reads well on a pale tint reads dark-on-dark once the tint is dark. */
  --on-pink-tint: #C2255C;
  --on-blue-tint: #1E5FBF;
  --on-yellow-tint: #7A5A00;
  --on-green-tint: #146B47;
  /* Red was the one tint with no partner, even though --red-tint is used as a text surface in five
     places (the danger button, the wrong-answer verdicts, the camera error). --red on --red-tint is
     3.20:1; this is 5.34. */
  --on-red-tint: #B3252A;
  /* Label text is 11-12px uppercase, which WCAG AA asks 4.5:1 of. --pink and --blue are tuned to
     read as *identity* on a big surface and only reach ~2.3:1 as small type, so small text uses
     the darker member of the same family. Same hue, same meaning, actually readable. */
  --pink-label: #C2255C;
  --blue-label: #1E5FBF;
  /* What sits ON a solid --pink or --blue fill (a name chip, a tag, the tick on a finished node).
     Unlike --on-ink these do NOT flip: the identity fills are pale in both themes, so a white label
     is ~2.4:1 on them in light AND in dark. A near-black from the same family is 7.5:1 in both. */
  --on-pink: #2A0A14;
  --on-blue: #06152B;
  /* What sits ON the pink→blue gradient (`--grad`), which is the two of them and never changes.
     A gradient has no single colour behind a label, so the label must survive its WORST end: white
     was 2.44:1 on the pink and 2.08:1 in dark, on the product's primary button, in seventeen
     places. This is 7.5:1 on the pink end and 6.9:1 on the blue, and it does not flip — both ends
     of the gradient stay pale in both themes. */
  --on-grad: #2A0A14;
  /* ---- the viewer's own colour (docs/R5-INTERFACE.md S3) ----
     Pink means one person and blue the other EVERYWHERE else in the product; that never changes.
     `--me` is the one exception and it is not about identity at all — it is the chrome the person
     holding the phone is touching. Until R5 the lit tab was always `--pink-label`, so on Sunny's
     phone the whole shell wore Evan's colour. Whichever page knows who this is stamps
     `data-me="pink|blue"` on <body> (client/scenes/appShell.js `markMe()`), and these four follow.
     Aliases, not new hues: they are `var(--pink)` & co., so they re-resolve in every theme block
     below with no restatement needed there. Pink is the default and the fallback — never absent. */
  --me: var(--pink);
  --me-label: var(--pink-label);
  --me-tint: var(--pink-tint);
  --on-me-tint: var(--on-pink-tint);
  /* Lavender: the fourth well colour (.iwell--lav, S6). It had a tint nowhere and a readable
     label member nowhere, and the first draft of the wells hard-coded #2E6B45 on it — which is
     unreadable at night, and was caught in review. Both members, both themes, like every pair. */
  --lav: #A78BFA;
  --lav-tint: #EDE7FE;
  --on-lav-tint: #5B3FBF;
  --ink-soft: #5B5E68;
  --ink-faint: #6E727D;  /* 2.71:1 on white was well under AA for 11px labels; this is 4.81 */
  --line: #E3E5EA;
  /* The second hairline. --line is the quiet divider inside a card; --line-strong is the edge of a
     control a finger is meant to find — the ghost button, an input, a dashed drop zone (§05).
     Neither ever carries text: they are 1.20:1 and 1.38:1 on --paper, which is a visible edge and
     nothing more. Text on either would be a bug. */
  --line-strong: #D2D6DE;
  --pink: #FF7BA3;
  --pink-tint: #FFE0EA;
  --blue: #5FA0FF;
  --blue-tint: #DCEBFF;
  --green: #22A06B;
  --green-tint: #E3F7EC;
  --yellow: #F5D66B;
  --yellow-tint: #FFF3C4;
  --red: #E5484D;
  --red-tint: #FDE2E4;
  --grad: linear-gradient(90deg, var(--pink), var(--blue));
  --grad-soft: linear-gradient(135deg, #FFE0EA 0%, #F3E8F5 50%, #DCEBFF 100%);
  /* The "one of everything" pack card (.pack-card--wide) — pink, lilac, mint, because it is
     every pack at once. It was three literal pastels, so at night it stayed a pale ribbon with
     near-white text on it (1.1:1) in quiz, hunt and fashion. Same three hues, lamplit. */
  --grad-mix: linear-gradient(90deg, #FFE7EE, #EEE8FF 45%, #E2F5EA);
  --r0: 0; --r4: 4px; --r8: 8px; --r16: 16px;
  /* The shell's radius scale (STYLE-GUIDE §05). Three sizes, and they mean something: a card, a
     control, a pill. The --r0..--r16 above are the games' free-form set and stay as they are — a
     game may round anything it likes; the room a couple lives in may not. */
  --r-card: 18px;
  --r-ctrl: 14px;
  --r-pill: 999px;
  --shadow-1: 0 1px 2px rgba(23, 24, 28, .04), 0 4px 16px rgba(23, 24, 28, .05);
  --shadow-2: 0 8px 32px rgba(23, 24, 28, .10);
  /* The shell's ONE card shadow. --shadow-1/--shadow-2 stay the games' generic pair; every raised
     surface in the shell uses this, so the whole room lifts off the page by the same amount. */
  --shadow-card: 0 1px 2px rgba(23, 24, 28, .04), 0 8px 28px rgba(23, 24, 28, .06);
  --font: 'Pretendard Variable', Pretendard, -apple-system, system-ui, 'Segoe UI', sans-serif;
  --mono: 'Space Mono', ui-monospace, Menlo, Consolas, monospace;
  --serif: 'Gowun Batang', Georgia, serif;
  --pixel: 'Press Start 2P', monospace;
  --col: 640px;      /* game column */
  --col-wide: 1100px;
  --tap: 44px;
  /* ---- the activity bar's tokens (docs/R2-INTERFACE.md S5) ----
     Declared HERE, on :root, and nowhere else. A custom property is substituted at the element
     that declares it, so these hold the SHELL's palette even on a page whose <body> repaints
     --paper/--ink/--line for its own register — which photobooth does, per style, seven times
     (photobooth/ui/photobooth.css:7-13). STYLE-GUIDE §04 is the reason: a game owns its face and
     may not own "the shell it is launched from and returns to", and the one strip that is always
     the way out is exactly the thing that must not change colour when the game does. They still
     follow the system scheme, because the dark blocks below redefine --paper-raised & co. on this
     same element and substitution re-runs there. */
  --bar-h: 44px;      /* the strip's height, and the room every scene must leave for it */
  --bar-ground: var(--paper-raised);
  --bar-ink: var(--ink);
  --bar-faint: var(--ink-faint);
  --bar-line: var(--line);
  --bar-edge: var(--line-strong);
  --bar-pink: var(--pink);
  --bar-blue: var(--blue);
  --bar-pink-label: var(--pink-label);
  --bar-blue-label: var(--blue-label);
}
.warm {
  --paper: #FBF6EF;
  --paper-raised: #FFFDF9;
  --line: #EADFD2;
  --line-strong: #DBCCBA;   /* 1.46:1 on this register's paper — an edge, not text */
  --ink-soft: #6B5F55;
  /* The cool faint above was measured against white (4.81:1); on this paper it lands at 4.47:1,
     under AA — the first R2 contrast run found it on nine labels in the Hub. The same grey walked
     warm and one notch darker: 5.31 on the paper, 5.62 on a card, still a step lighter than
     --ink-soft (5.76), so the hierarchy every warm page is drawn with survives. */
  --ink-faint: #726458;
  --accent-copper: #B4633A;
  /* The copper's readable member, the way --pink has --pink-label: the accent itself is 4.32:1 on
     a card and 3.57 on a pink tint, and the warm pages spend it on 10px mono kickers where AA asks
     4.5. This is 5.54 on the paper, 5.87 on a card, 4.85 and 4.93 on the two premium tints. */
  --accent-copper-label: #92542A;
}

/* ---------------------------------------------------------------------------------------------
   Dark register.
   This was missing, and its absence was visible: the landing page and the app home both answered
   the system theme while every game and /activity stayed bright white, so the product changed
   appearance as you walked through it. Everything below redefines TOKENS ONLY — no component rule
   lives in here — so a game written against `var(--paper)` gets a correct dark treatment without
   knowing dark exists.

   Two rules that keep it honest:
   · Pink and blue are the two people, so they lift rather than dull — they must stay legible as
     identity against a dark ground, not fade into it.
   · The ground is a warm near-black, not #000: a pure black void makes the two colours scream and
     is unpleasant to hold at night, which is when a long-distance couple actually opens this.
   Guarded with :not([data-theme="light"]) so an explicit light choice still wins.
--------------------------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #1B1A20;
    /* Light enough to read on their own dark tints — the same job the dark hues do on pale ones. */
    --on-pink-tint: #FFC2D6;
    --on-blue-tint: #B9D6FF;
    --on-yellow-tint: #F2DFA4;
    --on-green-tint: #A8E6C6;
    --on-red-tint: #FFB3B6;
    /* On a dark ground the readable member of the family is the lighter one. */
    --pink-label: #FFA9C4;
    --blue-label: #9AC5FF;
    /* Restated so the viewer's chrome colour is declared in every palette block like every other
       token (docs/R5-INTERFACE.md S3). They are aliases, so the values are identical in all three
       — what matters is that nothing reads --me from a block that never mentions it. */
    --me: var(--pink);
    --me-label: var(--pink-label);
    --me-tint: var(--pink-tint);
    --on-me-tint: var(--on-pink-tint);
    --paper-raised: #26242D;
    --ink: #F6F2EC;
    --on-ink: #1B1A20;
    --on-red: #1B1A20;
    --ink-soft: #B4AFB8;
    --ink-faint: #9B96A1;  /* 4.31:1 on a raised dark card was under AA; this is 5.3 */
    --line: #383540;
    --line-strong: #4A4653;   /* 1.89:1 on --paper — the same visible edge, lamplit */
    --pink: #FF93B4;
    --pink-tint: #3B2029;
    --blue: #7FB4FF;
    --blue-tint: #17253C;
    --green: #4CC38A;
    --green-tint: #12301F;
    --lav: #BFA8FF;
    --lav-tint: #2A2338;
    --on-lav-tint: #D6C9FF;
    --yellow: #F7DE8B;
    --yellow-tint: #33290C;
    --red: #F2666B;
    --red-tint: #3A1B1E;
    --grad-soft: linear-gradient(135deg, #33212A 0%, #2A2333 50%, #1E2A3C 100%);
    --grad-mix: linear-gradient(90deg, #35222A, #262238 45%, #17301F);
    --shadow-1: 0 1px 2px rgba(0, 0, 0, .34), 0 4px 16px rgba(0, 0, 0, .30);
    --shadow-2: 0 8px 32px rgba(0, 0, 0, .46);
    --shadow-card: 0 1px 2px rgba(0, 0, 0, .40), 0 8px 28px rgba(0, 0, 0, .35);
  }
  :root:not([data-theme="light"]) .warm {
    --paper: #211D18;
    --paper-raised: #2C2721;
    --line: #423A31;
    --line-strong: #55493D;
    --ink-soft: #BCB0A3;
    /* Declared here because the light .warm block sets --ink-faint and a class on the body beats
       the root's dark value — without this line a dark warm page reads 2.59:1. 6.43 on the paper,
       5.68 on a card, a step below --ink-soft (6.96) so the hierarchy holds at night too. */
    --ink-faint: #A89F95;
    --accent-copper: #E08A5B;
    --accent-copper-label: #E08A5B;   /* 6.96:1 and up on the dark ground: the accent reads at label size as it is */
  }
}
/* An explicit choice beats the OS in both directions. */
:root[data-theme="dark"] {
  --paper: #1B1A20;
  /* Light enough to read on their own dark tints — the same job the dark hues do on pale ones. */
  --on-pink-tint: #FFC2D6;
  --on-blue-tint: #B9D6FF;
  --on-yellow-tint: #F2DFA4;
  --on-green-tint: #A8E6C6;
  --on-red-tint: #FFB3B6;
  /* On a dark ground the readable member of the family is the lighter one. */
  --pink-label: #FFA9C4;
  --blue-label: #9AC5FF;
    /* Restated so the viewer's chrome colour is declared in every palette block like every other
     token (docs/R5-INTERFACE.md S3). They are aliases, so the values are identical in all three
     — what matters is that nothing reads --me from a block that never mentions it. */
  --me: var(--pink);
  --me-label: var(--pink-label);
  --me-tint: var(--pink-tint);
  --on-me-tint: var(--on-pink-tint);
  --paper-raised: #26242D;
  --ink: #F6F2EC;
  --on-ink: #1B1A20;
  --on-red: #1B1A20;
  --ink-soft: #B4AFB8;
  --ink-faint: #9B96A1;  /* 4.31:1 on a raised dark card was under AA; this is 5.3 */
  --line: #383540;
  --line-strong: #4A4653;   /* 1.89:1 on --paper — the same visible edge, lamplit */
  --pink: #FF93B4;
  --pink-tint: #3B2029;
  --blue: #7FB4FF;
  --blue-tint: #17253C;
  --green: #4CC38A;
  --green-tint: #12301F;
  --lav: #BFA8FF;
  --lav-tint: #2A2338;
  --on-lav-tint: #D6C9FF;
  --yellow: #F7DE8B;
  --yellow-tint: #33290C;
  --red: #F2666B;
  --red-tint: #3A1B1E;
  --grad-soft: linear-gradient(135deg, #33212A 0%, #2A2333 50%, #1E2A3C 100%);
  --grad-mix: linear-gradient(90deg, #35222A, #262238 45%, #17301F);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .34), 0 4px 16px rgba(0, 0, 0, .30);
  --shadow-2: 0 8px 32px rgba(0, 0, 0, .46);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, .40), 0 8px 28px rgba(0, 0, 0, .35);
}
:root[data-theme="dark"] .warm {
  --paper: #211D18;
  --paper-raised: #2C2721;
  --line: #423A31;
  --line-strong: #55493D;
  --ink-soft: #BCB0A3;
  --ink-faint: #A89F95;   /* see the block above: the body's .warm beats the root's dark faint */
  --accent-copper: #E08A5B;
  --accent-copper-label: #E08A5B;   /* 6.96:1 and up on the dark ground: the accent reads at label size as it is */
}

/* The viewer's own colour, switched (docs/R5-INTERFACE.md S3). One line, on <body>, written by
   `markMe()` in client/scenes/appShell.js from the same record identityFields writes. It sits after
   every palette block on purpose: body is inside :root, so this wins in light and dark alike, and
   the three values it points at are themselves theme-aware. There is no third value — an unknown
   or missing colour is pink, because the chrome must always be lit. */
body[data-me="blue"] {
  --me: var(--blue);
  --me-label: var(--blue-label);
  --me-tint: var(--blue-tint);
  --on-me-tint: var(--on-blue-tint);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  min-height: 100svh;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; }
img, svg, canvas, video { display: block; max-width: 100%; }
[hidden] { display: none !important; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } }

/* ---------- type ---------- */
.eyebrow { font: 700 12px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); }
.eyebrow--pink { color: var(--pink-label); }
.eyebrow--blue { color: var(--blue-label); }
.mono { font-family: var(--mono); }
.h1 { font-size: clamp(32px, 5vw, 40px); font-weight: 800; letter-spacing: -.02em; line-height: 1.1; margin: 0; text-wrap: balance; }
.h2 { font-size: clamp(22px, 3.2vw, 28px); font-weight: 800; letter-spacing: -.015em; line-height: 1.15; margin: 0; text-wrap: balance; }
.h3 { font-size: 18px; font-weight: 700; margin: 0; }
.lead { color: var(--ink-soft); font-size: 17px; margin: 0; max-width: 46ch; }
.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); }
.center { text-align: center; }
.balance { text-wrap: balance; }
.serif { font-family: var(--serif); }
.pixel { font-family: var(--pixel); }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: 0 20px; border-radius: 999px; border: 1px solid transparent;
  font-weight: 700; font-size: 16px; line-height: 1; text-decoration: none; white-space: nowrap;
  transition: transform .08s ease, background .15s ease, opacity .15s ease, box-shadow .15s ease;
  user-select: none;
}
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; transform: none; }
.btn-primary { background: var(--ink); color: var(--on-ink); }
.btn-primary:hover {
  /* Was a literal #2A2C33 — a light-register grey chosen when there was no dark register. In dark,
     --ink is near-white and --on-ink is near-black, so the label went from 15.48:1 at rest to
     1.24:1 the moment a cursor touched it, on every primary button in the product (REALUSE-1 §2,
     Cause B). Derived from the same two tokens the button already uses, it lifts the ink fill a
     little towards the paper in whichever direction "towards the paper" means today. Measured
     against --on-ink, 2026-09-05: light 13.94 → 12.63, dark 1.24 → 12.17. The light figure goes
     slightly DOWN, and that is the trade being made on purpose — 12.63 is far above the 4.5 AA
     asks, and buying it costs the theme that had no readable hover state at all.
     STYLE-GUIDE §02: take every colour from a token. */
  background: color-mix(in srgb, var(--ink) 88%, var(--paper));
}
.btn-ghost { background: var(--paper-raised); border-color: var(--line-strong); color: var(--ink); }
.btn-ghost:hover { border-color: var(--ink-faint); }
.btn-grad { background: var(--grad); color: var(--on-grad); box-shadow: var(--shadow-1); }
.btn-grad:hover { filter: brightness(1.03); }
.btn-link { background: none; border: 0; padding: 0; min-height: 0; color: var(--ink-soft); font-weight: 600; text-decoration: none; }
.btn-link:hover { color: var(--ink); }
.btn-lg { min-height: 56px; padding: 0 28px; font-size: 18px; border-radius: 12px; }
.btn-block { width: 100%; }
.btn-square { border-radius: var(--r8); }
.btn-pill-blue { background: var(--blue-tint); color: var(--on-blue-tint); }
.btn-danger { background: var(--red-tint); color: var(--on-red-tint); }

/* ---------- cards / chips / pills ---------- */
/* R5 S5 — a card is a surface that lifts, not a box that is drawn. The outline plus the shadow was
   saying the same thing twice, and an outlined rectangle with a 12px mono label over it is the
   single most template-looking thing in the shell. Ground, radius, one shadow, nothing else.
   `--shadow-card` is the shell's ONE lift, so every raised surface rises by the same amount. */
.card { background: var(--paper-raised); border-radius: var(--r-card); box-shadow: var(--shadow-card); }
.card--pad { padding: 20px; }
/* The flat variant keeps a hairline, because a card with no lift AND no edge is not a quiet card,
   it is an invisible one — on --paper-raised it would have no boundary at all. */
.card--flat { box-shadow: none; border: 1px solid var(--line); }
/* R5 S4 — the section label. It was `01 · KNOW EACH OTHER` in 12px mono, uppercase, letter-spaced
   and teal: five separate signals that this is software, stacked on one line. BRAND §3 asked for
   exactly that and has been rewritten. What is left is a small bold sentence in the quiet ink —
   the same thing a person writes at the top of a list, and nothing more.
   Listed twice on purpose: `p.slabel` carries one element of specificity so this beats the older
   mono rule in app/ui/app.css (tabs' file, loaded after this one) until that rule is deleted. */
.slabel, p.slabel {
  display: block; margin: 0;
  font-size: 13px; font-weight: 700; letter-spacing: 0; text-transform: none;
  color: var(--ink-faint);
}

/* R5 S6 — a tinted well behind an icon: the activity tiles and the row lists. Four colours because
   four kinds of thing, not four moods. Each pairs a tint with the readable member of its own
   family, so the glyph inherits a colour that is at least 4.5:1 on the square it sits in, in both
   themes. `flex: none` because a well in a flex row must not squash into an oval. */
.iwell {
  width: 40px; height: 40px; flex: none; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--pink-tint); color: var(--on-pink-tint);
}
.iwell--blue { background: var(--blue-tint); color: var(--on-blue-tint); }
.iwell--green { background: var(--green-tint); color: var(--on-green-tint); }
.iwell--lav { background: var(--lav-tint); color: var(--on-lav-tint); }
/* The viewer's own, for a well that means "you" rather than "this kind of thing". */
.iwell--me { background: var(--me-tint); color: var(--on-me-tint); }

.chip { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 12px; border-radius: 999px; font-size: 13px; font-weight: 600; }
.chip--pink { background: var(--pink-tint); color: var(--on-pink-tint); }
.chip--blue { background: var(--blue-tint); color: var(--on-blue-tint); }
.chip--ink { background: var(--ink); color: var(--on-ink); }
.chip--line { border: 1px solid var(--line); background: var(--paper-raised); }
.badge { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px; border-radius: 999px; font: 700 11px/1 var(--mono); letter-spacing: .04em; }
.badge--premium { background: var(--yellow-tint); color: var(--on-yellow-tint); }
.badge--unlock { background: var(--blue-tint); color: var(--on-blue-tint); }
.badge--adult { background: var(--ink); color: var(--on-ink); }
.badge--green { background: var(--green-tint); color: var(--on-green-tint); }
.pill { display: inline-flex; align-items: center; gap: 8px; height: 36px; padding: 0 14px; border-radius: 999px; background: var(--paper-raised); border: 1px solid var(--line); font-weight: 700; font-size: 14px; }
.pill--pink { border-color: var(--pink); }
.pill--blue { border-color: var(--blue); }
.pill .n { font-family: var(--mono); }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); vertical-align: middle; }
.dot--waiting { background: transparent; border: 2px solid var(--ink-faint); box-sizing: border-box; animation: pulse 1.4s ease-in-out infinite; }
.dot--on { background: var(--green); }
.dot--pink { background: var(--pink); } .dot--blue { background: var(--blue); }
@keyframes pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

/* ---------- segmented / toggles / fields ---------- */
.seg { display: inline-flex; gap: 8px; }
.seg button { min-width: 64px; height: 44px; padding: 0 16px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper-raised); font-weight: 700; }
.seg button[aria-pressed="true"] { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--r8); }
.toggle-row .t { font-weight: 700; }
.toggle-row .s { display: block; font-size: 13px; color: var(--ink-soft); font-weight: 400; }
.switch { position: relative; width: 44px; height: 26px; border-radius: 999px; background: var(--line); border: 0; padding: 0; transition: background .15s; flex: none; }
.switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .2); transition: transform .15s; }
.switch[aria-checked="true"] { background: var(--pink); }
.switch[aria-checked="true"]::after { transform: translateX(18px); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font: 700 12px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }
.input, textarea.input {
  height: 48px; padding: 0 14px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper-raised);
  font-size: 17px; width: 100%; outline: none; transition: border-color .15s;
}
.input:focus, textarea.input:focus { border-color: var(--ink); }
textarea.input { height: auto; min-height: 120px; padding: 12px 14px; resize: vertical; line-height: 1.45; }
.input--center { text-align: center; }
.count { font: 12px var(--mono); color: var(--ink-faint); text-align: right; }

/* ---------- code tiles ---------- */
.code { display: inline-flex; gap: 8px; }
.code-cell {
  width: 56px; height: 72px; border-radius: var(--r8); background: var(--ink); color: var(--on-ink);
  display: grid; place-items: center; font: 800 36px/1 var(--mono); letter-spacing: 0; text-transform: uppercase;
}
.code-cell--input { background: var(--paper-raised); color: var(--ink); border: 1px solid var(--line); }
.code-cell--input:focus-within, .code-cell--input.is-active { border-color: var(--ink); }
.code-cell input { width: 100%; height: 100%; border: 0; background: transparent; text-align: center; font: inherit; text-transform: uppercase; outline: none; caret-color: transparent; }
@media (max-width: 420px) { .code-cell { width: 48px; height: 62px; font-size: 30px; } .code { gap: 6px; } }

/* ---------- layout ---------- */
.page { min-height: 100svh; display: flex; flex-direction: column; }
.scene { width: min(var(--col), calc(100vw - 32px)); margin: 0 auto; padding: 24px 0 96px; display: flex; flex-direction: column; gap: 20px; align-items: center; text-align: center; }
.scene--wide { width: min(var(--col-wide), calc(100vw - 32px)); }
.scene--left { align-items: stretch; text-align: left; }
.stack { display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; gap: 12px; align-items: center; }
.row--between { justify-content: space-between; }
.row--center { justify-content: center; }
.row--wrap { flex-wrap: wrap; }
.grow { flex: 1 1 auto; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 640px) { .grid-2 { grid-template-columns: 1fr; } }
.topbar { position: fixed; inset: 0 0 auto 0; height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; z-index: 30; pointer-events: none; }
.topbar > * { pointer-events: auto; }
.avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--paper-raised); border: 1px solid var(--line); display: grid; place-items: center; font-weight: 700; }

/* ---------- the activity bar ----------
   S5 of docs/R2-INTERFACE.md, and the fix for the loudest thing real use found: "after finishing
   playing arcade there's no exit" (docs/REALUSE-1.md §3). Fifteen activities mounted into a bare
   page; five of the Arcade's seven screens had no header, no back and no home.

   44px, at the top of every scene of every activity, in the shell's own register whatever the game
   underneath is wearing. It is out of the flow like its parent .topbar — which is why .page pays
   for it in padding rather than the bar taking a row of its own; every game's first scene already
   assumes it starts at the top of the page, and a bar in the flow would push a hundred min-height
   rules 44px past the bottom of the screen. `padding-top` is inside `min-height: 100svh` because
   everything here is border-box, so the bar costs a scene nothing but the strip it covers. */
.topbar--activity {
  height: var(--bar-h); gap: 10px; padding: 0 12px; pointer-events: auto;
  background: var(--bar-ground); color: var(--bar-ink);
  border-bottom: 1px solid var(--bar-line);
  font-family: var(--font);
}
/* The bar is out of the flow, so the page pays for it — and every scene that asks for a whole
   screen has to ask for a whole screen MINUS the bar, or the page scrolls 44px for nothing. That
   is one number in fifteen stylesheets, so it is a token: `calc(100svh - var(--bar-h))` reads the
   same on a page with a bar and on one without, because without the class it is 0px and the rule
   is exactly what it was before R2.
   44 is written ONCE, on `--bar-h` at `:root` (with the rest of the bar's tokens), and read from
   there by the strip's own height and by the padding that keeps room for it. A body with no bar
   turns it off rather than restating it, so there is no second number to drift. */
body:not(.has-activitybar) { --bar-h: 0px; }
body.has-activitybar .page { padding-top: var(--bar-h); }
/* The shared scene layouts that ask for a whole screen. Every activity uses at least one of them,
   so they are corrected once here rather than fifteen times. */
body.has-activitybar .start,
body.has-activitybar .room,
body.has-activitybar .play,
body.has-activitybar .reveal,
body.has-activitybar .profile { min-height: calc(100svh - var(--bar-h)); }
/* Two fixed things that used to start at the top of the window and now start under the bar. The
   HUD (a game's score and timer) sits at z-index 20 and would simply have been covered; the
   connection banner sits at 80 and would have covered the one way out, which is the last thing a
   person should lose the moment the socket drops. */
body.has-activitybar .hud { top: calc(12px + var(--bar-h)); }
body.has-activitybar .connbar { top: var(--bar-h); }
/* On paper there is nothing to leave, so there is no bar and no room kept for one. future.css
   already hid `.topbar` for its printable plan; this makes that true for every activity and takes
   the 44px back with it. */
@media print {
  .topbar--activity { display: none !important; }
  /* Zeroing the token is the whole fix: the padding, every `calc(100svh - var(--bar-h))` and the
     two fixed offsets all read from it, so paper gets its top inch back in one line. */
  body.has-activitybar { --bar-h: 0px; }
}
/* The wordmark and the clocks are drawn here, not in app/ui/app.css where the shell draws them:
   a game page loads base.css and its own stylesheet and nothing else. */
.topbar--activity .mark { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--bar-ink); font: 800 17px/1 var(--font); letter-spacing: -.02em; }
.topbar--activity .mark__dots { display: inline-flex; gap: 4px; }
.topbar--activity .mark__dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.topbar--activity .mark__dot.is-pink { background: var(--bar-pink); }
.topbar--activity .mark__dot.is-blue { background: var(--bar-blue); }
.topbar--activity .clocks { display: flex; align-items: baseline; gap: 6px; min-width: 0; font: 700 12px/1 var(--mono); letter-spacing: -.01em; }
.topbar--activity .clock { display: inline-flex; align-items: baseline; gap: 4px; white-space: nowrap; }
.topbar--activity .clock__w { font-weight: 400; opacity: .78; }
.topbar--activity .clock.is-pink { color: var(--bar-pink-label); }
.topbar--activity .clock.is-blue { color: var(--bar-blue-label); }
.topbar--activity .clock-sep { color: var(--bar-faint); }
.topbar--activity .clocks[hidden], .topbar--activity .clock[hidden], .topbar--activity .clock-sep[hidden], .topbar--activity .clock__w[hidden] { display: none; }
.topbar--activity .topbar__right { display: flex; align-items: center; gap: 8px; }
/* One way out, and it says the word rather than drawing an arrow somebody has to interpret. */
.topbar__leave {
  position: relative; height: 32px; padding: 0 15px; border-radius: var(--r-pill);
  border: 1px solid var(--bar-edge); background: var(--bar-ground); color: var(--bar-ink);
  font: 700 13px/1 var(--font); cursor: pointer;
  transition: transform .08s ease, border-color .15s ease;
}
/* §05 wants 44px of finger. The bar IS 44px, so the pill is drawn small and its target grown to
   the full height of the strip, rather than the strip grown to fit a pill. */
.topbar__leave::after { content: ''; position: absolute; inset: -6px -4px; }
/* `.btn-ghost`'s hover, exactly — the border darkens and the fill does not move. STYLE-GUIDE §05
   says a shell surface does not invent a button, and this is `.btn-ghost` written in the bar's own
   tokens because the real one reads `--paper-raised` and `--ink`, which a game is allowed to
   repaint. The recipe is also the safe one: REALUSE-1 §2 Cause B is a hover that swapped a
   background and took a label from 15.48:1 to 1.24:1 in the dark register. A hover that never
   touches the ground it is read against cannot do that. */
.topbar__leave:hover { border-color: var(--bar-faint); }
.topbar__leave:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .topbar__leave:active { transform: none; } }
/* The last thing in the bar that was still reading a game's palette. STYLE-GUIDE §05: "Focus is
   always visible" — and for somebody on a keyboard the ring is not decoration, it is the only
   thing saying where "leave" is. The shell's global rule is `:focus-visible { outline: 2px solid
   var(--blue) }`, and `var(--blue)` is substituted at the FOCUSED element, which sits under a
   <body> that photobooth's karaoke room repaints to #56E0F0 (photobooth/ui/photobooth.css:10).
   So the ring alone would have gone cyan with the booth while every other pixel of the bar held.
   Same reason as every other --bar-* token, applied to the one property that was missed; only the
   colour is overridden, so the width, style and offset stay the shell's. */
.topbar--activity :focus-visible { outline-color: var(--bar-blue); }
@media (max-width: 430px) {
  .topbar--activity { padding: 0 10px; gap: 8px; }
  .topbar--activity .mark { font-size: 15px; }
  .topbar--activity .mark__dot { width: 6px; height: 6px; }
  .topbar--activity .clocks { font-size: 11px; gap: 4px; }
  .topbar--activity .clock__w { display: none; }
  .topbar__leave { padding: 0 12px; }
}

/* ---------- the bottom tab bar ----------
   Mounted by /app, /activity, /profile, /hub, /story and a couple's own front page. One bar, one
   look, written once against the base tokens: the app's face re-grounds --line, --paper-raised
   and --ink-faint on `body.face`, and the warm register re-tunes them on `.warm`, so these four
   rules come out right on every page that links this file — which is all of them. (R2 shipped
   them three times, in app.css, hub.css and a tabbar.css, because base.css was nobody's to widen
   in that block.) The floor under the bar is the page's contract, not this file's: the bar is
   fixed, so whichever page mounts it owns the padding that keeps its own last row clear. */
/* R5 S2 — a floating dock, not a full-bleed strip. Evan, of the old one: *"that menu bar is rlly
   ugly on mobile. it looks like AI"*. Four evenly-spaced hairline glyphs edge-to-edge along the
   bottom of the screen is the shape every generated app has; a dock that sits ON the page, inset
   and rounded, with the same card surface as everything else, is a room's furniture. The markup
   contract is untouched — same nav, same four data-tab values, same .is-on — because five surfaces
   mount it and four specs assert it. Only the look changed. */
.tabbar {
  position: fixed; z-index: 30; display: flex;
  left: 12px; right: 12px; bottom: calc(10px + env(safe-area-inset-bottom));
  max-width: 520px; margin-inline: auto;   /* on a laptop a centred dock, not a 1120px band */
  height: 60px;
  background: var(--paper-raised); border: 1px solid var(--line);
  border-radius: 22px; box-shadow: var(--shadow-card);
}
.tab {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; text-decoration: none; color: var(--ink-faint);
  font-size: 11px; font-weight: 600; letter-spacing: 0; min-height: var(--tap);
  border-radius: 20px;   /* so the focus ring and :active follow the dock's own corners */
}
/* The lit tab is the VIEWER's colour, not always pink (S3). --me-label is --pink-label or
   --blue-label, both of them the readable member of their family: 5.66:1 and 5.98:1 on the raised
   ground in light, and their lifted counterparts at night. 11px labels need 4.5:1 and get it. */
.tab.is-on { color: var(--me-label); }
.tab__icon { display: block; }
/* No motion on scroll — a bar that hides and returns is a bar you hunt for. One press, one pixel. */
.tab:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .tab:active { transform: none; } }

/* ---------- START ---------- */
.start { width: min(var(--col-wide), calc(100vw - 32px)); margin: 0 auto; min-height: 100svh; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 48px; align-items: center; padding: 48px 0; }
.start__copy { display: flex; flex-direction: column; gap: 14px; }
.start__back { font: 600 13px var(--mono); color: var(--ink-soft); text-decoration: underline; text-underline-offset: 3px; letter-spacing: .04em; }
.start__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.start__actions { display: flex; flex-direction: column; gap: 10px; width: min(380px, 100%); margin-top: 8px; }
.start__preview { border-radius: var(--r8); background: var(--grad-soft); box-shadow: var(--shadow-2); aspect-ratio: 4 / 3; position: relative; overflow: hidden; display: grid; place-items: center; }
.start__preview .cap { position: absolute; bottom: 20px; left: 0; right: 0; text-align: center; font: 13px var(--mono); color: var(--ink-soft); }
.start__join { display: flex; flex-direction: column; gap: 14px; width: min(380px, 100%); }
@media (max-width: 860px) { .start { grid-template-columns: 1fr; gap: 24px; padding: 24px 0 48px; } .start__preview { order: -1; aspect-ratio: 16 / 10; } }

/* ---------- ROOM ---------- */
.room { min-height: 100svh; justify-content: center; gap: 16px; padding-bottom: 24px; }
.room .presence { display: inline-flex; align-items: center; gap: 8px; color: var(--ink-soft); }
.room__actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }

/* ---------- PROFILE ---------- */
.profile { width: min(1240px, calc(100vw - 32px)); min-height: 100svh; justify-content: center; }
.profile__panels { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; width: 100%; }
.profile__panel { padding: 24px 20px 28px; display: flex; flex-direction: column; align-items: center; gap: 18px; min-height: 260px; }
.profile__panel .input { width: 280px; max-width: 100%; text-align: center; }
.swatches { display: flex; gap: 12px; }
.swatch { width: 56px; height: 56px; border-radius: 50%; border: 3px solid transparent; padding: 0; background-clip: padding-box; transition: transform .1s; }
.swatch--pink { background-color: var(--pink); } .swatch--blue { background-color: var(--blue); }
.swatch[aria-pressed="true"] { border-color: var(--ink); box-shadow: 0 0 0 2px var(--paper-raised) inset; }
.swatch:hover { transform: scale(1.04); }
.swatch--sm { width: 40px; height: 40px; }
.profile__name { font-size: 20px; font-weight: 800; }
@media (max-width: 720px) { .profile__panels { grid-template-columns: 1fr; } .profile__panel { min-height: 0; } }

/* ---------- PACK ---------- */
.pack { width: min(900px, calc(100vw - 32px)); padding-top: 32px; }
.pack__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 16px; width: 100%; }
.pack-card { position: relative; text-align: left; padding: 16px 14px 14px; display: flex; flex-direction: column; gap: 6px; min-height: 200px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper-raised); box-shadow: var(--shadow-1); transition: box-shadow .15s, transform .08s; }
.pack-card:hover { box-shadow: var(--shadow-2); }
.pack-card[aria-pressed="true"] { outline: 2px solid var(--ink); outline-offset: -1px; }
.pack-card .emoji { font-size: 28px; line-height: 1; }
.pack-card .t { font-size: 18px; font-weight: 800; letter-spacing: -.01em; }
.pack-card .d { font-size: 13px; color: var(--ink-soft); line-height: 1.35; flex: 1; }
.pack-card .n { font: 12px var(--mono); color: var(--ink-soft); }
.pack-card .badges { display: flex; gap: 6px; flex-wrap: wrap; }
.pack-card .lock { position: absolute; top: 12px; right: 12px; opacity: .55; }
.pack-card.is-locked { opacity: .78; }
.pack-card.is-locked .t, .pack-card.is-locked .d { color: var(--ink-faint); }
.pack-card--wide { grid-column: 1 / -1; flex-direction: row; align-items: center; min-height: 0; gap: 16px; padding: 22px 24px; background: var(--grad-mix); }
.pack__options { display: flex; flex-direction: column; align-items: center; gap: 14px; width: min(420px, 100%); margin-top: 8px; }
.pack__options .toggle-row { width: 100%; }
.hint { font-size: 14px; color: var(--ink-soft); }
.hint--blue { color: #1E5FBF; font-weight: 600; }
.hint--pink { color: var(--pink); font: 13px var(--mono); }

/* ---------- PLAY (HUD, headline, lock-in, reactions) ---------- */
.hud { position: fixed; top: 12px; left: 12px; right: 12px; display: flex; justify-content: space-between; pointer-events: none; z-index: 20; }
.hud .pill { pointer-events: auto; }
.play { min-height: 100svh; justify-content: center; gap: 18px; }
.play .meta { font: 700 13px var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); display: flex; gap: 14px; align-items: center; }
.play .meta .timer { color: var(--ink); font-size: 15px; letter-spacing: .04em; }
.play .meta .timer.is-low { color: var(--red); }
.play .question { font-size: clamp(24px, 3.4vw, 32px); font-weight: 800; letter-spacing: -.015em; line-height: 1.2; text-wrap: balance; max-width: 22ch; }
.options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
.option { min-height: 64px; padding: 14px 16px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper-raised); text-align: left; font-weight: 600; font-size: 16px; transition: border-color .12s, background .12s; }
.option:hover { border-color: var(--ink-faint); }
.option[aria-pressed="true"] { border-color: var(--ink); background: var(--ink); color: var(--on-ink); }
.option--pink[aria-pressed="true"] { background: var(--pink-tint); color: var(--ink); border-color: var(--pink); }
.option--blue[aria-pressed="true"] { background: var(--blue-tint); color: var(--ink); border-color: var(--blue); }
@media (max-width: 480px) { .options { grid-template-columns: 1fr; } }
.lockin { min-width: 200px; }
/* --green is a FILL, and 2.98:1 as text on its own tint. --on-green-tint is the paired token
   (5.83:1 light, 10.08:1 dark) and it was already declared — these just never used it. */
.lockin.is-locked { background: var(--green-tint); color: var(--on-green-tint); border: 1px solid var(--green); }
.reactions { display: flex; gap: 8px; justify-content: center; margin-top: 8px; }
.reactions button { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line); background: var(--paper-raised); font-size: 20px; line-height: 1; transition: transform .1s; }
.reactions button:hover { transform: scale(1.12); }
.reactions--sm button { width: 36px; height: 36px; font-size: 16px; }
.float-react { position: fixed; z-index: 40; pointer-events: none; font-size: 32px; animation: floatUp 1.8s ease-out forwards; }
.float-react.is-text { font-size: 14px; font-weight: 700; padding: 6px 10px; border-radius: 999px; background: var(--paper-raised); border: 1px solid var(--line); box-shadow: var(--shadow-1); }
@keyframes floatUp { 0% { transform: translateY(0) scale(.8); opacity: 0; } 15% { opacity: 1; transform: translateY(-10px) scale(1.1); } 100% { transform: translateY(-140px) scale(1); opacity: 0; } }

/* ---------- REVEAL ---------- */
.reveal { min-height: 100svh; justify-content: center; gap: 18px; }
.verdict { display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 16px; border-radius: 999px; font-weight: 800; }
.verdict--match { background: var(--green-tint); color: var(--on-green-tint); }
.verdict--miss { background: var(--red-tint); color: var(--on-red-tint); }
.verdict--close { background: var(--yellow-tint); color: var(--on-yellow-tint); }
.answers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
.answer { text-align: left; padding: 14px 16px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper-raised); display: flex; flex-direction: column; gap: 6px; }
.answer .cap { font: 700 11px var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }
.answer .v { font-size: 17px; font-weight: 600; word-break: break-word; }
.answer--pink { border-color: var(--pink); } .answer--blue { border-color: var(--blue); }
@media (max-width: 480px) { .answers { grid-template-columns: 1fr; } }
.ai-line { font-style: italic; color: var(--ink-soft); max-width: 44ch; }
.points { font: 700 13px var(--mono); letter-spacing: .08em; color: var(--ink-soft); }
.orb { width: 44px; height: 44px; border-radius: 50%; padding: 3px; background: conic-gradient(var(--pink), var(--blue), var(--pink)); animation: spin 1.1s linear infinite; }
.orb::after { content: ''; display: block; width: 100%; height: 100%; border-radius: 50%; background: var(--paper); }
@keyframes spin { to { transform: rotate(360deg); } }
.thinking { display: flex; flex-direction: column; align-items: center; gap: 10px; color: var(--ink-soft); font-size: 14px; }

/* ---------- RESULT ---------- */
.result { padding-top: 40px; gap: 22px; }
.result .headline { font-size: clamp(30px, 5vw, 44px); font-weight: 800; letter-spacing: -.02em; line-height: 1.1; }
.result .headline .pink { color: var(--pink); } .result .headline .blue { color: var(--blue); }
.scores { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
.score { padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.score .n { font: 800 32px/1 var(--mono); }
.score .who { font-weight: 700; }
.rounds-list { width: 100%; text-align: left; display: flex; flex-direction: column; }
.rounds-list .r { display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.rounds-list .r:last-child { border-bottom: 0; }
.nextup { width: 100%; display: flex; flex-direction: column; gap: 12px; align-items: center; }
/* The way home, first in the block. It is a full-width card rather than a link because it is the
   most important thing on a finished game's screen, and for a stranger it is the only invitation
   the product ever makes. Never a gradient behind this text — see --on-grad. */
.nextup__home {
  width: 100%; display: flex; flex-direction: column; gap: 3px;
  padding: 15px 17px; border-radius: var(--r16); text-decoration: none;
  border: 1px solid var(--line); background: var(--paper-raised); color: var(--ink);
}
.nextup__home.is-invite { border-color: var(--pink); background: var(--pink-tint); color: var(--on-pink-tint); }
.nextup__home:hover { box-shadow: var(--shadow-1); }
.nextup__home-t { font-weight: 800; font-size: 17px; }
.nextup__home-d { font-size: 13px; color: var(--ink-soft); }
.nextup__home.is-invite .nextup__home-d { color: var(--on-pink-tint); opacity: .9; }
.nextup__alts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
.nextup__alt { padding: 14px 16px; border-radius: var(--r8); text-align: left; text-decoration: none; display: flex; flex-direction: column; gap: 4px; }
.nextup__alt.pinkish { background: var(--pink-tint); } .nextup__alt.bluish { background: var(--blue-tint); }
.nextup__alt .t { font-weight: 800; } .nextup__alt .d { font-size: 13px; color: var(--ink-soft); }
.react-together { width: 100%; display: flex; flex-direction: column; gap: 10px; align-items: center; }
.say { display: flex; gap: 8px; width: min(420px, 100%); }
.say .input { flex: 1; height: 44px; }
.feedback-pill { position: fixed; left: 16px; bottom: 16px; z-index: 30; }

/* ---------- cursors ----------
   The partner's live pointer (client/core/cursors.js). `left/top: 0` is load-bearing and was the
   bug that made this feature look unbuilt for months: a `position: fixed` box with auto offsets is
   laid out at its STATIC position — for a node appended to <body>, that is the bottom of the page's
   content. The translate() then started from there, so on every screen taller than the viewport the
   arrow was drawn a page-height below the fold and nobody ever saw it. Pinning the origin to the
   viewport's top-left is what makes translate() mean what the sender meant.
   Opacity: fades out slowly when the pointer goes still (the class carries the long duration, fed
   from CURSOR_FADE_MS), and comes back fast when it moves again (the base rule's short one). */
.cursor { position: fixed; left: 0; top: 0; z-index: 50; pointer-events: none; opacity: 1; transition: transform .06s linear, opacity .14s ease; will-change: transform; }
.cursor--idle { opacity: 0; transition: transform .06s linear, opacity var(--cursor-fade, 700ms) ease; }
.cursor svg { width: 20px; height: 24px; display: block; }
.cursor--pink { color: var(--pink); } .cursor--blue { color: var(--blue); }
/* A finger is not an arrow. A touch partner gets a soft ring where they are pressing, which reads as
   "they are touching that" rather than as a mouse pointer nobody on a phone has. */
.cursor__dot { display: none; width: 18px; height: 18px; margin: -9px 0 0 -9px; border-radius: 50%; background: currentColor; opacity: .55; box-shadow: 0 0 0 2px #fff, 0 0 0 3px currentColor; }
.cursor--touch .cursor__arrow { display: none; } .cursor--touch .cursor__dot { display: block; }
.cursor__tag { position: absolute; left: 14px; top: 18px; padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 700; white-space: nowrap; }
.cursor--pink .cursor__tag { background: var(--pink-tint); color: var(--on-pink-tint); } .cursor--blue .cursor__tag { background: var(--blue-tint); color: var(--on-blue-tint); }
/* Reduced motion keeps the presence and drops the gliding: the arrow snaps to each sample instead
   of easing between them. The fade stays — it is a change of opacity, not movement. */
@media (prefers-reduced-motion: reduce) {
  .cursor { transition: opacity .14s ease; }
  .cursor--idle { transition: opacity var(--cursor-fade, 700ms) ease; }
}

/* ---------- modal / sheet / toast ---------- */
.scrim { position: fixed; inset: 0; background: rgba(23, 24, 28, .45); z-index: 60; display: grid; place-items: center; padding: 16px; animation: fade .15s ease; }
@keyframes fade { from { opacity: 0; } }
.modal { width: min(520px, 100%); max-height: calc(100svh - 32px); overflow: auto; background: var(--paper-raised); border-radius: var(--r16); box-shadow: var(--shadow-2); padding: 24px; position: relative; text-align: center; display: flex; flex-direction: column; gap: 14px; }
.modal > * { flex-shrink: 0; } /* a scrollable flex column must not squash its children (the skip link went to 0 height on phones) */
.modal .x { position: absolute; top: 12px; right: 12px; width: 36px; height: 36px; border-radius: 50%; border: 0; background: var(--paper); font-size: 18px; }
.sheet { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100%); background: var(--paper-raised); box-shadow: var(--shadow-2); z-index: 61; padding: 24px; overflow: auto; animation: slideIn .2s ease; }
@keyframes slideIn { from { transform: translateX(100%); } }
.toast { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 70; padding: 10px 16px; border-radius: 999px; background: var(--ink); color: var(--on-ink); font-weight: 600; font-size: 14px; box-shadow: var(--shadow-2); animation: fade .15s ease; }
.plan-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.plan-card { padding: 16px; border-radius: var(--r8); border: 1px solid var(--line); text-align: left; display: flex; flex-direction: column; gap: 6px; }
.plan-card.is-hot { border-color: var(--pink); background: var(--pink-tint); }
.plan-card .price { font-size: 26px; font-weight: 800; letter-spacing: -.02em; }
.plan-card .was { color: var(--ink-faint); text-decoration: line-through; font-weight: 600; margin-left: 6px; font-size: 16px; }
.plan-card .per { font-size: 13px; color: var(--ink-soft); }
@media (max-width: 480px) { .plan-cards { grid-template-columns: 1fr; } }

/* ---------- confetti ---------- */
.confetti { position: fixed; inset: 0; pointer-events: none; z-index: 45; overflow: hidden; }
.confetti i { position: absolute; top: -12px; width: 8px; height: 12px; border-radius: 2px; animation: confetti 1.6s ease-out forwards; }
@keyframes confetti { to { transform: translateY(110vh) rotate(720deg); opacity: .2; } }

/* ---------- misc ---------- */
.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; }
.connbar { position: fixed; top: 0; left: 0; right: 0; z-index: 80; text-align: center; font: 600 12px var(--mono); padding: 6px; background: var(--yellow-tint); color: var(--on-yellow-tint); }

/* ---------- "play whenever" (shared/async.js) ----------
   The mode chooser, the invite strip an unclaimed async room needs, and the WAITING screen. Plain
   cards, no new colours: this is a choice about time, not a feature that wants attention. */
.as-chooser { display: grid; gap: 8px; margin: 4px 0 12px; }
.as-modes { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.as-mode { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; text-align: left; padding: 12px 14px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper); cursor: pointer; }
.as-mode[aria-pressed="true"] { border-color: var(--pink); background: var(--pink-tint); }
.as-mode__t { font-weight: 700; font-size: 15px; }
.as-mode__s { font-size: 12px; color: var(--ink-soft); line-height: 1.35; }
.as-warn { font-size: 13px; line-height: 1.45; color: var(--on-yellow-tint); background: var(--yellow-tint); border-radius: var(--r8); padding: 8px 10px; }
.as-setup { display: grid; gap: 10px; }
.as-invite { display: grid; gap: 8px; padding: 12px 14px; border-radius: var(--r8); border: 1px dashed var(--line); }
.as-invite__t { font-size: 13px; color: var(--ink-soft); }
.as-invite__row { display: flex; gap: 8px; align-items: center; }
.as-link { flex: 1; min-width: 0; font: 600 13px var(--mono); }
.as-wait { display: grid; gap: 14px; justify-items: center; text-align: center; max-width: 560px; margin: 0 auto; }
.as-wait__count { font: 700 14px var(--mono); color: var(--ink-soft); }
.as-wait .as-invite { width: 100%; text-align: left; }
@media (max-width: 480px) { .as-modes { grid-template-columns: 1fr; } }

/* The "it is your turn" list on /app and /profile. */
.as-turns { display: grid; gap: 8px; }
.as-turn { display: flex; gap: 12px; align-items: center; padding: 12px 14px; border-radius: var(--r8); border: 1px solid var(--line); background: var(--paper); text-decoration: none; color: inherit; }
.as-turn.is-yours { border-color: var(--pink); background: var(--pink-tint); }
.as-turn__e { font-size: 22px; }
.as-turn__b { display: grid; gap: 2px; flex: 1; min-width: 0; }
.as-turn__t { font-weight: 700; }
.as-turn__s { font-size: 12px; color: var(--ink-soft); }
.as-turn__go { font: 700 12px var(--mono); }
