/* ============================================================================
   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 — Chitragupt. This is the only block that differs between products.
   ============================================================================
   Every value here is lifted from apps/website/src/app/globals.css, which is
   the running product's own token file — not remembered, not eyeballed off a
   screenshot. Where a name below has no counterpart there (the brand ramp
   steps, `sunken`), it is interpolated from the two the app does declare.

   RESERVED — the workspace and tier colours. Chitragupt shows four different
   people's data in one shell and sells three tiers, and the colour is how you
   tell which you are looking at before you read a word:

     --ws-personal  emerald   your own data          (also the brand)
     --ws-family    blue      the household          (also Pro Family)
     --ws-ca        amber     a chartered accountant (also money and ratings)
     --ws-admin     rose      the back office        (also destructive)
     --tier-ai      purple    AI Self, and nothing else

   None of the five may be spent on anything but its meaning. A green "Save"
   beside a green "Aarav · personal" makes the header stop being an answer to
   "whose money is this", which on a tax product is the question that matters.

   The set is drawn DARK — that is the product's look, not a preference — so
   `themes` in routes.js lists dark first and the set opens dark. :root is
   still the light palette, because _chrome.js applies a class only for a
   non-light theme and light is therefore always the base.
   ============================================================================ */

:root {
  --shadow-tint: 9 9 11;
  --shadow-a1: 0.06;
  --shadow-a2: 0.08;
  --shadow-a3: 0.12;

  /* brand — emerald. --brand-500 and --brand-400 are the app's --brand and
     --brand-soft; the rest of the ramp is Tailwind emerald at those steps. */
  --brand-50:  #ecfdf5;
  --brand-100: #d1fae5;
  --brand-200: #a7f3d0;
  --brand-400: #10b981;
  --brand-500: #059669;
  --brand-700: #047857;
  --brand-900: #064e3b;

  /* accent — amber. The CA marketplace, prices and ratings. */
  --accent-50:  #fffbeb;
  --accent-100: #fef3c7;
  --accent-200: #fde68a;
  --accent-400: #f59e0b;
  --accent-500: #d97706;
  --accent-600: #b45309;
  --accent-700: #b45309;
  --accent-900: #78350f;

  --ink: #111827;
  --ink-2: #4b5563;
  --ink-3: #6b7280;
  --ink-4: #9ca3af;
  --ink-inverse: #ffffff;

  --page: #f3f4f6;
  --frame: #f9fafb;
  --surface: #ffffff;
  --muted: #f3f4f6;
  --sunken: #e9ebef;

  --line: #d1d5db;
  --line-strong: #9ca3af;

  --success: #059669; --success-bg: #d1fae5; --success-fg: #047857;
  --warn:    #d97706; --warn-bg:    #fef3c7; --warn-fg:    #b45309;
  --danger:  #e11d48; --danger-bg:  #ffe4e6; --danger-fg:  #be123c;
  --info:    #2563eb; --info-bg:    #dbeafe; --info-fg:    #1d4ed8;

  /* PRODUCT · AI — the AI Self tier. Purple is this and nothing else. */
  --ai: #7c3aed; --ai-bg: #ede9fe; --ai-fg: #6d28d9;

  /* PRODUCT · WORKSPACE — see the banner. Whose data is on screen. */
  --ws-personal: #059669;
  --ws-family:   #2563eb;
  --ws-ca:       #d97706;
  --ws-admin:    #e11d48;
  --tier-ai:     #7c3aed;
}

/* Dark is a remap of the same names, never a second set of utilities. Every
   utility written against a variable follows the scope it lands in, so no
   markup changes between the two frames drawn side by side.

   Unlike tic-tac-toe, `surface` here is SOLID rather than a translucent white
   tint. The app's --bg-elevated is solid, and a solid surface is also what
   makes a modal card safe: §9's "a modal card uses bg-frame, not bg-surface"
   exists because a translucent surface lets the screen show through the card.

   TWO selectors, not one. _chrome.js scopes a theme by putting .phone--dark on
   every .phone, which works for a set drawn as phone frames and does nothing at
   all for one that is not: the web surface here has no .phone element, so the
   toggle set data-wf-theme on the root, stored it, and changed not a single
   colour. The root selector is what makes the control work on a web screen, on
   the flow chart and on the contact sheet. Both are kept because they resolve
   to the same values, so a phone frame is unaffected either way. */
