/* Design system v1.2.8 - generated by build.py from src/. Do not edit dist/; edit src/ and rebuild.
   Docs: GUIDE.md in the design-system repo. */
/* Self-hosted fonts (SIL Open Font License 1.1). Paths are relative to this file: keep fonts/ beside ds.css. */
@font-face { font-family:"Inter"; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/inter-300-700-cyrillic-ext.woff2") format("woff2"); unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family:"Inter"; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/inter-300-700-cyrillic.woff2") format("woff2"); unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family:"Inter"; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/inter-300-700-latin-ext.woff2") format("woff2"); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:"Inter"; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/inter-300-700-latin.woff2") format("woff2"); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:"Cormorant Garamond"; font-style:normal; font-weight:500 600; font-display:swap;
  src:url("fonts/cormorant-500-600-cyrillic-ext.woff2") format("woff2"); unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family:"Cormorant Garamond"; font-style:normal; font-weight:500 600; font-display:swap;
  src:url("fonts/cormorant-500-600-cyrillic.woff2") format("woff2"); unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family:"Cormorant Garamond"; font-style:normal; font-weight:500 600; font-display:swap;
  src:url("fonts/cormorant-500-600-latin-ext.woff2") format("woff2"); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:"Cormorant Garamond"; font-style:normal; font-weight:500 600; font-display:swap;
  src:url("fonts/cormorant-500-600-latin.woff2") format("woff2"); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* Andika: the kids variant only (toddler apps). An early-reader face; a browser downloads it only where it renders. */
