/*
 * Voultti design tokens: the brand package's colours, plus a type/spacing/radius scale derived from
 * the numbers in the client brand brief. Everything the app's stylesheet uses lives here, as custom
 * properties on :root, so there is exactly one place that knows a hex value or a size.
 *
 * Self-hosted Inter: no @import of any external URL — the font file ships under client/public/fonts
 * and is served from this origin, which is what the CSP's font-src 'self' allows.
 */

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin-wght-normal.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Colour — named --voultti-* to match the brand package's design-tokens.css. Every pair below is
     the one the brief verified against WCAG AA; do not introduce a colour outside this list. */
  --voultti-navy: #101D32;
  --voultti-deep: #0B172A;
  --voultti-mint: #7EE1D8;
  --voultti-teal: #0B807D;
  --voultti-ink: #17253B;
  --voultti-muted: #526277;
  --voultti-pale: #E4F5F2;
  --voultti-paper: #F8FAF9;
  --voultti-white: #FFFFFF;
  --voultti-border: #DCE5EB;

  /* Type. Inter with the system fallback stack the brief names. Sizes now live in the application
     scale further down — fixed, not fluid; see that block's own comment. */
  --voultti-font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --voultti-font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Menlo", monospace;

  /* The brief's own two leadings — 1.65 for body copy and 1.1 for a 59px heading — went with the
     marketing scale: an application's leading belongs to the size step it sets, not to a pair of
     global values, and while both survived here they kept two rules of 14px text at 1.65 and every
     heading at 1.1 (final-review.md I1). The application leadings are in the scale block below, one
     per step; tests/scale.test.ts's retired-token list keeps these two from coming back. */
  --voultti-tracking-tight: -0.01em;

  /* Spacing: a 4px base scale, named by step rather than pixel size so the numbers read in
     sequence. */
  --voultti-space-1: 0.25rem;  /* 4px */
  --voultti-space-2: 0.5rem;   /* 8px */
  --voultti-space-3: 0.75rem;  /* 12px */
  --voultti-space-4: 1rem;     /* 16px */
  --voultti-space-5: 1.5rem;   /* 24px */
  --voultti-space-6: 2rem;     /* 32px */
  --voultti-space-7: 2.5rem;   /* 40px */
  --voultti-space-8: 3rem;     /* 48px */

  /* Layout: the container and the narrower measure the brief asks form columns to use. */
  --voultti-container-max: 73.75rem;     /* 1180px */
  --voultti-container-pad: 1.75rem;      /* 28px */
  --voultti-container-pad-mobile: 1.25rem; /* 20px */
  --voultti-measure-form: 34rem;

  /* Focus ring: teal, 3px, offset 4px — never removed. */
  --voultti-focus-color: var(--voultti-teal);
  --voultti-focus-width: 3px;
  --voultti-focus-offset: 4px;

  /* Motion: short and quiet, for hover/disabled states rather than anything attention-seeking. */
  --voultti-transition: 120ms ease;
}

/* ---------- Semantic layer ----------
 * The brand values above are raw material; everything in app.css uses the roles below, so a rule
 * never names a colour and a scheme change happens in one place. Each role is declared once with
 * both its values via light-dark(), which is why there is no duplicated dark block to drift out of
 * sync — and no boot-time script, which the CSP would not allow inline anyway.
 *
 * The reds are new: the brand package has none. tests/contrast.test.ts is the authority on whether
 * any value here is legible; when it fails, change the new colours, never a brand value.
 */