:root[data-wf-theme="dark"],
.phone--dark {
  --shadow-a1: 0.5;
  --shadow-a2: 0.5;
  --shadow-a3: 0.6;

  --brand-50:  rgba(16,185,129,0.12);
  --brand-100: rgba(16,185,129,0.20);
  --brand-200: #6ee7b7;
  --brand-400: #34d399;
  --brand-500: #10b981;
  --brand-700: #6ee7b7;
  --brand-900: #a7f3d0;

  --accent-50:  rgba(245,158,11,0.12);
  --accent-100: rgba(245,158,11,0.20);
  --accent-200: #fcd34d;
  --accent-400: #fbbf24;
  --accent-500: #f59e0b;
  --accent-600: #fcd34d;
  --accent-700: #fcd34d;
  --accent-900: #fde68a;

  --ink: #fafafa;
  --ink-2: #a1a1aa;
  --ink-3: #71717a;
  --ink-4: #52525b;
  --ink-inverse: #09090b;

  --page: #0a0a0a;
  --frame: #09090b;
  --surface: #18181b;
  --muted: #27272a;
  --sunken: #131316;

  --line: #27272a;
  --line-strong: #3f3f46;

  --success: #10b981; --success-bg: rgba(16,185,129,0.14); --success-fg: #6ee7b7;
  --warn:    #f59e0b; --warn-bg:    rgba(245,158,11,0.14); --warn-fg:    #fcd34d;
  --danger:  #f43f5e; --danger-bg:  rgba(244,63,94,0.14);  --danger-fg:  #fda4af;
  --info:    #3b82f6; --info-bg:    rgba(59,130,246,0.14); --info-fg:    #93c5fd;

  --ai: #a855f7; --ai-bg: rgba(168,85,247,0.14); --ai-fg: #d8b4fe;

  --ws-personal: #10b981;
  --ws-family:   #3b82f6;
  --ws-ca:       #f59e0b;
  --ws-admin:    #f43f5e;
  --tier-ai:     #a855f7;
}

/* ============================================================================
   COMPONENTS — Chitragupt's own classes. Everything the screens use that is
   not a Tailwind utility is declared here, and nowhere else: there is no lint,
   and a class that resolves to nothing paints nothing without throwing.
   ============================================================================ */

/* ---- The donut ------------------------------------------------------------
   One shape, twenty-four instances across twenty files: r 42 centred at 50,50,
   a 14px stroke, rotated -90deg so the first arc starts at twelve o'clock. Only
   the arc lengths, their offsets and their colours are data, and those stay in
   the markup. The circumference is 2*PI*42 = 264, which is the gap every
   dasharray is measured against.

   Declared here rather than pasted because twenty copies of six attributes is
   twenty chances for one of them to drift, and nobody would ever see it. */
.donut { transform: rotate(-90deg); }
.donut circle {
  r: 42px; cx: 50px; cy: 50px;
  fill: transparent;
  stroke-width: 14;
}
.donut-track { stroke: var(--line); }

/* The tab bar pinned to the bottom of the phone frame. */
.tabbar { position: absolute; inset-inline: 0; bottom: 0; }

/* A settings / notification list row. Two paddings were in use across the set
   for what is visibly the same row; this is the one settings.html used. */
.row { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.row--top { align-items: flex-start; }
.row .arrow { color: var(--ink-4); }

/* A small status or category pill. */
.chip {
  display: inline-flex; align-items: center; gap: 0.25rem;
  border-radius: var(--radius-sm); padding: 0.125rem 0.5rem;
  font-size: var(--fs-2xs); line-height: 1.4;
}

/* Paid content behind the paywall: legible as a shape, unreadable as a number.
   Blur rather than a grey block, so the screen still shows how much is there. */
.blur-lock { filter: blur(4px); user-select: none; }

/* The one-time code input. Wide tracking so six digits read as six. */
.otp { font-family: var(--font-mono); letter-spacing: 6px; text-align: center; font-size: 22px; }

/* An inline literal — a forwarding address, an invoice id. */
.code {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  color: var(--ink-2); background: var(--muted);
  padding: 2px 6px; border-radius: var(--radius-sm);
}

/* A colour swatch on the components sheet. */
.swatch { display: block; }

/* The FAQ disclosure arrow, and the anchor offset that stops a jumped-to
   question landing under the sticky header. */
.chev { transition: transform 120ms ease; }
details[open] > summary .chev { transform: rotate(90deg); }
.scroll-mt { scroll-margin-top: 5rem; }

/* The payment spinner — slower than a loading spinner, because it is showing
   that a bank round-trip is still running rather than that a list is fetching. */
@keyframes spin-slow { to { transform: rotate(360deg); } }
.spin-slow { animation: spin-slow 1.8s linear infinite; }

/* A document rendered as paper — the parsed-PDF view, which is a light sheet
   in both themes because that is what the source document looks like. */
.paper { background: #fafaf9; color: #18181b; }