@font-face { font-family:"Andika"; font-style:normal; font-weight:400; font-display:swap;
  src:url("fonts/andika-400-cyrillic-ext.woff2") format("woff2"); unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:400; font-display:swap;
  src:url("fonts/andika-400-cyrillic.woff2") format("woff2"); unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:400; font-display:swap;
  src:url("fonts/andika-400-latin-ext.woff2") format("woff2"); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:400; font-display:swap;
  src:url("fonts/andika-400-latin.woff2") format("woff2"); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:700; font-display:swap;
  src:url("fonts/andika-700-cyrillic-ext.woff2") format("woff2"); unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:700; font-display:swap;
  src:url("fonts/andika-700-cyrillic.woff2") format("woff2"); unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:700; font-display:swap;
  src:url("fonts/andika-700-latin-ext.woff2") format("woff2"); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:"Andika"; font-style:normal; font-weight:700; font-display:swap;
  src:url("fonts/andika-700-latin.woff2") format("woff2"); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

:root, [data-theme="light"] {
/* Surfaces and text. Light is warm paper with deep bronze. Values from the claude.ai/design run of 3 Oct 2026,
     contrast measured: every text token >= 4.5:1 on --bg, --surface and --surface-2. */
  --bg:#F7F4EF; --surface:#FDFBF8; --surface-2:#F1ECE4;
  --line:#E6DFD4; --line-strong:#D3C9BA;
  --line-control:#928573;   /* outlines that identify a control (input, toggle track, chip, the selected seg): >= 3:1 on bg, surface AND surface-2 (3.29 / 3.49 / 3.07), WCAG 1.4.11 (1.2.6) */
  --text:#1F1B16; --text-2:#554D44; --text-3:#6F655A;
  --accent:#8A5A2B; --accent-hover:#74491F; --on-accent:#FFFBF5; --accent-soft:#F2E8DB;
  --good:#38704A; --warn:#866100; --bad:#A63D32; --focus:#8A5A2B;
  /* Charts. Order is fixed and is the colour-blind safety mechanism: validated with the dataviz validator
     (lightness band, chroma floor, protan/deutan separation, normal-vision floor, 3:1 on surface), the first
     three also all-pairs. Re-validate before changing any value. */
  --chart-1:#955D27; --chart-2:#5F4E8D; --chart-3:#229C9C; --chart-4:#914748; --chart-5:#27639C; --chart-6:#416729;
  --chart-grid:#ECE6DC; --chart-axis:#6F655A; --chart-muted:#928573;   /* a muted mark is still a mark: >= 3:1 on bg, surface AND surface-2 (3.29 / 3.49 / 3.07), drawn at full opacity (1.2.7; was #C2B8AA at .55 = 1.35:1) */
  --shadow-1:0 1px 2px rgba(31,27,22,.05);
  --shadow-2:0 12px 32px -8px rgba(31,27,22,.14);
  --scrim:rgba(31,27,22,.28);
  /* Shared (same in both themes) */
  --radius-s:6px; --radius-m:10px; --radius-l:18px;
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:20px; --space-6:24px; --space-7:32px; --space-8:48px;
  --font-sans:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --font-serif:"Cormorant Garamond","Cormorant",Georgia,serif;
  --text-xs:12px; --text-s:14px; --text-m:16px; --text-l:18px; --text-xl:22px; --text-2xl:30px; --text-hero:52px;
  --ease:cubic-bezier(.2,.7,.2,1); --dur:220ms;
  --tap:44px;               /* minimum touch target */
  --content:720px;          /* reading column: forms, lists */
  --content-wide:1080px;    /* analytics on desktop */
  color-scheme: light;
}
/* Dark is SELECTED, not flipped. Declared once in src/tokens-dark.css and emitted twice: the media query is the
   no-JS path and steps aside for an explicit light choice; [data-theme="dark"] is the explicit (or resolved) pick. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
/* Dark is warm near-black with soft champagne, its own steps, not an inverted light. */
    --bg:#161412; --surface:#1E1B18; --surface-2:#27231F;
    --line:#2F2A25; --line-strong:#443D36;
    --line-control:#766D62;   /* >= 3:1 on bg, surface AND surface-2 (3.62 / 3.37 / 3.07) (1.2.6) */
    --text:#EFE9E1; --text-2:#C2B9AD; --text-3:#9C9387;
    --accent:#D8BF94; --accent-hover:#E8D3AE; --on-accent:#1E1A14; --accent-soft:#2D271F;
    --good:#93C29A; --warn:#DDB869; --bad:#E79286; --focus:#E8D3AE;
    --chart-1:#C0834A; --chart-2:#725CA9; --chart-3:#25A6A6; --chart-4:#BD6B6C; --chart-5:#5B90C7; --chart-6:#719A5B;
    --chart-grid:#2A2622; --chart-axis:#9C9387; --chart-muted:#766D62;   /* >= 3:1 on bg, surface AND surface-2 (3.62 / 3.37 / 3.07), full opacity (1.2.7; was #5A524A) */
    --shadow-1:0 1px 2px rgba(0,0,0,.4);
    --shadow-2:0 16px 40px -8px rgba(0,0,0,.6);
    --scrim:rgba(0,0,0,.5);
    color-scheme: dark;
  }
}
[data-theme="dark"] {
/* Dark is warm near-black with soft champagne, its own steps, not an inverted light. */
    --bg:#161412; --surface:#1E1B18; --surface-2:#27231F;
    --line:#2F2A25; --line-strong:#443D36;
    --line-control:#766D62;   /* >= 3:1 on bg, surface AND surface-2 (3.62 / 3.37 / 3.07) (1.2.6) */
    --text:#EFE9E1; --text-2:#C2B9AD; --text-3:#9C9387;
    --accent:#D8BF94; --accent-hover:#E8D3AE; --on-accent:#1E1A14; --accent-soft:#2D271F;
    --good:#93C29A; --warn:#DDB869; --bad:#E79286; --focus:#E8D3AE;
    --chart-1:#C0834A; --chart-2:#725CA9; --chart-3:#25A6A6; --chart-4:#BD6B6C; --chart-5:#5B90C7; --chart-6:#719A5B;
    --chart-grid:#2A2622; --chart-axis:#9C9387; --chart-muted:#766D62;   /* >= 3:1 on bg, surface AND surface-2 (3.62 / 3.37 / 3.07), full opacity (1.2.7; was #5A524A) */
    --shadow-1:0 1px 2px rgba(0,0,0,.4);
    --shadow-2:0 16px 40px -8px rgba(0,0,0,.6);
    --scrim:rgba(0,0,0,.5);
  color-scheme: dark;
}
/* A themed subtree (a preview frame, a dark card on a light page) repaints itself from its own tokens. */
[data-theme]:not(:root) { background: var(--bg); color: var(--text); }

/* ---- base: element defaults. Every app gets these by loading ds.css. ---- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-m);
  line-height: 1.5;
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, p, ol, ul, figure { margin: 0; }
ul[class], ol[class] { list-style: none; padding: 0; }
img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; text-decoration-color: var(--line-strong); text-underline-offset: 3px; }
/* Every :hover in the system sits in (hover: hover): a touch browser keeps :hover on the last tapped element. */
@media (hover: hover) { a:hover { text-decoration-color: currentColor; } }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-s); }
::selection { background: var(--accent-soft); }
hr { border: 0; border-top: 1px solid var(--line); margin: var(--space-5) 0; }
table { border-collapse: collapse; width: 100%; }
.num, td.num, th.num { font-variant-numeric: tabular-nums; text-align: right; }
/* `hidden` always wins: a ds- class that sets display (ds-card, ds-tiles...) must not show a hidden element. (1.2.1) */
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; scroll-behavior: auto !important; }
}

