/* ============================================================
   Household design tokens. The single source of truth for every
   colour, type step, spacing step, radius and motion value used
   by any surface in this household: Spoon, Compass, the hub
   webpage, dashboards, artifacts.

   Spec: docs/superpowers/specs/2026-09-10-household-app-conventions-design.md
   Reasoning: brand/data/household-look.md

   THIS FILE IS COPIED INTO THE APP REPOS by
   brand/tools/sync-household-tokens.py, where it lands as
   tokens.css. Never hand-edit a downstream copy: edit this file
   and re-sync. `sync-household-tokens.py --check` reports drift
   without writing.

   Values are Spoon's, lifted unchanged. The STRUCTURE is not
   Spoon's: see "Dark promotion" below for the one deliberate
   restructure and why it removes a bug class.
   ============================================================ */

/* ------------------------------------------------------------
   TYPE

   Modular scale, ratio 1.125, anchored on 0.82rem. The anchor is
   not arbitrary: 0.82 was already the most common size in Spoon
   (10 declarations), so anchoring there is what kept the
   workhorse text still through the migration.

   Families: Rubik for everything, ui-monospace for figures.
   Weights 400 / 500 / 600 / 700, with 600 doing most of the work.
   Headings are 600 with -0.01em tracking.

   NOT ON THIS RAMP: chart text inside SVG. --fs-micro resolves to
   10.4px, which is too large for an axis label. Chart text is
   sized deliberately in px (Spoon uses 9px axis, 8px promo
   glyph). That is an exception, and it is the only one.
   ------------------------------------------------------------ */
:root {
  /* Tells the browser which way the page leans, so scrollbars, form controls
     and the Android gesture bar follow the theme instead of staying light.
     Lives here rather than per-surface because every surface wants it and a
     CSS-only page has no other way to say it. Spoon also sets it from JS,
     to the same value. */
  color-scheme: light;

  --font-ui: "Rubik", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;

  --fs-micro: 0.65rem;     /* nav labels, badges, carets */
  --fs-meta: 0.73rem;      /* card meta, tags, chips, secondary labels */
  --fs-ui: 0.82rem;        /* THE WORKHORSE: pills, buttons, list rows, banners, hints */
  --fs-body: 0.92rem;      /* inputs, card titles, toggles */
  --fs-lede: 1.04rem;      /* section titles, intro text */
  --fs-title: 1.17rem;     /* sheet section headings, metric values */
  --fs-title-lg: 1.32rem;  /* sheet titles */
  --fs-display: 1.48rem;   /* app header */

  --lh-base: 1.45;
  --tracking-heading: -0.01em;
  --tracking-label: 0.02em;

  /* weights: three, and only three */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-strong: 600;

  --lh-title: 1.25;         /* titles and sections; text uses --lh-base */

  /* PAGE TIER ONLY. The eight steps above are a phone app's ramp and stop at
     --fs-display, which is an app header: 1.48rem is about 24px. A dashboard
     headline needs more, and the Spend Dashboard proved it on first contact
     (its h1 was clamp(2.4rem, 5vw, 3.4rem)). These continue the same 1.125
     ratio upward rather than inventing a second scale beside it, so the whole
     ramp is still one geometric series.

     Consequence worth knowing: type IS tiered after all. The spec said it was
     not. A phone stops at --fs-display; a page may go past it. */
  --fs-xl: 1.67rem;
  --fs-2xl: 1.87rem;
  --fs-3xl: 2.11rem;
  --fs-4xl: 2.37rem;
  --fs-page-title: clamp(2.37rem, 5vw, 3.38rem);
}

/* ------------------------------------------------------------
   ELEVATION

   Spoon hardcodes its shadows as literal rgba in four places, which is
   why there was no token for this until a second surface needed one.
   Two levels: resting cards, and things that float above the page.
   ------------------------------------------------------------ */
:root {
  --shadow-card: 0 1px 2px rgba(20, 20, 20, 0.06), 0 6px 20px rgba(20, 20, 20, 0.05);
  --shadow-raised: 0 6px 20px rgba(20, 20, 20, 0.18);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.30), 0 6px 24px rgba(0, 0, 0, 0.35);
    --shadow-raised: 0 10px 28px rgba(0, 0, 0, 0.45);
  }
}

/* ------------------------------------------------------------
   SPACING

   Strict 4pt grid. Both fixed dimensions below are already on it,
   so neither moved: --nav-h is 4x16, --touch-min is 4x11.

   DENSITY TIERS are a convention, not a selector. Nothing here
   enforces them, because a surface that needs to break one
   should do so visibly rather than by defeating a rule:

     phone  (Spoon, Compass)      steps 1 to 4 do the work,
                                  --sp-5 is a section break
     page   (hub, dashboards,     steps 3 to 7
             artifacts)

   Where the nearest step is a tie, ROLE decides, not arithmetic:
     icon-to-label pair (dot beside a store name)  --sp-1
     inline control row (chips, pills, tags)       --sp-2
     grid gutter (card grids, metric grids)        --sp-3
     section spacing                               --sp-4 / --sp-5
   ------------------------------------------------------------ */
