/* ============================================================================
   CORE — identical in every product. Do not edit per product.
   ============================================================================
   Everything metric is shared; everything hued is not. A game and a tax app
   should not look the same — they should look like they were built by the same
   people. So the type scale, the radii, the shadow geometry, the frame and the
   token NAMES are fixed here, and each product supplies only colour, below the
   PALETTE banner.

   The naming rule, which is the whole naming policy:

       the CSS variable is the Tailwind token path with dots as dashes.
       line.strong <-> --line-strong.  ink.2 <-> --ink-2.  brand.500 <-> --brand-500.

   It ends every naming argument and it is checkable by eye. Before it, sets
   broke it in both directions at once — Tailwind key `line` with CSS var
   `--border`, key `ink` with `--text-primary` — and neither name could be
   trusted to find the other.
   ============================================================================ */

:root {
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Type. The first four steps were byte-identical across every set that had
     a token layer — the strongest existing convention in the tree. */
  --fs-2xs: 10px;     --lh-2xs: 1.3;
  --fs-xs: 11.5px;    --lh-xs: 1.4;
  --fs-sm: 13px;      --lh-sm: 1.45;
  --fs-base: 14px;    --lh-base: 1.5;
  --fs-body: 16px;    --lh-body: 1.5;
  --fs-lead: 18px;    --lh-lead: 1.45;
  --fs-h3: 22px;      --lh-h3: 1.25;    --ls-h3: -0.01em;
  --fs-h2: 28px;      --lh-h2: 1.15;    --ls-h2: -0.02em;
  --fs-h1: 36px;      --lh-h1: 1.08;    --ls-h1: -0.02em;
  --fs-display: 46px; --lh-display: 1.02; --ls-display: -0.03em;

  --tracking-label: 0.18em;

  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --radius-xl: 20px;
  --radius-2xl: 24px;
  --radius-3xl: 28px;
  --radius-pill: 999px;
  --radius-phone: 40px;

  /* Geometry is shared; tint and strength are per product, because a dark
     palette needs far more alpha to read at all. --shadow-tint is an `r g b`
     triple, declared in the palette block. */
  --shadow-a1: 0.05;
  --shadow-a2: 0.07;
  --shadow-a3: 0.18;
  --shadow-sm: 0 1px 2px rgb(var(--shadow-tint) / var(--shadow-a1));
  --shadow:    0 4px 14px rgb(var(--shadow-tint) / var(--shadow-a2));
  --shadow-lg: 0 14px 34px -10px rgb(var(--shadow-tint) / var(--shadow-a3));
  --shadow-phone: 0 0 0 1px var(--line-strong), 0 30px 60px -20px rgb(var(--shadow-tint) / var(--shadow-a3));

  --frame-w: 390px;
  --frame-h: 844px;
}

html, body {
  font-family: var(--font-sans);
  background: var(--page);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  margin: 0;
}

.mono { font-family: var(--font-mono); }
.num  { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; letter-spacing: -0.01em; }

/* ---- Phone frame — a device, not a utility --------------------------------
   390 wide is the spec and never changes. The HEIGHT is clamped to whatever is
   left under the chrome bar, and the content scrolls inside the frame the way
   it scrolls on the phone.

   This replaces a transform: scale() approach that looked right and was not —
   a transform does not change layout height, so the frame still occupied its
   full 844px, and the page scrolled to reveal a frame that was already drawn
   smaller. --chrome-h is measured by _chrome.js, because the bar wraps to two
   rows on a narrow window and a guessed constant is wrong exactly when it
   matters. */
.phone {
  width: var(--frame-w);
  max-width: 100%;
  height: min(var(--frame-h), calc(100dvh - var(--chrome-h, 0px) - var(--stage-pad, 32px)));
  background: var(--frame);
  /* The frame re-resolves ink itself. Without this, text with no colour class
     inherits body's COMPUTED colour, which was resolved in the light scope —
     so a dark frame kept black body text while everything token-driven around
     it turned white. A variable remap only reaches declarations that read the
     variable; inheritance carries the old value, not the new name. */
  color: var(--ink);
  display: flex;
  flex-direction: column;
  position: relative;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--radius-phone);
  box-shadow: var(--shadow-phone);
}
.phone::-webkit-scrollbar { width: 0; }
/* A footer pinned to the bottom of the DEVICE, not the bottom of the content:
   sticky keeps it in view while the content above it scrolls, which is what a
   tab bar or a primary action actually does on the phone. */