/* ---- components. Ported from the claude.ai/design run of 3 Oct 2026 ("Quiet"), prefixed ds- so they never
   collide with an app's own .card/.btn/.row while it migrates. ---- */

/* Icons: inline SVG, Lucide geometry, 1.5px stroke. <svg class="ds-i"><use href="#i-home"/></svg> or inline paths. */
.ds-i { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: none; }
.ds-i--s { width: 16px; height: 16px; }

/* ---------- type ---------- */
.ds-title { font-family: var(--font-serif); font-weight: 500; font-size: var(--text-2xl); line-height: 1.1; letter-spacing: .005em; }
.ds-label { font-size: var(--text-xs); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); line-height: 16px; }
.ds-hero { font-size: var(--text-hero); font-weight: 300; letter-spacing: -.035em; line-height: 1; font-variant-numeric: proportional-nums; }
.ds-h { font-size: var(--text-l); font-weight: 500; letter-spacing: -.01em; }
.ds-muted { color: var(--text-2); } .ds-faint { color: var(--text-3); }
.ds-small { font-size: var(--text-s); line-height: 20px; } .ds-xs { font-size: var(--text-xs); line-height: 16px; }
/* Delta: arrow + words, so it never relies on colour alone. <span class="ds-delta is-bad">↗ £97 more than September</span> */
.ds-delta { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-s); font-weight: 500; line-height: 20px; }
.ds-delta.is-good { color: var(--good); } .ds-delta.is-bad { color: var(--bad); } .ds-delta.is-flat { color: var(--text-3); }

/* ---------- app shell ----------
   <body><div class="ds-app">
     <header class="ds-topbar"><h1 class="ds-title">October</h1><button class="ds-iconbtn" aria-label="More actions">⋮</button></header>
     <main class="ds-main"> ... </main>
     <nav class="ds-tabbar"> <a aria-current="page">Home</a> <a>Insights</a> <a>Add</a> <button data-ds-sheet="more">More</button> </nav>
   </div></body>
   The document scrolls (not an inner box), so Android's address bar collapses and pull-to-refresh works.
   At 900px+ the tab bar becomes a left rail by itself: no app code. */
.ds-app { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; background: var(--bg); color: var(--text); }
.ds-topbar { position: sticky; top: 0; z-index: 3; min-height: 64px; display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); padding: env(safe-area-inset-top) var(--space-2) 0 var(--space-5); background: var(--bg); }
.ds-topbar--lead { padding-left: var(--space-2); }
.ds-topbar > .ds-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-main { flex: 1; width: 100%; max-width: var(--content); margin: 0 auto; padding: 0 var(--space-5) var(--space-7); }
.ds-main--wide { max-width: var(--content-wide); }
.ds-section { margin-top: var(--space-7); }
.ds-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-2); }

.ds-tabbar { position: sticky; bottom: 0; z-index: 3; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: var(--space-1) var(--space-2) calc(var(--space-2) + env(safe-area-inset-bottom)); background: var(--bg); border-top: 1px solid var(--line); }
.ds-tabbar > a, .ds-tabbar > button { min-height: 56px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  font-size: var(--text-xs); font-weight: 500; color: var(--text-3); border-radius: var(--radius-m); text-decoration: none; background: none; border: 0; }
.ds-tabbar > [aria-current="page"] { position: relative; color: var(--accent); }
/* The current tab carries a 2px bar as well as the accent (GUIDE rule 6: never colour alone). Phone: on the bar's top hairline. */
.ds-tabbar > [aria-current="page"]::before { content: ""; position: absolute; top: calc(-1 * (var(--space-1) + 1px)); left: calc(50% - 12px);
  width: 24px; height: 2px; border-radius: 0 0 2px 2px; background: var(--accent); }