:root {
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;

  --nav-h: 64px;      /* bottom tab bar. Scroll containers reserve
                         calc(var(--nav-h) + env(safe-area-inset-bottom, 0px)) */
  --touch-min: 44px;  /* minimum tap target. Secondary controls may go to 40px */
}

/* ------------------------------------------------------------
   RADIUS
   ------------------------------------------------------------ */
:root {
  --r-pill: 999px;              /* choosing: pills, chips, tabs, badges, filters */
  --r-card: 12px;               /* containers: cards, panels, menus */
  --r-control: 10px;            /* doing and entering: action buttons, text fields */
  --r-sheet: 18px 18px 0 0;     /* bottom sheets */
  --r-round: 50%;               /* one glyph or colour: icon buttons, avatars, dots, swatches */
  --r-square: 0;                /* bars and stacked bars, ALWAYS. See charts below */
}

/* ------------------------------------------------------------
   MOTION

   120 to 280ms. Colour and opacity use --ease-standard; sheet
   transforms and drag have their own curves.

   EVERY animated rule needs a prefers-reduced-motion: reduce
   counterpart. This is not optional and it is not decorative:
   Spoon carries four of them.

   Feedback is a LABEL SWAP held for --confirm-ms, not a toast
   ("Add to the shopping list" becomes "Added to the shopping
   list"). Status uses a banner, with a tappable variant.
   ------------------------------------------------------------ */
:root {
  --motion-fast: 120ms;
  --motion-base: 180ms;
  --motion-slow: 280ms;

  --ease-standard: ease;
  --ease-sheet: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-drag: cubic-bezier(0.3, 0.7, 0.3, 1);

  --confirm-ms: 1500ms;  /* JS keeps its own numeric constant; keep them equal */
}

/* ------------------------------------------------------------
   FOCUS

   NOTE: Spoon does not currently do this. It has `outline: none`
   in two places and no focus style anywhere else, so it is
   non-conformant against this rule as of 2026-09-19. Fixing that
   belongs to the Spoon CSS migration; the household standard is
   stated here regardless of who currently meets it.
   ------------------------------------------------------------ */
:root {
  --focus-ring: 2px solid var(--accent);
  --focus-offset: 2px;
}

/* ------------------------------------------------------------
   SEMANTIC COLOURS

   Outside the palette system on purpose, so a colour keeps its
   meaning under every palette. A palette's accent is decoration;
   "this got better" is information.

   The fixed-roster rule goes with them: an entity outside a known
   colour roster renders greyscale, never an invented colour.
   ------------------------------------------------------------ */
:root {
  --good-bg: #dff3ea;
  --good-tx: #1c6b4d;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --good-bg: #143f30;
    --good-tx: #7fd9b6;
  }
}

/* ------------------------------------------------------------
   FREE-TIME (Compass only)

   Compass's Free-time toggle (Clear/Open day states, the toggle
   button itself) is its own thing, not the household's "this got
   better" semantic above -- a free day isn't an outcome, so it
   gets its own pair rather than borrowing --good-bg/--good-tx.
   Sage, chosen against Compass's own Cobalt and Chartreuse
   accents. Same shape as the semantic pair: -bg is the fill
   (Clear), -tx is the border/text (Open's border, and Clear's
   text on top of its own fill).
   ------------------------------------------------------------ */
:root {
  --free-bg: #e4ecdd;
  --free-tx: #4c6a41;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --free-bg: #293422;
    --free-tx: #a8cf94;
  }
}

/* ------------------------------------------------------------
   THIRD TEXT TIER

   Spoon needs two text weights (--text, --text-dim) and has never wanted a
   third. A dense page does: the Spend Dashboard uses three, with 22 uses of
   the faintest, and collapsing them would flatten a hierarchy that is doing
   real work in a table.

   Derived rather than hand-authored, so it exists for all three palettes, with no
   without more hex values to keep in step. Order-
   independent: custom property references resolve after the cascade, so this
   picks up whichever palette's --text-dim and --bg won.
   ------------------------------------------------------------ */
:root {
  --text-faint: color-mix(in srgb, var(--text-dim) 70%, var(--bg));
}