.phone > .pin-bottom { margin-top: auto; position: sticky; bottom: 0; }

/* The stage is exactly the frame plus its padding, so the page itself never
   scrolls on a screen that already fits. */
.stage {
  display: grid;
  place-items: start center;
  /* The same variable the frame subtracts from its own height, so the two
     cannot disagree. A literal 16px here left the frame 32px shorter than the
     space it was given. */
  padding: calc(var(--stage-pad, 32px) / 2) 0;
  height: 100dvh;
  box-sizing: border-box;
}

/* ?bare is what the screenshots sheet embeds and what you capture a single
   screen with. The gutter and the device rounding are framing for a screen
   being *looked at*; inside a 390x844 tile they are a white margin and four
   clipped corners, and the frame no longer fills the tile it was sized for. */
[data-bare] .stage { padding: 0; }
[data-bare] .phone {
  border-radius: 0;
  box-shadow: none;
  max-width: none;
}

/* A theme scope is a token remap, never a second set of utilities. A utility
   written against a variable follows whichever scope it lands in, so the theme
   toggle changes no markup and nothing can be dark in one place and light in
   another. */
/* A screen with a modal over it does not scroll. `position: absolute; inset: 0`
   resolves against the frame's padding box, and in a scroll container that is
   the visible box pinned to the top of the scrollable area — so scrolling the
   frame slid the backdrop off the screen and left the card floating over
   nothing. A modal blocks the content underneath in the app too. */
.phone--modal { overflow: hidden; }

.phone--page   { background: var(--page); }
.phone--sunken { background: var(--sunken); }

/* The device status bar. The OS draws this, not the app — but the app pads for
   it (pt-safe), so a wireframe that omits it puts every screen ~44px too high
   and makes the first row look like it has more air than it will. */
.statusbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: none;
  padding: 12px 20px 2px;
  font-size: 13px;
  color: var(--ink);
}
.statusbar-time { font-weight: 600; letter-spacing: 0.02em; }
.statusbar-icons { display: flex; align-items: center; gap: 6px; }

/* ---- Dev chrome — injected by _chrome.js, not part of the product ---------
   Everything around the screen and nothing in it. One bar, same controls, same
   order, in every product.

   And the same COLOURS in every product. These are declared here rather than
   read from the palette because the bar is tooling: reading --brand-500 made
   the active chip blue in tic-tac-toe and navy in charades, so the same
   control looked like a different control depending on which set you opened,
   and the bar read as part of the product rather than as the thing holding
   it. Nothing below this line may reference a product token. */
:root {
  --chrome-bg:      #ffffff;
  --chrome-surface: #ffffff;
  --chrome-line:    #d4d4d8;   /* zinc-300 */
  --chrome-ink:     #3f3f46;   /* zinc-700 */
  --chrome-ink-2:   #71717a;   /* zinc-500 */
  --chrome-ink-3:   #a1a1aa;   /* zinc-400 */
  --chrome-active:  #18181b;   /* zinc-900 */
  --chrome-soft:    #f4f4f5;   /* zinc-100 */
}

.chrome {
  position: sticky;
  top: 0;
  z-index: 50;
  border-bottom: 1px solid var(--chrome-line);
  background: color-mix(in srgb, var(--chrome-bg) 92%, transparent);
  backdrop-filter: blur(8px);
  padding: 7px 16px;
}
.chrome-row {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  font-size: 11px;
}
.chrome-group { display: flex; align-items: center; gap: 6px; }
/* The middle group scrolls; the row does not wrap. A wrapped row is two
   stacked bars, which reads as two bars — chitragupt's settings flow has
   twenty-three screens and pushed the bar onto a second line on a full-width
   window. Scrolling the names keeps the row one line at every width, so the
   nav on the left and the display controls on the right never move, and
   --chrome-h stays put instead of doubling. min-width:0 is what actually lets
   a flex child shrink below its content and scroll. */
