/* ============================================================================
   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 — Aakalan. The only block that differs between products.
   ============================================================================
   RESERVED: green is "you are owed" and rose is "you owe". Nothing else in
   this product may wear either hue — not an avatar, not a group mark, not a
   category puck. A colour that means "Meera" on one side of a row and "you
   owe" on the other means neither, and a balance list is twenty of those rows
   at a glance. `--cat-home` was literally `--pos` once, so the Home puck was
   the money green sitting beside a column of money.

   `--settled` is the third money value and is NEUTRAL, never green. An
   all-square account is the ABSENCE of a direction, not a positive one.

   Indigo is the brand and saffron is the accent — those are role names. The
   Expo app still calls saffron `saffron` (src/global.css, `--color-saffron-*`),
   which is a hue name; the values are identical, only the key differs. See
   surfaces/tokens in the wiki.

   Every value below is the app's, taken from src/global.css. The dark block
   mirrors its `@variant dark` 1:1.
   ============================================================================ */

:root {
  /* rgb(22 24 43) — --ink, the ink this product shadows with. */
  --shadow-tint: 22 24 43;
  --shadow-a1: 0.05;
  --shadow-a2: 0.07;
  --shadow-a3: 0.18;

  /* Brand · indigo — primary actions, active nav, links. */
  --brand-50:  #eef0fe;
  --brand-100: #dcdffd;
  --brand-200: #b8befa;
  --brand-400: #7d80e8;
  --brand-500: #5b5bd6;
  --brand-700: #3a37a8;
  --brand-900: #211f5c;

  /* Accent · saffron — delight, upgrade, Pro. Never money. */
  --accent-50:  #fff7e8;
  --accent-100: #ffecc7;
  --accent-200: #ffd98d;
  --accent-400: #fbbf4a;
  --accent-500: #f5a524;
  --accent-600: #d98410;
  --accent-700: #a3620b;
  /* White on saffron-500 fails contrast, so anything painted on the accent
     takes this instead. `onSaffron` in src/theme/tokens.ts. */
  --accent-ink: #4a2c02;

  /* Text */
  --ink: #16182b;
  --ink-2: #565a75;
  --ink-3: #878ba6;
  --ink-4: #b6b9cc;
  --ink-inverse: #ffffff;

  /* Surfaces. `ui/Screen` is `bg-page`, so the frame's default IS the page
     colour; the handful of white screens (Add, New recurring, the three auth
     screens) carry `.phone--surface`. */
  --page: #f4f5f9;
  --frame: #f4f5f9;
  --surface: #ffffff;
  --muted: #fafafd;
  --sunken: #eeeff5;

  /* Borders */
  --line: #e6e8f0;
  --line-strong: #d3d6e4;

  /* Status. `success` and `danger` carry the same values as `pos` and `neg`
     because a green tick and a red delete are older conventions than this
     palette — but they are separate tokens, so a repalette of money direction
     does not silently repaint every confirmation in the app. */
  --success: #12a150; --success-bg: #eafaf1; --success-fg: #0c7a3d;
  --warn:    #f5a524; --warn-bg:    #fff7e8; --warn-fg:    #a3620b;
  --danger:  #e5484d; --danger-bg:  #ffefef; --danger-fg:  #c62a2f;
  --info:    #0091ff; --info-bg:    #e6f4ff; --info-fg:    #006adc;

  /* Product · MONEY DIRECTION — RESERVED, see the banner above. */
  --pos:     #12a150;   --pos-bg:  #eafaf1;  --pos-fg:  #0c7a3d;
  --neg:     #e5484d;   --neg-bg:  #ffefef;  --neg-fg:  #c62a2f;
  --settled: #878ba6;
  /* The blurred disc on a balance hero. A blurred disc of the base token
     disappears into the card it sits on, so these are lifted — money hues
     doing a money job on the money card, the one place the rule allows it. */
  --pos-glow: #3ddc84;
  --neg-glow: #ff7b7e;

  /* Product · CATEGORY — one hue per expense category, on the icon puck and
     the spending chart. No green and no rose on this wheel. `home` is brown:
     unoccupied here, obviously not a direction, and the one colour that
     already means bricks. Names match src/theme/tokens.ts `category`. */
  --cat-food:   #f97316;  --cat-foodbg:   #fff2e6;
  --cat-travel: #0091ff;  --cat-travelbg: #e6f4ff;
  --cat-stay:   #8b5cf6;  --cat-staybg:   #f3eeff;
  --cat-home:   #92400e;  --cat-homebg:   #f7efe7;
  --cat-fun:    #ec4899;  --cat-funbg:    #ffeef7;
  --cat-shop:   #f5a524;  --cat-shopbg:   #fff7e8;
  --cat-util:   #64748b;  --cat-utilbg:   #eef1f5;

  /* Product · AVATAR — the eight-colour wheel src/lib/avatar-color.ts hashes
     a person's id into, in its order, so `av-3` here is index 2 there. Same
     exclusion: no green, no rose. */
  --avatar-1: #7c3aed;  /* violet */
  --avatar-2: #5b5bd6;  /* indigo — brand-500 */
  --avatar-3: #0891b2;  /* cyan */
  --avatar-4: #f5a524;  /* saffron — accent-500, the one that takes dark ink */
  --avatar-5: #8b5cf6;  /* light violet */
  --avatar-6: #0091ff;  /* blue */
  --avatar-7: #ec4899;  /* pink */
  --avatar-8: #64748b;  /* slate */

  /* Product · SPLASH — the one colour the OS paints before React runs, so it
     is fixed in app.config.ts rather than derived. Light #3A37A8 is brand-700
     and dark #211F5C is brand-900, at their LIGHT values: the theme remap
     lightens both, and a splash that follows it would flash pale lilac. */
  --splash: #3a37a8;
}