/* ------------------------------------------------------------
   PALETTES

   Three palettes, Cobalt, Amber and Chartreuse, selected by
   data-palette on <html>; absent means Cobalt.

   THE NEUTRALS ARE SHARED. Background, surfaces, text, borders
   and rules are one set, written once below, and no palette
   touches them. A palette is its accent and nothing else:

     --accent        fills: selected chips, active bubble, buttons
     --accent-text   text on top of an accent fill
     --accent-soft   pale accent tint
     --accent-ink    the accent as TEXT, used only for the active tab
                     name. It is the accent moved in lightness until it
                     reaches 4.5:1 on --surface, per mode.

   The accent and its text colour are the same in light and dark;
   --accent-soft and --accent-ink are not, so those two are parked
   as --dk-* and promoted.

   TONAL LOGIC
     LIGHT  panel lightest, then header and footer, then
            background, then filters and search darkest
     DARK   that order inverted

   PANEL. Chart cards, price cards and recipe cards use --panel:
   pure white in light, pure black in dark, in every palette.

   ACCENT AS TEXT. No accent colour is ever a font colour, except
   --accent-ink on the active tab name. Everywhere else a state is
   carried by a fill (a pill's fill is not a font colour) or by
   --text / --text-dim.

   LIGHT / DARK FOLLOWS THE OS, ALWAYS. There is no in-app
   override. [data-theme] is an internal read mechanism only.

   Every token has its LIGHT definition on a bare selector first.
   Never declare a colour only inside a media or [data-theme]
   block. The promotion rules at the bottom MUST stay last: they
   tie with the palette selectors on specificity, so source order
   is what makes them win.
   ------------------------------------------------------------ */

/* ---- shared neutrals ---- */
:root {
  --bg: #f5f6fa;
  --surface: #ebedf3;
  --surface-2: #dde0ea;
  --card: #ffffff;
  --panel: #ffffff;
  --text: #1e1f24;
  --text-dim: #5b606c;
  --border: #d5d8e3;
  --rule: #dde0ea;
  --rule-strong: #bcc0cd;

  --dk-bg: #16181d;
  --dk-surface: #1c1e24;
  --dk-surface-2: #363b44;
  --dk-card: #0f1013;
  --dk-panel: #000000;
  --dk-text: #e7eaf2;
  --dk-text-dim: #a6acb8;
  --dk-border: #333944;
  --dk-rule: #2b2e36;
  --dk-rule-strong: #454b57;
}

/* ---- 01 Cobalt (default) ---- */
:root {
  --accent: #1a73e8;
  --accent-text: #ffffff;
  --accent-soft: #d7e6fb;
  --accent-ink: #1567d3;

  --dk-accent-soft: #17365f;
  --dk-accent-ink: #3a87eb;
}

/* ---- 02 Amber ---- */
:root[data-palette="amber"] {
  --accent: #f4b400;
  --accent-text: #201d0a;
  --accent-soft: #fdeecb;
  --accent-ink: #896500;

  --dk-accent-soft: #4a3d1a;
  --dk-accent-ink: #f4b400;
}

/* ---- 03 Chartreuse ---- */
:root[data-palette="chartreuse"] {
  --accent: #d7d63a;
  --accent-text: #1b1b06;
  --accent-soft: #eeeeba;
  --accent-ink: #6c6c16;

  --dk-accent-soft: #3c3c1e;
  --dk-accent-ink: #d7d63a;
}

/* ------------------------------------------------------------
   DARK PROMOTION - keep last, see the note above.
   Palette-independent: the dark set is parked as --dk-* and these
   promote it, so the values live in exactly one place. The accent
   and --accent-text are not promoted: they do not change with mode.
   ------------------------------------------------------------ */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--dk-bg);
  --surface: var(--dk-surface);
  --surface-2: var(--dk-surface-2);
  --card: var(--dk-card);
  --panel: var(--dk-panel);
  --text: var(--dk-text);
  --text-dim: var(--dk-text-dim);
  --accent-soft: var(--dk-accent-soft);
  --accent-ink: var(--dk-accent-ink);
  --border: var(--dk-border);
  --rule: var(--dk-rule);
  --rule-strong: var(--dk-rule-strong);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) {
    color-scheme: dark;
    --bg: var(--dk-bg);
    --surface: var(--dk-surface);
    --surface-2: var(--dk-surface-2);
    --card: var(--dk-card);
    --panel: var(--dk-panel);
    --text: var(--dk-text);
    --text-dim: var(--dk-text-dim);
    --accent-soft: var(--dk-accent-soft);
    --accent-ink: var(--dk-accent-ink);
    --border: var(--dk-border);
    --rule: var(--dk-rule);
    --rule-strong: var(--dk-rule-strong);
  }
}

/* ------------------------------------------------------------
   NOT IN THIS FILE, ON PURPOSE

   Store colours and line palettes (Spoon) - data colours owned by
   the grocery domain, fixed per store so a colour keeps meaning
   the same shop everywhere it appears.

   Isa's numbered finance group colours - fixed by the
   isa-finances-category-colours convention. Order and hue are
   never reshuffled and this file has no business touching them.

   Layout constants like --card-min and --card-gap - those are one
   app's grid, not a household decision. --card-gap should resolve
   to --sp-3.

   CHART RULES that are household-wide but are not tokens:
   bars and stacked bars are square-cornered (--r-square) even
   though cards and chips are rounded; nothing spent may be
   invisible; no legends. See isa-finances-chart-display-rules.
   ------------------------------------------------------------ */