.chrome-mid {
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.chrome-mid::-webkit-scrollbar { display: none; }
.chrome-left, .chrome-right { flex: 0 0 auto; }
/* Navigation left, settings right, and a gap between them that grows. */
.chrome-right { margin-left: auto; }
/* There is no .chrome-label. The breadcrumb it styled — `mobile · /game ›` —
   said what the surface toggle on the right already says and what the flow
   tint in the chart already says, and it sat between the nav chips and the
   screen chips pushing the two apart for no information. */
.chrome-chip {
  border-radius: 6px;
  border: 1px solid var(--chrome-line);
  background: var(--chrome-surface);
  padding: 2px 8px;
  color: var(--chrome-ink);
  text-decoration: none;
  font: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.chrome-chip:hover { border-color: var(--chrome-active); }
.chrome-chip.is-current {
  border-color: var(--chrome-active);
  background: var(--chrome-soft);
  color: var(--chrome-active);
  font-weight: 600;
}

/* Icon controls ------------------------------------------------------------
   The controls that mean the same thing on every page — all routes,
   screenshots, back, next, theme, surface, notes — are icons. They are lucide,
   the estate's one icon set, asked for by name: nothing here carries a copy of
   any path data, and there is no second icon library on the page.

   The chip keeps its label in `title` and `aria-label`, so hovering still says
   which control this is. An icon whose meaning you have to click to learn is
   worse than the word it replaced. */
.chrome-chip--icon {
  width: 24px;
  height: 22px;
  padding: 0;
  display: inline-grid;
  place-items: center;
}
.chrome-ico { display: grid; place-items: center; line-height: 0; }
.chrome svg { width: 14px; height: 14px; }
/* The fallback when the lucide CDN did not load: the placeholder renders its
   own label instead of an empty square. Wider than an icon slot, so the chip
   grows to fit rather than clipping the word. */
.chrome-ico--text {
  font-size: 9px;
  font-weight: 700;
  font-style: normal;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1;
  padding: 0 4px;
  white-space: nowrap;
}
.chrome-chip--icon:has(.chrome-ico--text) { width: auto; padding: 0 4px; }
.seg-chip--icon:has(.chrome-ico--text) { padding: 2px 6px; }

/* Segmented controls: surface, theme, and this screen's states. Always
   rendered in the same slot — a control that appears only sometimes moves
   everything beside it the day a product gains a surface or a dark scope. */
.seg {
  display: inline-flex;
  border: 1px solid var(--chrome-line);
  border-radius: 999px;
  overflow: hidden;
}
.seg--states { margin-left: 4px; }
.seg-chip {
  padding: 2px 10px;
  font: inherit;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--chrome-ink-2);
  background: var(--chrome-surface);
  text-decoration: none;
  border: 0;
  cursor: pointer;
  white-space: nowrap;
}
.seg-chip + .seg-chip { border-left: 1px solid var(--chrome-line); }
.seg-chip:hover { color: var(--chrome-active); }
.seg-chip.is-current { background: var(--chrome-active); color: #fff; }
.seg-chip--icon { padding: 2px 8px; display: inline-grid; place-items: center; }

/* A surface that is not drawn is struck through, never omitted: a missing
   thing has to look missing, or its absence reads as "no such thing". */
.seg-chip.is-absent { color: var(--chrome-ink-3); text-decoration: line-through; cursor: default; }
/* line-through does not cross an <svg>, so an icon half draws its own strike:
   a hairline at the same angle, in the same colour, so a struck icon and a
   struck word read as the same statement. It applies to a dead back/next as
   well as to an undrawn theme or surface — §4 says the ends of the set are
   struck through, and dimming alone did not say it. */
.seg-chip--icon.is-absent,
.chrome-chip--icon.is-absent { position: relative; text-decoration: none; }
.seg-chip--icon.is-absent::after,
.chrome-chip--icon.is-absent::after {
  content: "";
  position: absolute;
  left: 5px;
  right: 5px;
  top: 50%;
  border-top: 1px solid currentColor;
  transform: rotate(-20deg);
}
/* A dead back/next at the ends of the set: shown, not hidden, so the bar does
   not reflow as you step through it. */
.chrome-chip.is-absent { color: var(--chrome-ink-3); border-color: var(--chrome-line); cursor: default; }
.chrome-chip.is-absent:hover { border-color: var(--chrome-line); }

/* ---- Notes drawer --------------------------------------------------------
   The rationale lives in the wiki and only in the wiki. This renders it beside
   the screen on demand, collapsed by default — the screen is the thing under
   review, and an essay next to it competes for the eye.

   Dev chrome, so the same fixed colours as the bar. */
.notes {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(400px, 92vw);
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: var(--chrome-surface);
  border-left: 1px solid var(--chrome-line);
  box-shadow: -12px 0 32px rgb(24 24 27 / 0.12);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--chrome-ink);
}
/* A panel that covers a third of the screen needs a way out that is not
   "find the button you opened it with", which the bar may have scrolled. */
.notes-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--chrome-line);
  flex: none;
}
.notes-title {
  flex: 1;
  margin: 0;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 700;
  color: var(--chrome-ink-2);
}
.notes-close {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 1px solid var(--chrome-line);
  border-radius: 7px;
  background: var(--chrome-surface);
  color: var(--chrome-ink);
  font: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.notes-close:hover { background: var(--chrome-soft); border-color: var(--chrome-active); }
.notes-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 18px 28px; }
.notes-body h3, .notes-body h4, .notes-body h5 {
  font-size: 13px; color: var(--chrome-active); margin: 16px 0 6px;
}
.notes-body h3:first-child, .notes-body h4:first-child { margin-top: 0; }
.notes-body p { margin: 0 0 10px; }
.notes-body ul { margin: 0 0 10px; padding-left: 18px; }
.notes-body li { margin-bottom: 4px; }
.notes-body code {
  font-family: var(--font-mono); font-size: 11.5px;
  background: var(--chrome-soft); padding: 1px 4px; border-radius: 4px;
}
.notes-body a { color: var(--chrome-active); }