@media (hover: hover) {
  .ds-tabbar > a:hover, .ds-tabbar > button:hover { color: var(--text); }
  .ds-tabbar > [aria-current="page"]:hover { color: var(--accent); }
}
/* .ds-app--phone keeps the phone layout at any width (preview frames, an embedded phone-sized panel). */
@media (min-width: 900px) {
  .ds-app:not(.ds-app--phone):has(> .ds-tabbar) { display: grid; grid-template-columns: 88px 1fr; grid-template-rows: auto 1fr; }
  .ds-app:not(.ds-app--phone):has(> .ds-tabbar) > .ds-topbar { grid-column: 2; }
  .ds-app:not(.ds-app--phone):has(> .ds-tabbar) > .ds-main { grid-column: 2; }
  .ds-app:not(.ds-app--phone) > .ds-tabbar { grid-column: 1; grid-row: 1 / span 2; position: sticky; top: 0; height: 100vh; height: 100dvh; align-self: start;
    display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-5) var(--space-2); border-top: 0; border-right: 1px solid var(--line); }
  .ds-app:not(.ds-app--phone) > .ds-tabbar > a, .ds-app:not(.ds-app--phone) > .ds-tabbar > button { min-height: 64px; }
  .ds-app:not(.ds-app--phone) > .ds-tabbar > :last-child { margin-top: auto; }   /* More pins to the rail's foot */
  .ds-app:not(.ds-app--phone) > .ds-tabbar > [aria-current="page"]::before { top: calc(50% - 12px); left: calc(-1 * var(--space-2));
    width: 2px; height: 24px; border-radius: 0 2px 2px 0; }   /* rail: the bar moves to the screen edge */
}

/* ---------- buttons ---------- */
.ds-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); min-height: var(--tap); padding: 0 var(--space-5);
  border: 0; border-radius: var(--radius-m); font-size: var(--text-m); font-weight: 500; letter-spacing: -.005em; white-space: nowrap; text-decoration: none;
  background: transparent; color: var(--text); box-shadow: inset 0 0 0 1px var(--line-control);
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease), color var(--dur) var(--ease); }
.ds-btn--primary { background: var(--accent); color: var(--on-accent); box-shadow: none; }
.ds-btn--ghost { color: var(--accent); box-shadow: none; padding: 0 var(--space-3); }
.ds-btn--danger { color: var(--bad); box-shadow: inset 0 0 0 1px var(--bad); }
@media (hover: hover) {
  .ds-btn:hover { background: var(--surface-2); }
  .ds-btn--primary:hover { background: var(--accent-hover); }
  .ds-btn--ghost:hover { background: var(--accent-soft); }
  .ds-btn--danger:hover { background: color-mix(in oklab, var(--bad) 8%, transparent); }
}
.ds-btn:disabled, .ds-btn[aria-disabled="true"] { background: var(--surface-2); color: var(--text-3); box-shadow: none; cursor: not-allowed; }
.ds-btn--block { width: 100%; }
.ds-btn[aria-busy="true"] { pointer-events: none; }
.ds-btn[aria-busy="true"]::before { content: ""; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid currentColor; border-right-color: transparent;
  animation: ds-spin 900ms linear infinite; }
@keyframes ds-spin { to { transform: rotate(360deg); } }
.ds-iconbtn { flex: none; width: var(--tap); height: var(--tap); display: inline-grid; place-items: center; border: 0; border-radius: 50%; background: none;
  color: var(--text-2); transition: background var(--dur) var(--ease); }
@media (hover: hover) { .ds-iconbtn:hover { background: var(--surface-2); color: var(--text); } }
.ds-actions { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-6); }

/* ---------- chips, badges ----------
   The chip itself is the 44px tap target (GUIDE "Layout and touch"); the pill you see stays 36px: a 4px transparent
   border above and below, with the fill (background-clip) and the outline (inset shadow) drawn inside it. In a
   .ds-chips row the rows touch, so those borders make the 8px gap between pills. (1.2.5: a ::after hit area was
   invisible to a measurement and the chip measured 36px.) */
.ds-chips { display: flex; flex-wrap: wrap; gap: 0 var(--space-2); }
.ds-chip { display: inline-flex; align-items: center; gap: 6px; min-height: var(--tap); padding: 0 var(--space-3);
  border: 4px solid transparent; border-width: 4px 0; border-radius: 999px; background-color: transparent; background-clip: padding-box;
  font-size: var(--text-s); color: var(--text-2); box-shadow: inset 0 0 0 1px var(--line-control); }
.ds-chip[aria-pressed="true"] { background-color: var(--text); color: var(--bg); box-shadow: none; }
.ds-badge { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px; border-radius: var(--radius-s);
  font-size: var(--text-xs); font-weight: 500; line-height: 1; background: var(--surface-2); color: var(--text-2); }
.ds-badge--accent { background: var(--accent-soft); color: var(--accent); }
.ds-badge--good, .ds-badge--warn, .ds-badge--bad { background: transparent; box-shadow: inset 0 0 0 1px currentColor; }
.ds-badge--good { color: var(--good); } .ds-badge--warn { color: var(--warn); } .ds-badge--bad { color: var(--bad); }

/* ---------- segmented control ----------
   <div class="ds-seg" role="group" aria-label="Range"><button aria-pressed="true" data-value="30d">30D</button>...</div>
   ds.js flips aria-pressed and fires a "ds-change" event carrying {value}. */