/* Dark is a token remap and nothing else — every utility is written against a
   variable, so no markup changes and nothing can be dark in one place and
   light in another. Mirrors the `@variant dark` block in src/global.css 1:1:
   if a value changes there, change it here in the same edit.

   Not an inversion. pos and neg are LIFTED rather than dimmed, because a dark
   green on near-black reads as grey and the reserved rule depends on those two
   being unmistakable. Fill/ink pairs swap roles, which is why brand-50 darkens
   while brand-900 lightens. Solid fills (brand-500, accent-500) keep their
   value: they are painted under white or near-black text that already passes. */
.phone--dark {
  /* Black, not ink: a tinted shadow on a near-black page reads as a smudge. */
  --shadow-tint: 0 0 0;
  --shadow-a1: 0.35;
  --shadow-a2: 0.45;
  --shadow-a3: 0.60;

  --brand-50:  #232566;
  --brand-100: #2c2f7a;
  --brand-200: #3b3f9e;
  --brand-400: #9a9cf0;
  --brand-500: #5b5bd6;
  --brand-700: #b3b5ff;
  --brand-900: #dcdcff;

  --accent-50:  #33260a;
  --accent-100: #45340e;
  --accent-200: #6b5116;
  --accent-400: #fbbf4a;
  --accent-500: #f5a524;
  --accent-600: #ffc766;
  --accent-700: #ffd98d;
  --accent-ink: #4a2c02;

  --ink: #f4f5fb;
  --ink-2: #b9bcd6;
  --ink-3: #8f93b3;
  --ink-4: #6a6e8e;
  --ink-inverse: #16182b;

  --page: #101223;
  --frame: #101223;
  --surface: #1c1e35;
  --muted: #23253d;
  --sunken: #262842;

  --line: #2a2c48;
  --line-strong: #3a3d5e;

  --success: #3ddc84; --success-bg: #10301f; --success-fg: #3ddc84;
  --warn:    #f5a524; --warn-bg:    #33260a; --warn-fg:    #ffd98d;
  --danger:  #ff7b7e; --danger-bg:  #351a1d; --danger-fg:  #ff7b7e;
  --info:    #4db8ff; --info-bg:    #0d2438; --info-fg:    #7cc9ff;

  --pos:     #3ddc84;   --pos-bg:  #10301f;  --pos-fg:  #3ddc84;
  --neg:     #ff7b7e;   --neg-bg:  #351a1d;  --neg-fg:  #ff7b7e;
  --settled: #8f93b3;
  --pos-glow: #3ddc84;
  --neg-glow: #ff7b7e;

  --cat-food:   #ff9448;  --cat-foodbg:   #3a2312;
  --cat-travel: #4db8ff;  --cat-travelbg: #0d2438;
  --cat-stay:   #a98bff;  --cat-staybg:   #291f45;
  --cat-home:   #d99a5b;  --cat-homebg:   #33230f;
  --cat-fun:    #ff6fb0;  --cat-funbg:    #3b1830;
  --cat-shop:   #ffc766;  --cat-shopbg:   #33260a;
  --cat-util:   #9aa6bd;  --cat-utilbg:   #232a36;

  --splash: #211f5c;
}