/* The info button lives at the far right of the bar, past the display
   controls: it is about the page you are on, and it is the only control that
   opens something over the screen rather than changing the screen. */
/* The notes button is a .chrome-chip--icon like the rest; it only differs in
   being round, because it is the one control that opens something OVER the
   screen rather than changing which screen you are looking at. */
.chrome-info { border-radius: 999px; margin-left: 2px; }
.chrome-info.is-current { background: var(--chrome-active); border-color: var(--chrome-active); color: #fff; }
/* A screen with no wiki page still renders the button, struck out, for the
   same reason a missing theme is struck out rather than omitted: the gap has
   to look like a gap. */
.chrome-info.is-absent { color: var(--chrome-ink-3); cursor: default; }

/* There is no .screen-layout and no .rationale any more. One screen per file:
   a second frame in a file has no URL, cannot appear in routes.js, and cannot
   be linked to. Themes are the toggle in the chrome bar, states are their own
   files, and the reasoning is in the wiki behind the notes drawer. */

/* ---- The flow chart ------------------------------------------------------
   The gallery is this and nothing else: no title, no description, no tables.
   React Flow draws it; the node positions come from routes.js. */
/* The chart fills what is left under the bar. _chrome.js measures the bar and
   publishes --chrome-h, so this stays right when it wraps to two rows. */
.chart-page { height: 100dvh; display: flex; flex-direction: column; }
.chart-canvas { flex: 1; min-height: 0; background: var(--page); }

/* React Flow paints its own node chrome; this replaces it rather than sitting
   on top, so the nodes look like the rest of the estate. */
.react-flow__node.fnode {
  width: auto;
  padding: 10px 14px 10px 12px;
  border: 1px solid var(--line);
  /* The flow's colour, on the edge a reader's eye follows down a column. */
  border-left: 4px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  font-family: var(--font-sans);
  opacity: 0.88;
  display: flex;
  align-items: center;
  gap: 12px;
}
.react-flow__node.fnode:hover { border-color: var(--brand-500); opacity: 1; }
/* The normal way through the product is the thing a flow chart has to answer
   first, so the screens on it are the ones that read as solid. */
.react-flow__node.fnode.is-main { opacity: 1; box-shadow: var(--shadow); }
/* The two ends say so: where a session starts, and where it finishes. */
.react-flow__node.fnode.is-start { box-shadow: 0 0 0 2px var(--brand-500), var(--shadow); }
.react-flow__node.fnode.is-end   { box-shadow: 0 0 0 2px var(--success), var(--shadow); }
/* Hovering a screen isolates its own paths: everything it does not touch fades
   back. With thirty edges on one chart this is the difference between tracing
   a route and guessing at one. Declared after .is-main deliberately — same
   specificity, and the dim has to win over the spine's own opacity. */
.react-flow__node.fnode.is-dim { opacity: 0.16; }
.react-flow__node.fnode.is-lit {
  opacity: 1;
  border-color: var(--brand-500);
  box-shadow: 0 0 0 2px var(--brand-500), var(--shadow);
}
/* The screen whose routes are being traced, as against the screens that lead
   to it — otherwise "how do I reach X" lights up a dozen boxes and X is not
   one of them to look at. */
.react-flow__node.fnode.is-sel {
  border-color: var(--brand-700);
  background: var(--brand-50);
  box-shadow: 0 0 0 3px var(--brand-500), var(--shadow-lg);
}
.fnode-label { display: flex; flex-direction: column; gap: 2px; text-align: left; }
/* The box asks the chart a question; only this opens the screen. Without a
   separate control there was no way to interrogate a node without leaving the
   page you were interrogating it from. */
.fnode-go {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-left: auto;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--frame);
  color: var(--ink-3);
}
.fnode-go:hover { background: var(--brand-500); border-color: var(--brand-500); color: #fff; }
.fname { font-size: 13px; font-weight: 700; color: var(--ink); line-height: 1.2; }
.fflow { font-size: 9.5px; color: var(--ink-3); letter-spacing: 0.02em; }
.react-flow__handle { opacity: 0; }

/* The key. Six swatches, so the colours mean something without a paragraph. */
.react-flow__panel.fkey {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  max-width: 260px;
  margin: 12px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  box-shadow: var(--shadow-sm);
  font-size: 10px;
  color: var(--ink-2);
}
.fkey-item { display: inline-flex; align-items: center; gap: 5px; }
.fkey-dot { width: 9px; height: 3px; border-radius: 2px; }
.fkey-line { width: 14px; height: 0; border-top: 2px solid var(--brand-500); }
.fkey-toggle {
  flex: 0 0 100%;
  margin-bottom: 2px;
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font: inherit;
  font-weight: 700;
  color: var(--ink-2);
  cursor: pointer;
}
.fkey-toggle.is-on { background: var(--brand-500); border-color: var(--brand-500); color: #fff; }
.fkey-hint { flex: 0 0 100%; margin-top: 2px; color: var(--ink-4); font-size: 9.5px; }

.react-flow__controls-button {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  fill: var(--ink-2);
}

/* ---- Gallery -------------------------------------------------------------
   The route table IS the screens inventory. A second inventory in Markdown is
   a copy that drifts the day a screen is added. */
.route-table { width: 100%; font-size: 13px; border-collapse: collapse; }
.route-table th {
  text-align: left;
  padding: 9px 13px;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-3);
  border-bottom: 1px solid var(--line);
  background: var(--muted);
}
.route-table td {
  padding: 9px 13px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.route-table tr:last-child td { border-bottom: none; }

.section-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--ink-3);
  font-weight: 700;
}