/* Each button is a full 44px tap target; the pill is drawn 3px inside it (transparent top and bottom border, the
   fill clipped to the padding box, an inset outline), so the 44px track still shows a 38px pill. The selected button
   is never told by its fill alone (rule 6; the fill is ~1.1:1 against the track): it also gets an outline in
   --line-control, which reaches 3:1 against the track (--surface-2) as well as the pill (WCAG 1.4.11; 1.2.6, was 2.88:1
   light / 2.94:1 dark on the track), and a semibold label. (1.2.5) */
.ds-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; padding: 0 3px; border-radius: var(--radius-m); background: var(--surface-2); min-height: var(--tap); }
.ds-seg > button { min-height: var(--tap); border: 3px solid transparent; border-width: 3px 0; background-color: transparent; background-clip: padding-box;
  border-radius: calc(var(--radius-m) - 3px) / var(--radius-m); font-size: var(--text-s); font-weight: 500;
  color: var(--text-2); transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.ds-seg > button[aria-pressed="true"] { background-color: var(--surface); color: var(--text); font-weight: 600; box-shadow: inset 0 0 0 1px var(--line-control); }

/* ---------- lists ----------
   <ul class="ds-list"><li class="ds-row"><span class="ds-row-ic">svg</span><span class="ds-row-main"><b>Waitrose</b><span>Groceries · Today</span></span><span class="ds-row-end">−£38.65</span></li></ul> */
.ds-list { list-style: none; margin: 0; padding: 0; }
.ds-row { display: flex; align-items: center; gap: var(--space-3); min-height: 64px; padding: var(--space-3) 0; border-bottom: 1px solid var(--line); }
.ds-list > .ds-row:last-child, .ds-list > li:last-child > .ds-row { border-bottom: 0; }
.ds-row-ic { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--surface-2); color: var(--text-2); flex: none; }
.ds-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ds-row-main > b { font-weight: 500; font-size: var(--text-m); line-height: 22px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-row-main > span { font-size: var(--text-s); line-height: 20px; color: var(--text-3); }
.ds-row-end { text-align: right; font-variant-numeric: tabular-nums; font-weight: 500; white-space: nowrap; }
.ds-row-end .ds-i { color: var(--text-3); }
button.ds-row, a.ds-row { width: 100%; text-align: left; border: 0; border-bottom: 1px solid var(--line); background: none; color: var(--text); text-decoration: none; min-height: 56px; }

/* ---------- cards ---------- */
.ds-card { background: var(--surface); border-radius: var(--radius-l); box-shadow: inset 0 0 0 1px var(--line); padding: var(--space-5); }

/* ---------- forms ----------
   <div class="ds-field"><label for="a">Amount</label><div class="ds-input"><span class="ds-unit">£</span><input id="a" inputmode="decimal"></div><p class="ds-help">...</p></div>
   Error: add .is-error to .ds-input and use <p class="ds-help is-error">icon + words</p>. */
.ds-form { display: flex; flex-direction: column; gap: var(--space-5); }
.ds-form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr)); gap: var(--space-3); }
.ds-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ds-field > label, .ds-field > .ds-flabel { font-size: var(--text-s); font-weight: 500; color: var(--text-2); line-height: 20px; }
.ds-input { display: flex; align-items: center; gap: var(--space-2); min-height: 48px; padding: 0 var(--space-4); border-radius: var(--radius-m);
  background: var(--surface); box-shadow: inset 0 0 0 1px var(--line-control); transition: box-shadow var(--dur) var(--ease); min-width: 0; }
.ds-input:focus-within { box-shadow: inset 0 0 0 1.5px var(--accent); }
.ds-input > input, .ds-input > select, .ds-input > textarea { flex: 1; min-width: 0; width: 100%; height: 46px; border: 0; background: transparent; outline: none;
  font-size: var(--text-m); appearance: none; -webkit-appearance: none; color: var(--text); }