/* ============================================================================
   COMPONENTS — Aakalan's own. Anything Tailwind can already express is a
   utility in the markup; this file holds only what it cannot: the phone
   chrome the app draws itself, pseudo-elements, and the money primitives that
   also set tabular numerals.
   ============================================================================ */

/* Add and New recurring are `bg-surface`, not `bg-page`, and so are the three
   auth screens — they are forms, and a form on the page colour reads as a
   card that lost its card. */
.phone--surface { background: var(--surface); }

/* ---- App chrome the product draws itself ---------------------------------- */

/* Header on a tab screen: avatar + title on the left, one round action right. */
.appheader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 16px 10px;
  flex: none;
}
.appheader-left { display: flex; align-items: center; gap: 10px; }

/* Header on a pushed screen: back, title, optional action. */
.subheader {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 14px 8px;
  flex: none;
}
.subheader h2 { font-size: 17px; font-weight: 900; letter-spacing: -0.01em; margin: 0; }
.subheader-sub { font-size: var(--fs-2xs); color: var(--ink-3); }

.iconbtn {
  width: 40px; height: 40px;
  border-radius: var(--radius-pill);
  background: var(--sunken);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-2);
  border: none; cursor: pointer;
  text-decoration: none;
  flex-shrink: 0;
}
.iconbtn:active { opacity: 0.7; }
.iconbtn--on-cover { background: rgba(255,255,255,0.2); color: #fff; }
.iconbtn-sm {
  width: 32px; height: 32px;
  border-radius: 10px;
  background: var(--sunken);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-2);
  border: 1px solid var(--line);
  cursor: pointer; text-decoration: none;
  flex-shrink: 0;
}
.iconbtn-sm.is-pos   { background: var(--pos-bg);   color: var(--pos-fg);   border-color: transparent; }
.iconbtn-sm.is-brand { background: var(--brand-50); color: var(--brand-700); border-color: transparent; }

/* ---- Bottom tab bar — two tabs and the raised centre Add ------------------
   It was five tabs. Friends merged into Balances (the same balances, cut by
   person), Activity and Insights merged into Spending (the same month,
   summarised and itemised), and Account went back to the header avatar it was
   always reachable from. A tab is for a place you return to; a push is for a
   place you go, do a thing and leave. */
.tabbar {
  background: color-mix(in srgb, var(--surface) 98%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}
.tabbar-row { display: flex; }
.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  color: var(--ink-4);
  text-decoration: none;
  font-size: var(--fs-2xs);
  font-weight: 600;
}
.tab.is-active { color: var(--brand-700); font-weight: 800; }
.tab-icon { width: 22px; height: 22px; }

/* The raise and the pull-back are one constraint, not two taste calls.
   translateY does not change layout, so the circle still occupies 54px in the
   column; -26px is what clears the label underneath it. At -15px the circle's
   painted bottom overlapped the word "Add". */
.tab-fab {
  flex: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 2px 0 0;
  position: relative;
  text-decoration: none;
}
.tab-fab-inner {
  width: 54px; height: 54px;
  border-radius: var(--radius-pill);
  background: var(--brand-500);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 20px -4px rgb(91 91 214 / 0.55), 0 0 0 4px var(--frame);
  transform: translateY(-26px);
}
.tab-fab-label {
  position: absolute; bottom: 9px;
  font-size: var(--fs-2xs); font-weight: 700;
  color: var(--brand-700);
}
.tabbar-handle { display: flex; align-items: center; justify-content: center; padding: 3px 0 6px; }
.tabbar-handle-bar { height: 4px; width: 128px; border-radius: var(--radius-pill); background: var(--line-strong); }

/* A footer outside the scroll container: Save can never be scrolled away, and
   it is within reach of a thumb, which the top-right corner of a 6.7" phone
   is not. */