/* Status of a screen, on its gallery row and nowhere else. A screen must not
   move sections the day it is approved. */
.status { font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 6px; white-space: nowrap; }
.status--built    { background: var(--success-bg); color: var(--success-fg); }
.status--approved { background: var(--muted);      color: var(--ink-2); }
.status--review   { background: var(--warn-bg);    color: var(--warn-fg); }

.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

/* lucide injects an <svg> in place of the <i> and inherits currentColor. */
.lucide { display: inline-block; vertical-align: -0.125em; stroke-width: 2; }

/* ---- a11y baseline -------------------------------------------------------- */
button:focus-visible,
a:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
}

/* ============================================================================
   PALETTE — Imposter. The only block that differs between products.
   ============================================================================
   RESERVED: the light green is Civilian and black is Imposter. Nothing else in
   this set may resolve to either. A player reads the reveal in two seconds
   across a noisy table; if green can also mean "Food & Drink" then the one
   colour carrying the whole game carries nothing.

   NO RED, including for destructive actions. `danger` is a white outline and a
   plain word. That convention is older than this palette and it loses,
   deliberately — there is no third hue to spend.

   THIS IS A DARK-ONLY SET, and that is the product, not a gap. The app ships
   one look: every screen is flooded black and `userInterfaceStyle` is `dark`
   with no light block to hand off to (app.config.ts). So the palette lives in
   `:root` and `.phone--dark` is the same scope by another name — the chrome
   bar toggles that class on, and it must resolve to something.

   KNOWN DRIFT — the ramp is ordered for the scope it renders in.
   src/global.css calls this hue `accent-*` and runs it palest-first, the way a
   light palette does. Here it is `brand-*` — §8 reserves `accent` for a
   product's SECOND colour and imposter has one hue, not two — and it runs
   light-ward as the number rises, which is what tic-tac-toe's `.phone--dark`
   does and what the flow chart needs: `_gallery.js` paints main-path edge
   labels in `--brand-700`, and #15803d on a black canvas is unreadable.
   The VALUES are the app's, slot for slot:

       design --brand-200  ===  app accent-700  #15803d
       design --brand-400  ===  app accent-500  #22c55e
       design --brand-500  ===  app accent-400  #4ade80
       design --brand-700  ===  app accent-300  #86efac
       design --brand-900  ===  app accent-100  #dcfce7
       design --onbrand    ===  app onaccent    #052e16

   Renaming the app's scale to match is an app change, not a design change.
   ============================================================================ */