:root {
  color-scheme: light dark;

  /* The crafted-minimal palette (2026-09-29 app shell redesign): a near-neutral page and card, a
     teal accent, and a dedicated selection tint — replacing the marketing brand's navy/paper pair.
     tests/contrast.test.ts is still the authority on every value below. */
  --surface-page:     light-dark(#FBFBFA, #111113);
  --surface-card:     light-dark(#FFFFFF, #161618);
  --surface-fill:     light-dark(#F0F0EE, #1D1D20);
  --surface-selected: light-dark(#EAF6F4, #12302F);

  --text-strong:      light-dark(#151515, #EDEDEF);
  --text-body:        light-dark(#151515, #EDEDEF);
  --text-muted:       light-dark(#6B6B66, #8E8E96);
  --text-on-accent:   light-dark(#FFFFFF, #06221F);
  /* For light mode's ink-filled primary button: near-white text on --text-strong used as a fill. */
  --text-on-ink:      light-dark(#FBFBFA, #111113);

  --separator:        light-dark(#EDEDEA, #232327);
  --border-subtle:    light-dark(#8A8A84, #707078);
  --border-strong:    light-dark(#8A8A84, #707078);

  --accent:           light-dark(#0B807D, #6EE0D5);
  /* A second accent step for text that has to sit on --surface-selected or --surface-card at 4.5:1 —
     label chips on a selected row, and any accent text where plain --accent would not clear body text's
     own floor. */
  --accent-strong:    light-dark(#096A68, #6EE0D5);
  --accent-hover:     light-dark(#096A68, #A5EDE6);
  --accent-surface:   light-dark(#EAF6F4, #12302F);

  /* Type tiles: each item type's icon sits on a two-stop gradient (app.css's .item-icon), fixed
     across schemes — a tile's colour identifies the type, not the theme. A white glyph sits on the
     lighter stop (the "-from" one), which is why tests/contrast.test.ts's "type tiles" block checks
     that stop alone against #FFFFFF at 3:1, in both colour schemes. */
  --tile-login-from: #5B8DEF;     --tile-login-to: #2F5FC4;
  --tile-card-from: #CC7700;      --tile-card-to: #A85A00;
  --tile-identity-from: #22A06F;  --tile-identity-to: #12804F;
  --tile-bank-from: #1F93C4;      --tile-bank-to: #0F74A0;
  --tile-insurance-from: #E5608F; --tile-insurance-to: #B83462;
  --tile-file-from: #8B5CF6;      --tile-file-to: #6D45D9;
  --tile-note-from: #8792A1;      --tile-note-to: #606A79;

  /* Name colours (nameColor.ts): a label's, and a login's monogram tile, pick one pair by name — the
     text on its own tint, which also clears 4.5:1 on --surface-card (tests/contrast.test.ts). */
  --label-teal:    light-dark(#096A68, #6EE0D5);
  --label-teal-surface:    light-dark(#E4F4F1, #12302F);
  --label-blue:    light-dark(#1D5BB8, #9DBEFF);
  --label-blue-surface:    light-dark(#E7EEFB, #172848);
  --label-violet:  light-dark(#6539C2, #C5B0FF);
  --label-violet-surface:  light-dark(#F0EBFC, #281F46);
  --label-pink:    light-dark(#A62A62, #FFA6CA);
  --label-pink-surface:    light-dark(#FBE8F0, #3B1A2A);
  --label-orange:  light-dark(#9A4A00, #FFB677);
  --label-orange-surface:  light-dark(#FCEEDF, #38240F);
  --label-green:   light-dark(#1F6E3A, #8DDCA4);
  --label-green-surface:   light-dark(#E4F3E8, #14301E);
  --label-amber:   light-dark(#6F5B00, #E6D06A);
  --label-amber-surface:   light-dark(#F5F0D8, #312A0D);
  --label-slate:   light-dark(#4A5566, #C1CAD6);
  --label-slate-surface:   light-dark(#ECEFF3, #242930);

  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;

  --danger:              light-dark(#B3261E, #FFB4AB);
  --danger-surface:      light-dark(#FDECEA, #3B1513);
  --danger-border:       light-dark(#AC7B76, #C24239);
  /* The destructive *fill*. Light mode darkens the red and puts white on it; dark mode cannot, and
   * the first attempt (#93000A, Material's error *container*, meant to hold pale text rather than to
   * be a button) made "Delete forever" a 1.81:1 hole in the card while Save stayed bright mint —
   * inverting the whole point of the danger colour (final-review.md I2). So dark mode lightens the
   * fill until it is louder than the surface behind it (5.32:1) and moves the label to near-black.
   * The floor is in tests/contrast.test.ts's FILLED_CONTROLS table, which is what was missing. */
  --danger-solid:        light-dark(#B3261E, #FF5449);
  --danger-solid-hover:  light-dark(#8C1D17, #FF6B5E);
  /* Not scheme-independent any more, for the reason above: white on #FF5449 is 3.17:1. */
  --text-on-danger-solid: light-dark(var(--voultti-white), #410002);

  --focus-color: light-dark(var(--voultti-teal), var(--voultti-mint));
}

/* A browser without light-dark() (below Chrome 123 / Safari 17.5 / Firefox 120) gets today's light
 * palette and no dark mode — a lost preference, never an unreadable page. */
@supports not (color: light-dark(#000, #fff)) {
  :root {
    --surface-page: #FBFBFA;
    --surface-card: #FFFFFF;
    --surface-fill: #F0F0EE;
    --surface-selected: #EAF6F4;
    --text-strong: #151515;
    --text-body: #151515;
    --text-muted: #6B6B66;
    --text-on-accent: #FFFFFF;
    --text-on-ink: #FBFBFA;
    --separator: #EDEDEA;
    --border-subtle: #8A8A84;
    --border-strong: #8A8A84;
    --accent: #0B807D;
    --accent-strong: #096A68;
    --accent-hover: #096A68;
    --accent-surface: #EAF6F4;
    --danger: #B3261E;
    --danger-surface: #FDECEA;
    --danger-border: #AC7B76;
    --danger-solid: #B3261E;
    --danger-solid-hover: #8C1D17;
    --text-on-danger-solid: var(--voultti-white);
    --focus-color: var(--voultti-teal);
    --label-teal: #096A68;
    --label-teal-surface: #E4F4F1;
    --label-blue: #1D5BB8;
    --label-blue-surface: #E7EEFB;
    --label-violet: #6539C2;
    --label-violet-surface: #F0EBFC;
    --label-pink: #A62A62;
    --label-pink-surface: #FBE8F0;
    --label-orange: #9A4A00;
    --label-orange-surface: #FCEEDF;
    --label-green: #1F6E3A;
    --label-green-surface: #E4F3E8;
    --label-amber: #6F5B00;
    --label-amber-surface: #F5F0D8;
    --label-slate: #4A5566;
    --label-slate-surface: #ECEFF3;
    /* Elevation is not a scheme preference. Without these two the shadow tokens are simply
       undefined here, `box-shadow: var(--shadow-card)` resolves to nothing, and an older engine gets
       flat cards and a popover with no lift — which is a second thing lost, where this block's whole
       promise is "the light palette, and no dark mode". The light values, because that is what this
       block is. */
    --shadow-card: 0 1px 4px rgb(0 0 0 / 0.08);
    --shadow-popover: 0 4px 16px rgb(0 0 0 / 0.2);
  }
}

/* The manual override. `only` pins the scheme, which is what flips light-dark() and also tells the
 * browser how to paint form controls and scrollbars. */
:root[data-theme="light"] { color-scheme: only light; }
:root[data-theme="dark"]  { color-scheme: only dark; }

/* ---------- Scale, shape, density ----------
 * Application proportions, not the marketing brief's. Fixed sizes rather than clamp(): fluid type
 * belongs on a page where one heading spans a viewport, and in an app it makes every row breathe
 * differently at every width, which defeats a density rhythm. Numbers follow the app shell redesign's
 * crafted-minimal scale (body 0.875rem/1.43, an 8px control radius, 34px controls) — see
 * docs/superpowers/specs/2026-09-29-app-shell-redesign-design.md.
 */
:root {
  /* Each size comes with its own leading, on the line below it, because that pair is the unit the
     spec's table actually defines — a step. The first migration onto this scale wired the sizes and
     left three of the leadings undefined and two more unreferenced, so `h1` kept the marketing 1.1
     and 14px text had two leadings at once (final-review.md I1). tests/scale.test.ts now fails a
     rule that sets a size without its step's leading, and fails this block if either number drifts
     from the spec's table.

     The app shell redesign (2026-09-29) replaces the whole scale below with its own, compact table.
     Every step is declared now, including the ones no rule in app.css sets yet — --text-heading-lg,
     --text-input, --text-secondary and --text-overline — because later tasks in this redesign build
     the new layout against these exact names; a step defined only once its first consumer lands would
     leave those tasks with no token to reach for. --text-title and --text-caption keep their old
     names but move to new sizes (24px→22px, and --text-caption's leading only); --text-section and
     --text-strong-ui are retired in favour of --text-title and the new --text-row. */
  --text-display:      2.375rem;  /* 38px — currently unused: Home's wordmark moved to the Brand
                                      component's own size (--text-title at size="lg") in the app
                                      shell redesign; kept declared for the scale's completeness
                                      (tests/scale.test.ts) */
  --leading-display:   1.21;
  --text-heading-lg:   1.625rem;  /* 26px — a pane's own heading */
  --leading-heading-lg: 1.23;
  --text-title:        1.375rem; /* 22px — a screen or sheet's heading */
  --leading-title:     1.27;
  --text-input:        1rem;     /* 16px — every text input on a coarse pointer: any smaller and
                                     iOS Safari zooms the page in to focus it */
  --leading-input:     1.25;
  --text-row:          0.9375rem; /* 15px — a list row's primary line */
  --leading-row:       1.33;
  --text-body-ui:      0.875rem;  /* 14px — the app's default */
  --leading-body:      1.43;
  --text-secondary:    0.8125rem; /* 13px — a row's secondary line */
  --leading-secondary: 1.38;
  --text-caption:      0.75rem;   /* 12px — timestamps, chips, finest print */
  --leading-caption:   1.33;
  --text-overline:     0.6875rem; /* 11px — an all-caps label above a group */
  --leading-overline:  1.27;

  /* A heading's weight, and a control label's — 500 is ../pms's button weight, and a button's label
     is not a heading. The two are separate tokens because they drifted apart by accident once
     already (final-review.md M7). */
  --weight-heading:  600;
  --weight-control:  500;

  /* Three radii for three kinds of shape: a control's own corner, a group of controls (a card, a
     row group), and a sheet or dialog that covers most of the viewport. --radius and --radius-chip
     survive as aliases — --radius so app.css's many `var(--radius)` control corners need no rename,
     --radius-chip because a chip's radius is a fixed pill, not a step on this scale. */
  --radius-control: 0.5rem;   /* 8px */
  --radius-group:   0.75rem;  /* 12px */
  --radius-sheet:   1rem;     /* 16px */
  --radius:      var(--radius-control);
  --radius-chip: 999px;

  /* Pointer-fine height. Redefined to 44px under (pointer: coarse) below — every consumer of this
     token (min-height, and min-width on a square control) inherits the touch floor automatically,
     with no selector list to keep in sync and no specificity fight against whatever selector a
     control happens to use. A per-selector @media block (the first version of this) was tried and
     twice found to lose that fight silently: a class selector (.theme-choice-button's own
     min-width) and a compound one (a.button-primary) each out-specificity'd the coarse block's
     bare `button`/`.button-primary`, and no test caught either until a live, emulated-touch
     measurement did. client/e2e/touch-targets.spec.ts now polices this for every reachable
     screen. */
  --control-height: 2.125rem; /* 34px */

  /* A hairline always; the shadow only in light mode. On a dark ground a black shadow is dirt and the
     hairline is the separation — ../pms makes the same call with applyStyles('light', ...). */
  --shadow-card: light-dark(0 1px 4px rgb(0 0 0 / 0.08), none);

  /* A popover is not a card. A card sits *on* the ground and the hairline is enough to say so; a
     popover floats *over* content of the same fill, and elevation is the whole of what it means — so
     this one keeps a shadow in dark mode, where --shadow-card deliberately has none. The first
     restyle gave the user menu --shadow-card and --border-subtle, which in dark left an overlay with
     no backdrop separated from the card beneath it by a 3.16:1 hairline and nothing else
     (final-review.md M2). Heavier and more spread than the card's shadow in both schemes, because it
     has to read against a surface rather than against a page. */
  --shadow-popover: light-dark(0 4px 16px rgb(0 0 0 / 0.2), 0 8px 24px rgb(0 0 0 / 0.6));

  --page-pad:        1.5rem;  /* 24px */
  --page-pad-mobile: 1rem;    /* 16px */
  /* The page gutter *at this width* — one token the breakpoint below redefines, rather than a media
     query per rule. The bar, the page frame, a card's own padding and a list row's inset all read
     it, so they step together and none of them can be left behind on the desktop value: the bar was
     left at 24px against the page's 16px on a phone when its own @media block was deleted
     (final-review.md M1), which is the same failure --control-height's coarse-pointer redefinition
     below was introduced to make impossible. */
  --gutter:          var(--page-pad-mobile);
  --measure-read:    60rem;   /* 960px — reading and form screens */
  --measure-list:    73.75rem;/* 1180px — the vault list */
  --bar-height:      3rem;    /* 48px */

  --row-gap-tight: 0.5rem;  /* 8px — within a group */
  --row-gap:       1rem;    /* 16px — between groups */
  --section-gap:   1.5rem;  /* 24px — between sections */
}

/* A finger is not a mouse: 44px is the smallest target a thumb hits reliably. Redefining the token
 * at the root — rather than adding a second, selector-based rule that has to out-specificity every
 * possible consumer — is what makes the floor structural: app.css's buttons, text inputs and the
 * icon-only theme toggles all size themselves from var(--control-height), so they inherit 44px here
 * for free, in every pointer mode, without app.css naming a single one of them again. The one
 * exception is the checkbox (app.css's .checkbox-field input[type="checkbox"]), which was never
 * sized from this token — it keeps its own small @media (pointer: coarse) rule right next to it.
 */
@media (pointer: coarse) {
  :root {
    --control-height: 44px;
  }
}

/* The one width breakpoint the application has (the conflict view's 700px column split is a layout
 * of its own). Above it the gutter opens from 16px to 24px everywhere at once. */
@media (min-width: 640px) {
  :root {
    --gutter: var(--page-pad);
  }
}