.ds-input > textarea { height: auto; min-height: 96px; padding: var(--space-3) 0; resize: vertical; }
.ds-input > input::placeholder, .ds-input > textarea::placeholder { color: var(--text-3); }
.ds-input > input::-webkit-calendar-picker-indicator { opacity: .55; }
.ds-input > .ds-unit { color: var(--text-3); font-size: var(--text-m); }
.ds-input > .ds-i { color: var(--text-3); }
.ds-input.is-error { box-shadow: inset 0 0 0 1.5px var(--bad); }
.ds-help { font-size: var(--text-s); line-height: 20px; color: var(--text-3); }
.ds-help.is-error { color: var(--bad); display: flex; gap: 6px; align-items: center; }
.ds-check-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); min-height: 56px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/* Toggle: <label class="ds-switch"><input type="checkbox" role="switch"><span></span></label> */
.ds-switch { position: relative; width: 48px; height: 28px; flex: none; display: inline-block; }
.ds-switch > input { position: absolute; inset: -8px; opacity: 0; cursor: pointer; z-index: 1; margin: 0; }
.ds-switch > span { position: absolute; inset: 0; border-radius: 999px; background: var(--line-control); transition: background var(--dur) var(--ease); }
.ds-switch > span::after { content: ""; position: absolute; left: 3px; top: 3px; width: 22px; height: 22px; border-radius: 50%; background: var(--surface);
  box-shadow: var(--shadow-1); transition: transform var(--dur) var(--ease); }
.ds-switch > input:checked + span { background: var(--accent); }
.ds-switch > input:checked + span::after { transform: translateX(20px); background: var(--on-accent); }
.ds-switch > input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ---------- bottom sheet ----------
   <dialog class="ds-sheet" id="more"><div class="ds-grab"></div><div class="ds-sheet-head"><h2 class="ds-h">More</h2><button class="ds-iconbtn" data-ds-close aria-label="Close">×</button></div> ... </dialog>
   Open with <button data-ds-sheet="more"> or DS.sheet.open("more"). Esc, the scrim and the close button dismiss it. */
.ds-sheet { position: fixed; inset: auto 0 0 0; margin: 0 auto; width: 100%; max-width: 560px; max-height: 88dvh; overflow: auto; border: 0;
  color: var(--text); background: var(--surface); border-radius: var(--radius-l) var(--radius-l) 0 0; box-shadow: 0 0 0 1px var(--line), var(--shadow-2);
  padding: 0 var(--space-5) calc(var(--space-6) + env(safe-area-inset-bottom)); }
.ds-sheet[open] { animation: ds-sheet-in calc(var(--dur) * 1.4) var(--ease); }
.ds-sheet::backdrop { background: var(--scrim); animation: ds-fade var(--dur) var(--ease); }
@keyframes ds-sheet-in { from { transform: translateY(100%); } }
@keyframes ds-fade { from { opacity: 0; } }
@media (min-width: 900px) { .ds-sheet { inset: 0; margin: auto; border-radius: var(--radius-l); max-height: 80vh; padding-top: var(--space-2); }
  .ds-sheet > .ds-grab { display: none; } }   /* a centred dialog has nothing to drag */
.ds-grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: 10px auto 6px; }
.ds-sheet-head { display: flex; align-items: center; justify-content: space-between; min-height: 52px; margin-right: calc(var(--space-2) * -1); }
.ds-sheet-head > .ds-h { min-width: 0; overflow-wrap: anywhere; }   /* a long title wraps; the 44px close button never shrinks (1.2.8) */

/* ---------- disclosure ----------
   <details class="ds-disclosure"><summary>Show breakdown</summary> ...table... </details> */
.ds-disclosure > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; min-height: 48px;
  border-top: 1px solid var(--line); font-size: var(--text-s); font-weight: 500; cursor: pointer; }
.ds-disclosure > summary::-webkit-details-marker { display: none; }
.ds-disclosure > summary::after { content: ""; width: 8px; height: 8px; border-right: 1.5px solid var(--text-2); border-bottom: 1.5px solid var(--text-2);
  transform: translateY(-2px) rotate(45deg); transition: transform var(--dur) var(--ease); margin-right: 6px; }