:root,
.phone--dark {
  /* Black ink on a black ground needs far more alpha than a light set does. */
  --shadow-tint: 0 0 0;
  --shadow-a1: 0.30;
  --shadow-a2: 0.35;
  --shadow-a3: 0.60;

  /* Brand · the one light green. See the KNOWN DRIFT note above for why this
     ramp runs dark-to-light rather than the other way round. */
  --brand-50:  rgba(134,239,172,0.14);
  --brand-100: rgba(134,239,172,0.24);
  --brand-200: #15803d;   /* the deep green — the 3D button edge, the Civilian stage's top stop */
  --brand-400: #22c55e;   /* the strong green — the corner glow, a toggle that is on */
  --brand-500: #4ade80;   /* the chart spine and the focus ring */
  --brand-700: #86efac;   /* THE light green — every CTA fill, and the Civilian role */
  --brand-900: #dcfce7;   /* the palest wash */

  /* Ink on the green. The CTA is the same green on every stage, so its label
     cannot follow the ink ramp or it turns white-on-green. */
  --onbrand: #052e16;

  /* No `accent-*`. §8 reserves that name for a product's SECOND brand colour,
     and this product has exactly one hue. `_gallery.js` tints the `store` flow
     with `var(--accent-500, var(--brand-500))` precisely so a set like this one
     falls back rather than painting nothing. */

  /* Text — white alphas, because every stage is dark. */
  --ink: #ffffff;
  --ink-2: rgba(255,255,255,0.78);
  --ink-3: rgba(255,255,255,0.56);
  --ink-4: rgba(255,255,255,0.36);
  --ink-inverse: #0a0a0a;     /* ink on the one light surface — see --paper */
  --ink-inverse-2: #52525b;   /* its quieter label, for the caption over a secret */

  /* Surfaces. Every panel sits on a flooded stage, so these are white alphas
     rather than greys: a solid #1f1f1f card on the Civilian green reads as a
     hole punched through the screen. */
  --page: #0a0a0a;
  --frame: #000000;
  --surface: rgba(255,255,255,0.10);
  --muted: rgba(255,255,255,0.06);
  --sunken: rgba(255,255,255,0.16);

  --line: rgba(255,255,255,0.16);
  --line-strong: rgba(255,255,255,0.30);

  /* Status. Success is the brand green. Everything else is neutral white,
     because a warning hue and a danger hue would both have to be red. */
  --success: #86efac;             --success-bg: rgba(134,239,172,0.14); --success-fg: #86efac;
  --warn:    rgba(255,255,255,0.5); --warn-bg:   rgba(255,255,255,0.12); --warn-fg:    #ffffff;
  --danger:  rgba(255,255,255,0.5); --danger-bg: rgba(255,255,255,0.12); --danger-fg:  #ffffff;
  --info:    rgba(255,255,255,0.5); --info-bg:   rgba(255,255,255,0.12); --info-fg:    #ffffff;

  /* Product · THE TWO ROLES — reserved, see the banner. Civilian is the same
     green as `--brand-700` and Imposter the same black as `--coal-800`; both
     are named again here because the role is the meaning and the ramp is not.
     Rename the ramp and the roles must not move with it. */
  --role-civilian: #86efac; --role-civilian-bg: #052e16; --role-civilian-fg: #15803d;
  --role-imposter: #0a0a0a; --role-imposter-bg: #ffffff; --role-imposter-fg: #3f3f46;

  /* Product · COAL — the ground, and the greys lifted off it. Every stage
     bottoms out at `--coal-900`; these are the stops above it, and they are
     the only solid greys in the set. */
  --coal-900: #000000;
  --coal-800: #0a0a0a;
  --coal-700: #141414;
  --coal-600: #1f1f1f;
  --coal-500: #2b2b2b;
  --coal-400: #3f3f46;

  /* Product · PAPER — the one light surface, for anything that must read as a
     physical object rather than a tint: a pack plate, the secret panel under
     the card. Its ink is `--ink-inverse`, and `--paper-2` is the plate sitting
     ON the paper — the imposter's hint box. src/global.css has both. */
  --paper: #ffffff;
  --paper-2: #f4f4f2;

  /* Product · the card radius. Bigger than `--radius-3xl` and used by exactly
     two things: the player card and the pack tiles. */
  --radius-card: 34px;

  /* Product · one step ABOVE the CORE type ramp, and one extra tracking step.
     A secret word is read across a table at an angle by someone else's friend,
     and the role word is read from a metre away; `display` at 46px is the top
     of the shared ramp and it is not big enough for either. Declared here
     rather than in CORE because no other product in the estate needs a step
     this large — CORE stays byte-identical. */
  --fs-hero: 60px;  --lh-hero: 0.98;  --ls-hero: -0.04em;
  --tracking-role: 0.22em;
}