.sticky-bottom {
  background: color-mix(in srgb, var(--surface) 98%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  padding: 10px 16px 12px;
}

/* ---- Money primitives — the core of the product --------------------------
   Amounts are always tabular and always signed by COLOUR rather than by a
   minus sign, so a column of them scans without reading. */
.amt {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-weight: 800;
  letter-spacing: -0.01em;
}
.amt-pos     { color: var(--pos-fg); }
.amt-neg     { color: var(--neg-fg); }
.amt-neutral { color: var(--ink-3); }
.amt-xl { font-size: 34px; font-weight: 900; line-height: 1.05; }
.amt-lg { font-size: 22px; font-weight: 900; line-height: 1.1; }
.amt-md { font-size: 16px; }
.amt-sm { font-size: 13px; }

/* The balance hero. The CARD carries the direction, not only the eyebrow:
   an indigo hero made the biggest number on the screen the one amount whose
   colour said nothing. The figure stays white because it sits on a saturated
   ground — a green number on a green card is unreadable, so the gradient
   carries the meaning and the words agree with it.

   Settled is NOT green. Green is spoken for, and a green flood reads as "you
   are owed" at a glance, which is the one thing all-square does not mean. */
.balance-hero {
  border-radius: var(--radius-2xl);
  padding: 12px 14px;
  color: #fff;
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
.balance-hero--owed {
  background:
    radial-gradient(120% 90% at 100% 0%, rgb(61 220 132 / 0.45) 0%, transparent 58%),
    linear-gradient(140deg, #06301a 0%, var(--pos-fg) 55%, var(--pos) 100%);
}
.balance-hero--owe {
  background:
    radial-gradient(120% 90% at 100% 0%, rgb(255 138 140 / 0.42) 0%, transparent 58%),
    linear-gradient(140deg, #45100f 0%, var(--neg-fg) 55%, var(--neg) 100%);
}
.balance-hero--settled {
  background:
    radial-gradient(120% 90% at 100% 0%, rgb(255 255 255 / 0.30) 0%, transparent 58%),
    linear-gradient(140deg, var(--ink) 0%, var(--ink-2) 55%, var(--settled) 100%);
}
.balance-hero-eyebrow {
  font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: 0.2em; text-transform: uppercase;
  opacity: 0.8;
}
.balance-hero-amount {
  font-size: 26px; font-weight: 900; line-height: 1.05;
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.balance-hero-sub { font-size: 12px; opacity: 0.85; margin-top: 3px; }
.orb { position: absolute; border-radius: var(--radius-pill); filter: blur(28px); opacity: 0.45; pointer-events: none; }

/* The group cover — a gradient keyed to the group's derived category, with
   the header sitting on it and the balance card overlapping its bottom edge. */
.cover { position: relative; flex: none; padding: 4px 16px 60px; color: #fff; }
.cover-travel { background: linear-gradient(135deg, #0091ff, #7d80e8); }
.cover-home   { background: linear-gradient(135deg, #12a150, #7dd3a0); }
.cover-food   { background: linear-gradient(135deg, #f5a524, #f97316); }
.cover-fun    { background: linear-gradient(135deg, #8b5cf6, #ec4899); }
.cover-stay   { background: linear-gradient(135deg, #8b5cf6, #ec4899); }
.cover-shop   { background: linear-gradient(135deg, #f5a524, #f97316); }
.cover-util   { background: linear-gradient(135deg, #b6b9cc, #d3d6e4); }

/* ---- Rows ---------------------------------------------------------------- */

/* Person or group against an amount. The workhorse: Balances, group detail
   and the settle screens all render this same shape, so a balance looks the
   same wherever you meet it. */
.bal-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 13px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
}
.bal-row:last-child { border-bottom: none; }
/* The pressable part of the row stops short of the trailing buttons. An <a>
   inside an <a> is not a thing HTML has — the parser closes the outer one — and
   the app hits the same wall for the same reason: a Pressable inside a
   Pressable swallows the inner one. So the row is a container, and its main
   target and its actions are siblings. */
.bal-row-main {
  display: flex; align-items: center; gap: 12px;
  flex: 1; min-width: 0;
  text-decoration: none; color: inherit;
}
.bal-row-body { flex: 1; min-width: 0; }
.bal-row-name { font-size: 14.5px; font-weight: 700; }
/* One line, always. These rows carry two trailing buttons, so a subtitle that
   wraps turns a 56px row into a 90px one and the column stops scanning. */
.bal-row-sub {
  font-size: var(--fs-xs); color: var(--ink-3); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bal-row-right { text-align: right; flex-shrink: 0; }
.bal-row-right .lbl {
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.bal-row-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* A device contact who is not a person in the ledger yet. Deliberately
   lighter than .bal-row: a candidate is not a relationship, and it must not
   read as somebody you already split with. */
.contact-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 13px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
.contact-row:last-child { border-bottom: none; }
.contact-row-body { flex: 1; min-width: 0; }
.contact-row-name { font-size: 14px; font-weight: 700; color: var(--ink); }
.contact-row-sub {
  font-size: var(--fs-xs); color: var(--ink-3); margin-top: 1px;
  font-variant-numeric: tabular-nums;
}

/* An expense in a list. */
.exp-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 13px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
.exp-row:last-child { border-bottom: none; }
.exp-body { flex: 1; min-width: 0; }
.exp-title { font-size: 14px; font-weight: 700; }
.exp-sub { font-size: var(--fs-xs); color: var(--ink-3); margin-top: 2px; }
.exp-right { text-align: right; flex-shrink: 0; }

/* A settings or navigation row. Also used on <button>, which defaults to
   shrink-to-fit and centred text — both pinned here rather than at the call
   sites. */
.row {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 11px 13px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  text-align: left; text-decoration: none; color: inherit;
}
.row:last-child { border-bottom: none; }
.row-icon {
  width: 36px; height: 36px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.row-body { flex: 1; min-width: 0; }
.row-title { font-size: 14px; font-weight: 700; }
.row-sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }

/* A day heading inside the spending ledger. */
.list-date {
  padding: 10px 16px 6px;
  font-size: 10.5px; font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3);
  background: var(--page);
}

/* An edit or a deletion in the log. An expense can be removed; the record
   that somebody removed it cannot. So these render permanently, struck
   through and muted, never hidden. */
.audit {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 9px 13px;
  background: var(--muted);
  border-bottom: 1px solid var(--line);
  font-size: 12px; color: var(--ink-3); line-height: 1.45;
}
.audit:last-child { border-bottom: none; }
.audit .strike { text-decoration: line-through; }

/* ---- Category puck ------------------------------------------------------- */
.cat {
  width: 40px; height: 40px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.cat-sm { width: 32px; height: 32px; border-radius: 10px; }
.cat-lg { width: 52px; height: 52px; border-radius: 16px; }
.cat-food   { background: var(--cat-foodbg);   color: var(--cat-food); }
.cat-travel { background: var(--cat-travelbg); color: var(--cat-travel); }
.cat-stay   { background: var(--cat-staybg);   color: var(--cat-stay); }
.cat-home   { background: var(--cat-homebg);   color: var(--cat-home); }
.cat-fun    { background: var(--cat-funbg);    color: var(--cat-fun); }
.cat-shop   { background: var(--cat-shopbg);   color: var(--cat-shop); }
.cat-util   { background: var(--cat-utilbg);   color: var(--cat-util); }

/* ---- Avatars ------------------------------------------------------------- */
.avatar {
  width: 36px; height: 36px;
  border-radius: var(--radius-pill);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 14px; color: #fff;
  flex-shrink: 0;
}
.avatar-sm { width: 28px; height: 28px; font-size: 11px; }
.avatar-lg { width: 56px; height: 56px; font-size: 21px; }
.avatar-xl { width: 76px; height: 76px; font-size: 28px; }
.av-1 { background: var(--avatar-1); }
.av-2 { background: var(--avatar-2); }
.av-3 { background: var(--avatar-3); }
.av-4 { background: var(--avatar-4); color: var(--accent-ink); }
.av-5 { background: var(--avatar-5); }
.av-6 { background: var(--avatar-6); }
.av-7 { background: var(--avatar-7); }
.av-8 { background: var(--avatar-8); }
.av-ghost { background: var(--sunken); color: var(--ink-3); border: 1px dashed var(--line-strong); }
.av-stack { display: flex; }
.av-stack .avatar { box-shadow: 0 0 0 2px var(--surface); margin-left: -8px; }
.av-stack .avatar:first-child { margin-left: 0; }

/* ---- Buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 8px;
  padding: 11px 18px;
  border-radius: var(--radius-2xl);
  font-weight: 800; font-size: 14.5px; letter-spacing: 0.01em;
  border: none; cursor: pointer; text-decoration: none;
  width: 100%;
}
.btn:active { opacity: 0.85; }
.btn-primary { background: var(--brand-500); color: #fff; }
.btn-accent  { background: var(--accent-500); color: var(--accent-ink); }
.btn-pos     { background: var(--pos); color: #fff; }
.btn-secondary {
  background: var(--surface);
  color: var(--brand-700);
  border: 1px solid var(--line-strong);
}
.btn-ghost { background: transparent; color: var(--ink-2); font-weight: 700; }
.btn-danger { background: var(--surface); color: var(--neg-fg); border: 1px solid var(--line-strong); }
/* Disabled is a sunken slab with ink-4 lettering, never a faded primary: a
   translucent brand button still reads as the thing you are meant to press. */
.btn-off { background: var(--sunken); color: var(--ink-4); }
.btn-sm { padding: 7px 12px; font-size: 13px; border-radius: var(--radius-lg); width: auto; }

/* ---- Cards, labels, chips ------------------------------------------------ */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-2xl);
  padding: 12px 13px;
}
.card-flat { padding: 0; overflow: hidden; }
.card--muted  { background: var(--muted); }
.card--brand  { background: var(--brand-50); border-color: var(--brand-100); }
.card--accent { background: var(--accent-50); border-color: var(--accent-100); }
.card--pos    { background: var(--pos-bg); border-color: color-mix(in srgb, var(--pos) 25%, transparent); }
.card--neg    { background: var(--neg-bg); border-color: color-mix(in srgb, var(--neg) 25%, transparent); }
.card--warn   { background: var(--warn-bg); border-color: var(--warn); }
.card--dashed { background: var(--muted); border: 1px dashed var(--line-strong); }


.chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px;
  border-radius: 6px;
  font-size: 10.5px; line-height: 1.3; font-weight: 700;
  text-decoration: none;
}
.chip-brand   { background: var(--brand-50);  color: var(--brand-700); }
.chip-accent  { background: var(--accent-100); color: var(--accent-700); }
.chip-success { background: var(--success-bg); color: var(--success-fg); }
.chip-warn    { background: var(--warn-bg);    color: var(--warn-fg); }
.chip-info    { background: var(--info-bg);    color: var(--info-fg); }
.chip-neutral { background: var(--sunken);     color: var(--ink-2); }

/* A selectable tile: payer chips, currency chips, the Cash / Bank pair. */
.chip-lg {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 11px 13px;
  border-radius: var(--radius-2xl);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  font-weight: 700; text-align: left;
  cursor: pointer; text-decoration: none; color: inherit;
}
.chip-lg.is-selected {
  border-color: var(--brand-500);
  background: var(--brand-50);
  color: var(--brand-900);
}

/* ---- Segmented control ---------------------------------------------------
   inline-flex, not the default block: lucide swaps <i> for an <svg>, and an
   svg inside a plain button drops onto its own line — which turned the
   People/Groups switch into two stacked rows inside a pill sized for one. */
.segmented {
  display: flex; width: 100%;
  background: var(--sunken);
  border-radius: var(--radius-2xl);
  padding: 4px;
  gap: 2px;
}
.segmented button {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: var(--radius-lg);
  font-size: 12px; font-weight: 700; line-height: 1.2;
  color: var(--ink-3);
  background: transparent; border: none; cursor: pointer;
  white-space: nowrap;
}
.segmented button.is-active {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}
/* The Balances cut is a pill rather than a slab — it switches WHAT you are
   looking at, where the Spending filter narrows the same thing. */
.segmented--pill { border-radius: var(--radius-pill); }
.segmented--pill button { border-radius: var(--radius-pill); }
.segmented--pill button.is-active { color: var(--brand-700); }

/* ---- Fields -------------------------------------------------------------- */
.field-label {
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px; display: block;
}
.field-help { font-size: 11px; color: var(--ink-3); margin-top: 6px; }
.field-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-2xl);
  background: var(--surface);
}
.field-row.is-bad { border-color: var(--neg); }
.field-input {
  flex: 1; min-width: 0;
  font-size: 15px; font-weight: 700;
  color: var(--ink);
}
.field-input.is-placeholder { color: var(--ink-4); font-weight: 400; }

/* Search. The magnifier sits inside the field rather than beside it: the list
   filters as you type, so there is nothing to submit. */
.searchfield {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px 9px 34px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-2xl);
  background: var(--surface);
}
.searchfield-icon {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  color: var(--ink-3); pointer-events: none;
  display: flex;
}
/* Sync, inside the field. The address book is read from here and nowhere
   else, and the question it answers is the one being typed. */
.searchfield-sync {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 10px; border-radius: var(--radius-sm);
  background: var(--brand-50); color: var(--brand-700);
  font-size: 11px; font-weight: 800;
  flex-shrink: 0;
}

/* The six OTP boxes. One hidden input behind them in the app — real keyboard
   behaviour and SMS autofill, without six fields fighting over focus. */
.otp { display: flex; gap: 8px; justify-content: center; }
.otp span {
  width: 46px; height: 56px;
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  display: flex; align-items: center; justify-content: center;
  font-size: 24px; font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.otp span.is-active { border-color: var(--brand-500); background: var(--brand-50); }
.otp span.is-bad { border-color: var(--neg); }

.toggle {
  width: 44px; height: 26px; border-radius: var(--radius-pill);
  background: var(--line-strong);
  position: relative; flex-shrink: 0; border: none; cursor: pointer;
}
.toggle::after {
  content: ""; position: absolute;
  top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: var(--radius-pill); background: #fff;
  box-shadow: var(--shadow-sm);
}
.toggle.is-on { background: var(--brand-500); }
.toggle.is-on::after { left: 21px; }

.check {
  width: 22px; height: 22px; border-radius: 7px;
  border: 1.5px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; background: var(--surface);
}
.check.is-on { background: var(--brand-500); border-color: var(--brand-500); color: #fff; }
.radio { border-radius: var(--radius-pill); }

/* ---- Sheets -------------------------------------------------------------- */
.backdrop { position: absolute; inset: 0; background: rgb(22 24 43 / 0.5); }
/* Named `bottom-sheet`, not `sheet`: screenshots.html uses `.sheet` for its
   own grid and that file is identical in every product.
   bg-frame, not bg-surface: in the dark palette a translucent tint over a
   backdrop lets the screen show straight through the card. */
.bottom-sheet {
  position: absolute;
  inset-inline: 0; bottom: 0;
  background: var(--frame);
  border-top-left-radius: var(--radius-3xl);
  border-top-right-radius: var(--radius-3xl);
  padding: 8px 16px 16px;
  box-shadow: var(--shadow-lg);
}
.bottom-sheet-grip {
  width: 40px; height: 4px; border-radius: var(--radius-pill);
  background: var(--line-strong);
  margin: 0 auto 12px;
}
/* A picker sheet holds every group and every person, so it caps its height
   and scrolls inside itself — the grip, the title and the search field stay
   put. A sheet taller than the phone is how a simpler picker becomes a worse
   one. */
.bottom-sheet--tall { max-height: 84%; display: flex; flex-direction: column; }
.bottom-sheet--tall .bottom-sheet-scroll { overflow-y: auto; margin: 0 -16px; padding: 0 16px 2px; }

/* ---- Picker row — one control for "who is this with?" --------------------
   It replaced a horizontal chip scroller that worked at three items and broke
   at seven: everything past the third was behind a sideways swipe with no
   affordance, it could not be searched, and it put four unlike things —
   yourself, a person, a group, and an action that CREATES a group — on one
   axis as if they were the same kind of answer. A row that names the current
   answer costs the same zero taps when the default is right, and it works
   identically at nine hundred contacts. */
.picker {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-2xl);
  background: var(--surface);
  text-align: left; cursor: pointer; text-decoration: none; color: inherit;
}
.picker-body { flex: 1; min-width: 0; }
.picker-label {
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-3);
}
.picker-value {
  font-size: 15px; font-weight: 700; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.picker-sub { font-size: 11px; color: var(--ink-3); margin-top: 1px; }

/* ---- Split editor -------------------------------------------------------- */
.split-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.split-row:last-child { border-bottom: none; }
.split-box {
  display: flex; align-items: center; gap: 4px;
  border-radius: var(--radius);
  background: var(--sunken);
  padding: 6px 10px;
  font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 13px;
}
/* Never absent and never neutral: either the bill adds up or this line says
   by how much it does not. A split editor you can close with money
   unassigned produces a ledger that cannot be settled. */
.reconcile {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px;
  padding: 8px 12px;
  border-radius: var(--radius-lg);
  font-size: 12px; font-weight: 700;
}
.reconcile.is-good { background: var(--pos-bg); color: var(--pos-fg); }
.reconcile.is-bad  { background: var(--neg-bg); color: var(--neg-fg); }

/* The big amount entry on Add. */
.amount-input { display: flex; align-items: center; gap: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.amount-input .cur { font-size: 28px; font-weight: 800; color: var(--ink-3); }
.amount-input .val {
  font-size: 34px; font-weight: 900;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.amount-input .val.is-placeholder { color: var(--ink-4); }
.amount-input .caret {
  width: 3px; height: 36px;
  background: var(--brand-500);
  border-radius: 2px;
}

/* ---- Charts -------------------------------------------------------------- */
/* A labelled row, not a legend you have to cross-reference against a donut. */
.catbar { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
.catbar-track { flex: 1; height: 10px; border-radius: var(--radius-pill); background: var(--sunken); overflow: hidden; }
.catbar-fill { height: 100%; border-radius: var(--radius-pill); }
.catbar-val {
  width: 78px; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 800; font-size: 13px;
}
.bars { display: flex; align-items: flex-end; gap: 8px; height: 96px; }
.bars .bar { flex: 1; border-radius: 6px 6px 2px 2px; background: var(--brand-200); }
.bars .bar.is-active { background: var(--brand-500); }
.bars-axis { display: flex; gap: 8px; margin-top: 6px; }
.bars-axis span { flex: 1; text-align: center; font-size: var(--fs-2xs); font-weight: 700; color: var(--ink-3); }
/* Own spend against your share of splits, as one bar. Two segments, never a
   pie — a pie of two slices is a bar with extra steps. */
.split-bar { display: flex; height: 8px; border-radius: var(--radius-pill); overflow: hidden; background: var(--sunken); }
.split-bar > i { display: block; height: 100%; }
.split-bar .seg-own   { background: var(--brand-500); }
.split-bar .seg-share { background: var(--accent-400); }

/* ---- Empty state --------------------------------------------------------
   Exactly one next action; two options and people pick neither. "Nothing yet"
   and "nothing left" are different states: the second is an achievement, gets
   the green treatment, and never carries a CTA. */
.empty {
  text-align: center;
  padding: 24px 16px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-2xl);
  background: var(--muted);
}
.empty--achievement {
  border-style: solid;
  border-color: color-mix(in srgb, var(--pos) 30%, transparent);
  background: var(--pos-bg);
  color: var(--pos-fg);
}
.empty-illus {
  width: 72px; height: 72px;
  border-radius: var(--radius-pill);
  margin: 0 auto 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-50);
  color: var(--brand-500);
}
.empty--neutral .empty-illus { background: var(--sunken); color: var(--ink-3); }
.empty--achievement .empty-illus { background: var(--pos-bg); color: var(--pos-fg); }
.empty-title { font-size: 15px; font-weight: 800; }
.empty-sub { font-size: 12.5px; color: var(--ink-2); margin-top: 4px; line-height: 1.5; }
.empty--achievement .empty-title,
.empty--achievement .empty-sub { color: var(--pos-fg); }

/* ---- Receipt stand-in ---------------------------------------------------
   A wireframe is about where the image sits and what it costs, not what is in
   it. */
.shot {
  background:
    repeating-linear-gradient(0deg, var(--line) 0 2px, transparent 2px 9px),
    linear-gradient(160deg, var(--muted), var(--sunken));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

/* ---- Locked behind Pro ---------------------------------------------------
   Never on adding, settling or exporting. Only on insights depth and
   multi-currency, which is the whole of the paywall's reach into the UI. */
.locked { position: relative; }
.locked > .locked-veil {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  flex-direction: column; gap: 8px; text-align: center;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface) 55%, transparent) 0%,
    color-mix(in srgb, var(--surface) 96%, transparent) 55%);
  border-radius: var(--radius-lg);
  padding: 0 20px;
}

/* ---- Skeleton ------------------------------------------------------------ */
@keyframes shimmer { from { background-position: -200px 0; } to { background-position: 200px 0; } }
.skel {
  background: linear-gradient(90deg, var(--sunken) 0%, var(--muted) 50%, var(--sunken) 100%);
  background-size: 400px 100%;
  animation: shimmer 1.4s linear infinite;
  border-radius: 8px;
}
.skel-line { height: 12px; border-radius: 6px; }

/* ---- Odds and ends ------------------------------------------------------- */
/* A personal expense is a first-class expense, not a degenerate group of one —
   so it gets a quiet label, never an empty avatar stack. */
.solo-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3);
}
.settle-arrow {
  flex: 1; height: 2px;
  background: repeating-linear-gradient(90deg, var(--line-strong) 0 5px, transparent 5px 10px);
  position: relative;
}
.settle-arrow::after {
  content: "";
  position: absolute; right: -1px; top: -3px;
  border-left: 7px solid var(--line-strong);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}