.ds-disclosure[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
.ds-disclosure[open] > summary { color: var(--text); }

/* ---------- analytics ---------- */
.ds-meter { height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
.ds-meter > i { display: block; height: 100%; background: var(--accent); border-radius: 2px; }
.ds-meter-legend { display: flex; justify-content: space-between; gap: var(--space-3); margin-top: var(--space-2); font-size: var(--text-s); color: var(--text-2); font-variant-numeric: tabular-nums; }
.ds-kpi { margin: var(--space-6) 0 var(--space-5); }
.ds-kpi > .ds-hero { margin: var(--space-2) 0 var(--space-2); }
/* Stat tiles: at most three. The delta sits on its own line so it cannot spill out of a 412px phone's tile. */
.ds-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
.ds-tile { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: var(--space-3); border: 0; border-radius: var(--radius-m); background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line); text-align: left; color: var(--text); transition: box-shadow var(--dur) var(--ease); }
@media (hover: hover) { button.ds-tile:hover { box-shadow: inset 0 0 0 1px var(--line-strong); } }
.ds-tile-l { font-size: var(--text-xs); color: var(--text-3); line-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-tile-v { font-size: var(--text-l); font-weight: 500; letter-spacing: -.02em; line-height: 24px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-tile > .ds-delta { font-size: var(--text-xs); line-height: 16px; white-space: nowrap; }
.ds-tile > .ds-chart { margin-top: 6px; height: 28px; }
.ds-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin: var(--space-7) 0 var(--space-3); }
/* A chart's Table / Chart switch sits in its head as a ghost button: text-sized, but a full 44px tap target (the
   negative margins keep the head as tall as its label). <button class="ds-btn ds-btn--ghost" aria-pressed="false">Table</button>
   The same works in a .ds-section-head ("Show all"). (1.2.6) */
.ds-chart-head > .ds-btn, .ds-section-head > .ds-btn { align-self: center; min-height: var(--tap); margin: calc((20px - var(--tap)) / 2) calc(var(--space-3) * -1);
  font-size: var(--text-s); }
/* A hero read as a share of a whole: <div class="ds-hero">6<span class="ds-hero-of">of 15</span></div> (1.2.6) */
.ds-hero-of { margin-left: var(--space-2); font-size: var(--text-xl); font-weight: 400; letter-spacing: 0; color: var(--text-3); }
/* A status word with its icon, for a list's end column: never colour alone (rule 6). Plain = quiet text.
   <span class="ds-flag is-bad"><svg class="ds-i ds-i--s"><use href="#i-alert"/></svg>5 errors</span> (1.2.6) */
.ds-flag { display: inline-flex; align-items: center; justify-content: flex-end; gap: 4px; font-size: var(--text-s); line-height: 20px; color: var(--text-3); white-space: nowrap; }
.ds-flag.is-warn { color: var(--warn); } .ds-flag.is-bad { color: var(--bad); } .ds-flag.is-good { color: var(--good); }
.ds-legend { display: flex; gap: var(--space-3); font-size: var(--text-xs); color: var(--text-2); }
.ds-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.ds-legend > span > i { width: 12px; height: 2px; border-radius: 1px; background: var(--chart-1); }
.ds-table { width: 100%; border-collapse: collapse; font-size: var(--text-s); font-variant-numeric: tabular-nums; }
.ds-table th { font-size: var(--text-xs); font-weight: 500; color: var(--text-3); text-align: left; padding: 8px 0; border-bottom: 1px solid var(--line); letter-spacing: .02em; }
.ds-table td { height: 44px; border-bottom: 1px solid var(--line); color: var(--text); }
.ds-table tr:last-child td { border-bottom: 0; }
.ds-table .num { text-align: right; }
.ds-table th.num { text-align: right; }

/* charts drawn by ds-charts.js */
.ds-chart { position: relative; width: 100%; }
.ds-chart > svg { display: block; overflow: visible; touch-action: pan-y; }
.ds-chart > svg:focus-visible { outline-offset: 4px; }
.ds-axis { font-size: var(--text-xs); fill: var(--chart-axis); font-family: var(--font-sans); font-variant-numeric: tabular-nums; }
/* Y labels ride on a halo of whatever the chart sits on (page, card, sheet, tile), so a line or bar never hides them. */
.ds-axis--y { paint-order: stroke; stroke: var(--chart-bg, var(--bg)); stroke-width: 4px; stroke-linejoin: round; }
.ds-card, .ds-sheet, .ds-tile { --chart-bg: var(--surface); }
.ds-tip { position: absolute; left: 0; top: 0; z-index: 2; pointer-events: none; background: var(--text); color: var(--bg); border-radius: var(--radius-s);
  padding: 6px 10px; font-size: var(--text-xs); line-height: 16px; white-space: nowrap; font-variant-numeric: tabular-nums; box-shadow: var(--shadow-2); }
.ds-tip-title { opacity: .72; }
.ds-tip-row { display: flex; align-items: center; gap: 6px; }
.ds-tip-row > i { width: 10px; height: 2px; border-radius: 1px; }
.ds-tip-row > b { font-weight: 600; font-size: var(--text-s); }
.ds-rank { list-style: none; margin: 0; padding: 0; }
.ds-rank > li { display: grid; grid-template-columns: 1fr auto; column-gap: var(--space-3); row-gap: 6px; padding: var(--space-3) 0; border-bottom: 1px solid var(--line); }
.ds-rank > li:last-child { border-bottom: 0; }
.ds-rank-label { font-size: var(--text-s); }
.ds-rank-value { font-size: var(--text-s); font-weight: 500; font-variant-numeric: tabular-nums; }
.ds-rank-track { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
.ds-rank-fill { display: block; height: 100%; border-radius: 2px; background: var(--chart-muted); }
.ds-rank-fill.is-first { background: var(--chart-1); }

/* ---------- feedback ---------- */
.ds-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-2); padding: var(--space-8) var(--space-5); }
.ds-empty > .ds-row-ic { margin-bottom: var(--space-2); }
.ds-empty > p { color: var(--text-2); font-size: var(--text-s); max-width: 280px; }
.ds-toast { position: fixed; left: 50%; bottom: calc(88px + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: 20; width: min(calc(100% - 32px), 420px);
  display: flex; align-items: center; gap: var(--space-3); min-height: 52px; padding: 0 var(--space-2) 0 var(--space-4); border-radius: var(--radius-m);
  background: var(--text); color: var(--bg); font-size: var(--text-s); box-shadow: var(--shadow-2); animation: ds-toast-in var(--dur) var(--ease);
  top: auto; right: auto; height: auto; margin: 0; border: 0; overflow: visible; }   /* 1.1.2: undo the [popover] UA box (inset 0, margin auto, border) */
.ds-toast > span { flex: 1; }
.ds-toast > button { min-height: var(--tap); padding: 0 var(--space-3); border: 0; background: none; color: var(--bg); font-weight: 500; }
@keyframes ds-toast-in { from { opacity: 0; transform: translate(-50%, 8px); } }
.ds-skel { background: var(--surface-2); border-radius: var(--radius-s); animation: ds-pulse 1.6s var(--ease) infinite; }
@keyframes ds-pulse { 50% { opacity: .55; } }
.ds-busy { opacity: .55; transition: opacity var(--dur) var(--ease); }   /* refetch: hold the old render, dimmed, no skeleton flash */

/* ---------- kids variant (toddler apps) ----------
   <html data-ds-variant="kids">. Same tokens and theme handling; for a three-year-old: a literacy font, everything
   bigger, rounder, one bright colour per tile, no small text, no overflow menu. Grown-up settings sit behind a
   press-and-hold button (data-ds-hold="1500" data-ds-hold-sheet="grownups"), not in a tab bar. */
[data-ds-variant="kids"] {
  --font-sans: "Andika", system-ui, sans-serif; --font-serif: "Andika", system-ui, sans-serif;
  --text-xs: 14px; --text-s: 17px; --text-m: 20px; --text-l: 24px; --text-xl: 28px; --text-2xl: 40px; --text-hero: 64px;
  --radius-s: 12px; --radius-m: 20px; --radius-l: 28px; --tap: 64px;
  /* Tile colours, label contrast measured: white on 1/2/3/5 is 4.3-5.8:1, dark ink on 4/6 is 6.9-8.6:1 (labels are 24px bold). */
  --kid-1:#D9481F; --kid-2:#2878C8; --kid-3:#2E8B4A; --kid-4:#F4B400; --kid-5:#7C4DB8; --kid-6:#F08A24; --on-kid:#FFFFFF; --on-kid-dark:#2B1600;
}
[data-ds-variant="kids"] .ds-title { font-weight: 700; letter-spacing: 0; }
[data-ds-variant="kids"] .ds-btn { min-height: 64px; font-weight: 700; border-radius: 999px; padding: 0 var(--space-7); }
.ds-kid-grid { margin-top: var(--space-3); display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-4); }
.ds-kid-tile { aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-3);
  border: 0; border-radius: var(--radius-l); background: var(--kid-2); color: var(--on-kid); font-family: var(--font-sans);
  font-size: var(--text-l); font-weight: 700; text-decoration: none; box-shadow: inset 0 -6px 0 rgba(0,0,0,.14);
  transition: transform 120ms var(--ease); }
.ds-kid-tile:active { transform: scale(.96); }
.ds-kid-tile > svg { width: 56%; height: auto; }
.ds-kid-tile[data-kid="1"] { background: var(--kid-1); } .ds-kid-tile[data-kid="3"] { background: var(--kid-3); }
.ds-kid-tile[data-kid="4"] { background: var(--kid-4); color: var(--on-kid-dark); } .ds-kid-tile[data-kid="5"] { background: var(--kid-5); }
.ds-kid-tile[data-kid="6"] { background: var(--kid-6); color: var(--on-kid-dark); }
.ds-hold { position: relative; overflow: hidden; }
.ds-hold::after { content: ""; position: absolute; left: 0; bottom: 0; height: 3px; width: 0; background: var(--accent); }
.ds-hold.is-holding::after { width: 100%; transition: width var(--ds-hold-ms, 1500ms) linear; }