/* ============================================================================
   COMPONENTS — Imposter
   ============================================================================ */

/* ---- THE FOUR STAGES ------------------------------------------------------
   Every screen is flooded; only which one changes. A full-bleed saturated
   ground with one card on it is what every app above 5M downloads in this
   category does.

     brand      outside a round: onboarding, setup, store, settings
     night      inside a round, working: the talk screen, the re-check
     civilian   a card that knows the word
     imposter   a card that does not — and the unmasking at the end

   Brand and night are both black; the only difference is that brand carries
   the green glow and night has no light in it at all. That one difference
   separates "the app" from "a round is happening" without spending a second
   hue. The stops are src/theme/stages.ts, value for value.

   Each stage's LIGHTEST stop has to carry white body copy at AA or better.
   Civilian started at #86efac and white on it measured about 1.5:1 — a stage
   is not a swatch. The light green survives as the glow and the CTA. */
.phone--brand,
.phone--night,
.phone--civilian,
.phone--imposter { background-color: var(--coal-900); }

.phone--brand {
  background-image:
    radial-gradient(130% 80% at 80% -10%, rgba(34,197,94,0.55) 0%, transparent 55%),
    linear-gradient(165deg, #2b2b2b 0%, #141414 55%, #000000 100%);
}
.phone--night {
  background-image: linear-gradient(165deg, #1f1f1f 0%, #0a0a0a 55%, #000000 100%);
}
.phone--civilian {
  background-image:
    radial-gradient(130% 80% at 80% -10%, rgba(134,239,172,0.45) 0%, transparent 55%),
    linear-gradient(165deg, #15803d 0%, #08351b 55%, #000000 100%);
}
/* Imposter is black lit from the corner in white — a spotlight on someone
   hiding. Night is black with no light in it at all, which is what keeps the
   two apart once the content is stripped away. */
.phone--imposter {
  background-image:
    radial-gradient(130% 80% at 80% -10%, rgba(255,255,255,0.35) 0%, transparent 55%),
    linear-gradient(165deg, #3f3f46 0%, #141414 55%, #000000 100%);
}

/* The corner lift — the CSS equivalent of the blurred orb src/components/ui/
   stage.tsx renders. It is part of the stage scope rather than a hand-placed
   div on every screen: four copies of one overlay is four chances to forget it.

   The sunburst that used to sit here is gone, and it was wrong twice over. A
   repeating conic gradient has hard stops, so even at 2% white it banded into
   twenty visible wedges across every card; and stage.tsx never had one, so the
   wireframes specified a treatment the app does not implement. */
.phone--brand::before,
.phone--night::before,
.phone--civilian::before,
.phone--imposter::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(60% 45% at 82% 4%, rgba(255,255,255,0.13) 0%, transparent 70%);
  pointer-events: none;
  z-index: 0;
}
.phone--brand > *,
.phone--night > *,
.phone--civilian > *,
.phone--imposter > * { position: relative; z-index: 1; }

/* ---- Elevation ------------------------------------------------------------
   `--shadow-card` is the party-game lift: a card sitting ON a saturated stage
   has to read as a physical object, and a 14px blur at 7% opacity does not.
   `--shadow-press` is the 3D button bottom edge — the chunky pressable every
   app in this category uses for its primary action. */
.shadow-card { box-shadow: 0 24px 48px -12px rgba(0,0,0,0.55), 0 2px 0 0 rgba(255,255,255,0.12) inset; }
.shadow-press { box-shadow: 0 5px 0 0 var(--brand-200); }

/* ---- The face-down card back ----------------------------------------------
   Unlit, so its lift comes entirely from the stage behind it: a card that glows
   is a card someone else at the table can read from across the room. */
.bg-cardback { background-image: linear-gradient(150deg, var(--coal-500) 0%, var(--coal-800) 100%); }

/* ---- Timer ring -----------------------------------------------------------
   A ring, not a bar: the talk phase is the loudest part of the game and nobody
   is watching the phone, so "time left" has to read from across a table. The
   hole is a pseudo-element, which is why this is not a utility. The sweep comes
   from `--pct`, set per instance with Tailwind's arbitrary-property form
   `[--pct:65%]`. */
.timer {
  position: relative;
  background: conic-gradient(var(--brand-500) var(--pct, 65%), rgba(255,255,255,0.14) 0);
}
.timer::after {
  content: "";
  position: absolute;
  inset: 13px;
  border-radius: var(--radius-pill);
  background: var(--coal-800);
}
/* The hole is a later positioned sibling, so without this it paints OVER the
   countdown and the ring renders with an empty middle. */
.timer > * { position: relative; z-index: 1; }
/* The last ten seconds go white, not red. There is no red. */
.timer--warning { background: conic-gradient(#ffffff var(--pct, 15%), rgba(255,255,255,0.14) 0); }

/* ---- Toggle knob — a pseudo-element, so no utility form exists ------------ */
.toggle::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 20px; height: 20px;
  border-radius: var(--radius-pill);
  background: #ffffff;
  box-shadow: var(--shadow-sm);
  transition: left 0.15s;
}
.toggle.is-on::after { left: 21px; }

/* ---- Overlapping avatar stack — a negative margin on every child but the
        first, which no utility expresses. */
.av-stack > * { margin-left: -9px; box-shadow: 0 0 0 2px var(--coal-800); }
.av-stack > *:first-child { margin-left: 0; }

/* ---- Skeleton shimmer ---------------------------------------------------- */
@keyframes shimmer { from { background-position: -200px 0; } to { background-position: 200px 0; } }
.skel {
  background: linear-gradient(90deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0.10) 100%);
  background-size: 400px 100%;
  animation: shimmer 1.4s linear infinite;
}

/* ---- Backdrop for modals --------------------------------------------------
   A modal CARD uses `bg-frame`, never `bg-surface`: `--surface` is a
   translucent white tint, which is right on a solid stage and wrong over a
   backdrop — the screen shows straight through the card. */
.backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.62); }

/* ---- Utility ------------------------------------------------------------- */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
