/*
  facet.css — the whole Facet design system in one readable file.

  Facet is a plain HTML, CSS and JS design library. Any project consumes it
  with one link tag pointed at this file's public URL, plus one script tag
  for facet.js. No build step, no framework, never minified.

  How this file is organised, top to bottom:

    1. TOKENS       every design decision as a CSS variable, named by role,
                    one block per theme
    2. RESET        box sizing, margin reset, sensible element defaults
    3. BASE STYLES  defaults for every raw HTML tag: text, forms, media,
                    tables, focus rings, selection, scrollbars, print
    4. COMPONENTS   one clearly commented section per component

  Theming: one attribute on the <html> tag switches the theme, layout
  containers included. No attribute at all is the Default theme. Dark mode
  is a second attribute and composes with every theme:

    <html>                                 Default, light
    <html data-mode="dark">                Default, dark
    <html data-theme="sand">               Sand, light
    <html data-theme="sand" data-mode="dark">   Sand, dark

  The same attributes work on any element, not just <html>: put
  data-mode="dark" on a section and only that subtree flips — that is how
  the library site shows light and dark side by side. Tokens are inherited
  custom properties, so the nearest element carrying the attribute wins.

  Every color below is a semantic token (--background, --surface, --text...),
  so a theme swaps values in one block and the entire page restyles itself.

  Rules that hold everywhere in this file:
    - Tokens are named by role, not value: --surface, never --gray-100.
    - Components use tokens only. No raw hex or magic px below the token layer.
    - One component, one section. Nothing is scattered.
    - Every component section opens with its canonical description — what it
      is, what it is for, when to use it — the same text, word for word, as
      its entry on the library site and in llms.txt.
    - prefers-reduced-motion is honoured: all motion collapses to none.

  To-dos are marked "TODO" inline where they sit.
*/


/* ==========================================================================
   1. TOKENS
   Layer 1 of the library. Everything downstream reads these variables.
   ========================================================================== */

/*
   Theme · Default (light). Ships first, no attribute needed.
   Paper white, near-black ink, gray hairlines, small radii, whisper
   shadows. Zero decoration, the quiet base. The accent ranks are the ink
   itself: accent-1 primary actions are black on white deepening to full
   black, accent-2 is the quiet surface fill, accent-3 the interactive
   ink for links and small affordances.

   The non-color tokens (type, spacing, shape, elevation, motion, focus)
   live here once and are shared by every theme; themes override only what
   they change. [data-mode="light"] shares this block so a light subtree
   can sit inside a dark page.
*/
:root,
[data-mode="light"] {

  /* ----- Colors: core palette ----- */
  --background: #FFFFFF;      /* paper: the page itself */
  --surface: #FAFAFA;         /* a breath above the page: cards, inputs */
  --text: #17171B;            /* near-black ink */
  --text-muted: #6E6E76;      /* secondary text, hints, captions */
  --border: #E4E4E8;          /* gray hairlines and edges */

  /* ----- Colors: the accent ranks ----- */
  /* Components never pick colors; they pick ranks. Accent-1 is the one
     primary action on a screen. Accent-2 is every secondary action's
     fill. Accent-3 is the quiet interactive color: links, labels, focus,
     small affordances. Each rank carries hover, pressed and an on-color
     for text sitting on it. In Default, accent-1 and accent-3 are the
     ink itself; accent-2 is the surface family. */
  --accent-1: #17171B;
  --accent-1-hover: #09090B;
  --accent-1-pressed: #000000;
  --on-accent-1: #FFFFFF;

  --accent-2: #FAFAFA;
  --accent-2-hover: #F0F0F2;
  --accent-2-pressed: #E4E4E8;
  --on-accent-2: #17171B;

  --accent-3: #17171B;        /* the quiet interactive rank — the ink */
  --accent-3-hover: #09090B;
  --accent-3-pressed: #000000;
  --on-accent-3: #FFFFFF;

  /* The OS accent, as its own opt-in color — dormant, used by nothing until
     you reach for it (var(--os-accent) on a link, a highlight, whatever).
     Defaults to the iOS system blue everywhere; where the browser exposes
     the real OS accent (see the @supports block below) it becomes that. So
     it is blue for everyone by default, and the user's own accent only for
     the few who set a custom one on a supporting browser. */
  --os-accent: #0A84FF;
  --os-accent-hover: color-mix(in srgb, #0A84FF 86%, black);
  --os-accent-pressed: color-mix(in srgb, #0A84FF 74%, black);
  --on-os-accent: #FFFFFF;

  /* ----- Colors: status ----- */
  --success: #187C3D;         /* things that went well */
  --warning: #8A5A00;         /* needs attention, not danger */
  --error: #B3261E;           /* something failed */
  --info: #295FA6;            /* neutral notices */

  /* ----- Typography: font stacks ----- */
  /* System stacks: nothing to download, instant render, native feel.
     Headings share the body stack and differentiate by weight and spacing. */
  --font-heading: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* ----- Typography: type scale, named by role, scaled by size -----
     --text-scale (data-text-size small/medium/large) shifts type globally;
     medium is the default. A theme may override any of these. */
  --text-scale: 1;
  --text-display:  calc(clamp(2.5rem, 6vw, 3.5rem) * var(--text-scale));   /* hero numbers and statements */
  --text-h1:       calc(clamp(1.875rem, 4vw, 2.25rem) * var(--text-scale));
  --text-h2:       calc(1.5rem   * var(--text-scale));
  --text-h3:       calc(1.25rem  * var(--text-scale));
  --text-h4:       calc(1.125rem * var(--text-scale));
  --text-body:     calc(1rem     * var(--text-scale));
  --text-quote:    calc(1.125rem * var(--text-scale));   /* pull-quotes and lead-ins */
  --text-footnote: calc(0.875rem * var(--text-scale));   /* the fine print (was --text-footnote) */
  --text-caption:  calc(0.75rem  * var(--text-scale));   /* labels, badges, meta */

  /* ----- Typography: weights and line heights ----- */
  --weight-heading: 650;      /* variable-font friendly; falls back to bold */
  --weight-body: 400;
  --weight-strong: 600;
  --leading-heading: 1.2;
  --leading-body: 1.6;

  /* ----- Spacing: named by intent, scaled by density -----
     Pick spacing by role, never by a bare number. Each token says where it
     belongs. --density-scale (data-density small/medium/large) shifts the
     whole system at once; medium is the default. A theme may override any
     of these to set its own rhythm. */
  --density-scale: 1;
  --space-tight:   calc(0.25rem * var(--density-scale));  /*  4px · bound pairs: a label and its field */
  --space-control: calc(0.5rem  * var(--density-scale));  /*  8px · padding inside buttons, inputs, chips */
  --space-inline:  calc(0.75rem * var(--density-scale));  /* 12px · gaps between items in a row */
  --space-stack:   calc(1rem    * var(--density-scale));  /* 16px · default gap between stacked blocks */
  --space-card:    calc(1.5rem  * var(--density-scale));  /* 24px · padding inside cards and panels */
  --space-page:    calc(2rem    * var(--density-scale));  /* 32px · container side padding, page margins */
  --space-section: calc(3rem    * var(--density-scale));  /* 48px · between whole page sections */
  --space-hero:    calc(4rem    * var(--density-scale));  /* 64px · the biggest breaks: hero bands */

  /* ----- Spacing: container widths ----- */
  --width-narrow: 40rem;      /* prose, forms, calculators */
  --width-default: 64rem;     /* most pages */
  --width-wide: 80rem;        /* dashboards, galleries */

  /* ----- Shape: radii named for what they round ----- */
  --radius-control: 6px;      /* buttons, inputs, chips — the small controls */
  --radius-panel: 10px;       /* fields, result blocks, mid-size panels */
  --radius-card: 16px;        /* cards, modals, hero — the large surfaces */
  --radius-pill: 999px;       /* pills, sliders, avatars */
  /* Borders named by role. Hairline and element are both 1px — the name
     says the job: a faint separator vs the outline of an element. Focus and
     highlight are the 2px emphasis pair: the focus ring vs drawing attention
     to an element (active bars, selected rows). */
  --border-hairline: 1px;     /* a faint separator: dividers, rules, row lines */
  --border-element: 1px;      /* the outline of an element: cards, inputs, chips */
  --border-focus: 2px;        /* the focus ring width — focus only */
  --border-highlight: 2px;    /* draw attention to an element: active bars, selected */

  /* ----- Elevation: whisper-flat shadows ----- */
  --shadow-flat: none;
  --shadow-raised: 0 1px 2px rgb(23 23 27 / 0.06);
  --shadow-floating: 0 4px 12px rgb(23 23 27 / 0.08);
  --shadow-overlay: 0 12px 32px rgb(23 23 27 / 0.14);

  /* ----- Motion: the personality ----- */
  /* Lively, the default: momentum, weight, a settle in every arrival.
     Color changes use --ease-out; anything that moves or scales uses
     --ease-spring, which overshoots slightly and settles. Swap the whole
     personality with data-motion="calm" (quiet, near-linear) or stop it
     with data-motion="off" — one attribute, page-wide or per subtree.
     prefers-reduced-motion sits above all of it. */
  --duration-fast: 150ms;     /* hovers, color changes */
  --duration-normal: 280ms;   /* presses, reveals, toggles */
  --duration-slow: 450ms;     /* overlays, page-level moves */
  --ease-out: cubic-bezier(0.2, 0, 0, 1);       /* things arriving */
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);  /* things moving */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);  /* bouncy back-out;
                                upgraded to a true spring curve below */

  /* ----- Focus ----- */
  --focus-ring: var(--border-focus) solid var(--accent-3);
  --focus-offset: 2px;
}

/*
   Theme · Default (dark). <html data-mode="dark">.
   Near-black ground, paper ink. The ranks flip with the ink: accent-1
   primary actions become paper on black. Only color tokens change; type,
   spacing, shape and motion carry over.
*/
[data-mode="dark"] {
  --background: #121214;
  --surface: #1B1B1F;
  --text: #F4F4F5;
  --text-muted: #9C9CA3;
  --border: #2E2E33;

  --accent-1: #F4F4F5;
  --accent-1-hover: #FFFFFF;
  --accent-1-pressed: #E2E2E5;
  --on-accent-1: #17171B;

  --accent-2: #1B1B1F;
  --accent-2-hover: #232328;
  --accent-2-pressed: #2E2E33;
  --on-accent-2: #F4F4F5;

  --accent-3: #F4F4F5;
  --accent-3-hover: #FFFFFF;
  --accent-3-pressed: #E2E2E5;
  --on-accent-3: #17171B;

  --success: #56B87A;
  --warning: #D8A23E;
  --error: #E5766A;
  --info: #7AA7DC;

  --shadow-raised: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-floating: 0 4px 12px rgb(0 0 0 / 0.45);
  --shadow-overlay: 0 12px 32px rgb(0 0 0 / 0.55);
}

/*
   Theme · Sand (light). <html data-theme="sand">.
   Modern beige, boring on purpose, quietly elegant. Palette pulled from
   Minecraft desert biome blocks, with the ink darkened for AA contrast.
   Parked, not deleted: available whenever a project wants it.
*/
[data-theme="sand"] {
  --background: #EDE5C0;      /* sand */
  --surface: #F6F0D8;         /* smooth sandstone */
  --text: #33291A;            /* dark earth */
  --text-muted: #665A38;      /* dead bush, deepened for AA on sand */
  --border: #D9CD9E;          /* sandstone shadow */

  --accent-1: #487D31;        /* cactus, deep enough for white labels (AA) */
  --accent-1-hover: #3E6E27;
  --accent-1-pressed: #356020;
  --on-accent-1: #FFFFFF;

  --accent-2: #F6F0D8;        /* smooth sandstone, the quiet action fill */
  --accent-2-hover: #F0E9CE;
  --accent-2-pressed: #E5DBB4;
  --on-accent-2: #33291A;

  --accent-3: #3A6824;        /* deep cactus: AA as text on sand */
  --accent-3-hover: #356020;
  --accent-3-pressed: #2F551C;
  --on-accent-3: #FFFFFF;

  --success: #3A6B25;
  --warning: #7A5711;
  --error: #93452F;
  --info: #456073;

  --shadow-raised: 0 1px 2px rgb(51 41 26 / 0.08);
  --shadow-floating: 0 4px 12px rgb(51 41 26 / 0.10);
  --shadow-overlay: 0 12px 32px rgb(51 41 26 / 0.16);
}

/*
   Theme · Sand (dark). Composes: <html data-theme="sand" data-mode="dark">.
   Dark earth ground, sand-colored ink, cactus stays the accent.
*/
[data-theme="sand"][data-mode="dark"] {
  --background: #211A0F;
  --surface: #2C2415;
  --text: #EFE7C8;
  --text-muted: #A99C6E;
  --border: #453B24;

  --accent-1: #6FAE52;
  --accent-1-hover: #7CBB5F;
  --accent-1-pressed: #619E46;
  --on-accent-1: #1C2611;

  --accent-2: #2C2415;
  --accent-2-hover: #352C1A;
  --accent-2-pressed: #453B24;
  --on-accent-2: #EFE7C8;

  --accent-3: #7CBB5F;        /* lighter cactus: AA as text on dark earth */
  --accent-3-hover: #8AC46E;
  --accent-3-pressed: #6FAE52;
  --on-accent-3: #1C2611;

  --success: #6FAE52;
  --warning: #C79A45;
  --error: #C4735C;
  --info: #7FA0B4;

  --shadow-raised: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-floating: 0 4px 12px rgb(0 0 0 / 0.45);
  --shadow-overlay: 0 12px 32px rgb(0 0 0 / 0.55);
}


/*
   Theme · Velvet (light). <html data-theme="velvet">.
   A CRED-inspired neumorphic material, extracted from the shipped
   inflation app (hand-tuned in the app's velvet-tuner and user-approved
   — trust these values over taste). Matte velvet
   surfaces, ONE light source from straight above: elements are either
   RAISED out of the fabric (face gradient + key light + cast shadow +
   bevel) or CARVED into it (wells and pits with interior shadows). Gold
   is the only accent ink.

   The behavioral laws that ARE this theme (full text ships with the
   library docs): one raised face per screen — the primary CTA wears the
   accent; cushion press — the dent is a fading overlay, the face
   gradient never changes on press; raised text rides up and dips while
   pressed; icons engrave like text; state changes ease, never jump;
   elements carrying velvet lift/press transforms must NOT register with
   parallax modules (inline transforms collide); never put `inherit`
   inside a font list.

   Standard tokens are mapped so every Facet component works here:
   accent-1 is the gold, accent-2 the raised gray face family, accent-3
   the gold ink. The --v-* material tokens are kept verbatim from the
   reference so recipes diff cleanly against the app.
*/
[data-theme="velvet"] {
  --background: #e4e4e9;
  --surface: #d6d6db;         /* the well: carved things sit on this */
  --text: #636367;
  --text-muted: #56565C;      /* deepened: AA even on the carved wells */
  --border: #cfcfd4;

  --accent-1: #8a6c26;        /* the gold */
  --accent-1-hover: #7a5f20;
  --accent-1-pressed: #6b531c;
  --on-accent-1: #FFFFFF;

  --accent-2: #e6e6ea;        /* the raised face family */
  --accent-2-hover: #dfdfe3;
  --accent-2-pressed: #d6d6db;
  --on-accent-2: #636367;

  --accent-3: #6B531C;        /* gold ink deepened: AA as link text on the wells */
  --accent-3-hover: #5F4A19;
  --accent-3-pressed: #544117;
  --on-accent-3: #FFFFFF;

  /* Status inks deepened too: the wells (--surface) are darker than
     the fabric, and status words must stay AA inside them. */
  --success: #0F5A30;
  --warning: #664300;
  --error: #9A2019;
  --info: #1B457A;

  /* Two type voices: an elegant serif display for statements, the
     rounded stack for controls and micro labels. */
  --font-heading: "Baskerville", "Palatino", "New York", ui-serif, Georgia, serif;
  --weight-heading: 400;

  /* ---- VELVET value block · light (verbatim from the reference) ---- */
  --v-bg: #e4e4e9;
  --v-well-bg: #d6d6db;
  --v-face:
    radial-gradient(120% 90% at 50% 0%, rgba(255,255,255,0.45), transparent 68%),
    linear-gradient(180deg, #e6e6ea, #d6d6db);
  --v-face-accent:
    radial-gradient(120% 90% at 50% 0%, rgba(255,255,255,0.6), transparent 68%),
    linear-gradient(180deg, #ecd8b2, #d1bb91);
  --v-ink: #8c8c90;
  --v-ink-strong: #636367;
  --v-ink-accent: #5C4A18;
  --v-gold: #8a6c26;
  --v-weight: 750;
  --v-text-shadow: 0 1px 1px rgba(255,255,255,0.5), 0 -1px 1px rgba(0,0,0,0.1);
  --v-icon-filter: drop-shadow(0 1px 1px rgba(255,255,255,0.65)) drop-shadow(0 -1px 1px rgba(0,0,0,0.13));
  --v-key: 0 -7px 16px rgba(255,255,255,0.9);
  --v-cast: 0 12px 28px rgba(0,0,0,0.15);
  --v-bevel:
    inset 0 1.5px 2.5px rgba(255,255,255,0.95),
    inset 0 -1.5px 2.5px rgba(255,255,255,0.35),
    inset 0 -3px 5px rgba(0,0,0,0.12);
  --v-raised: var(--v-key), var(--v-cast), var(--v-bevel);
  --v-raised-pressed: 0 -5.95px 13.6px rgba(255,255,255,0.9), 0 10.2px 23.8px rgba(0,0,0,0.15), var(--v-bevel);
  --v-pill: var(--v-cast), var(--v-bevel);   /* inside a well: no key light */
  --v-raised-2nd: 0 -3.15px 7.2px rgba(255,255,255,0.9), 0 5.4px 12.6px rgba(0,0,0,0.15), var(--v-bevel);
  --v-well-shadow:
    var(--v-key), var(--v-cast),
    inset 0 3px 6px rgba(255,255,255,0.5),
    inset 0 2.8px 7px rgba(0,0,0,0.22),
    inset 0 -2.8px 7px rgba(255,255,255,0.9);
  --v-pit:
    inset 0 2.8px 7px rgba(0,0,0,0.22),
    inset 0 -2.8px 7px rgba(255,255,255,0.9);
  --v-dent-btn: radial-gradient(72% 62% at 50% 58%, rgba(0,0,0,0.056), transparent 78%);
  --v-dent-tap: radial-gradient(72% 62% at 50% 58%, rgba(0,0,0,0.14), transparent 78%);
  --v-div-line: linear-gradient(180deg, transparent, rgba(0,0,0,0.18) 25%, rgba(0,0,0,0.18) 75%, transparent);
  --v-div-edge: 0 1px 2px rgba(255,255,255,0.6), 1px 0 2px rgba(255,255,255,0.6);
  --v-div-blur: 0.3px;
  --v-well-pad: 7px;  --v-pill-drop: 2px;  --v-face-up: 2.5px;
  --v-lift: 3px;  --v-dip: 1.5px;  --v-sink: 1px;
  --v-font: ui-rounded, "SF Pro Rounded", "Arial Rounded MT Bold", "Varela Round", "Nunito", sans-serif;
  /* press lands fast, release springs back — tuned, final, personality-proof */
  --v-press-in: 0.09s ease-out;
  --v-press-out: 0.32s cubic-bezier(0.34, 1.45, 0.6, 1);
}

/*
   Theme · Velvet (dark). <html data-theme="velvet" data-mode="dark">.
   Same material at night: deeper wells, wider casts, gold turns pale.
*/
[data-theme="velvet"][data-mode="dark"] {
  --background: #1a1a1e;
  --surface: #17171a;
  --text: #ffffff;
  --text-muted: #9c9ca1;
  --border: #2e2e33;

  --accent-1: #dcbe8e;
  --accent-1-hover: #e6cc9f;
  --accent-1-pressed: #d0b07c;
  --on-accent-1: #160e00;

  --accent-2: #353539;
  --accent-2-hover: #3c3c41;
  --accent-2-pressed: #454549;
  --on-accent-2: #ffffff;

  --accent-3: #dcbe8e;
  --accent-3-hover: #e6cc9f;
  --accent-3-pressed: #d0b07c;
  --on-accent-3: #160e00;

  /* Status inks lifted back up: the light block deepened them for its
     wells, and this block must answer with dark-ground values. */
  --success: #56B87A;
  --warning: #D8A23E;
  --error: #E5766A;
  --info: #7AA7DC;

  /* ---- VELVET value block · dark (verbatim from the reference) ---- */
  --v-bg: #1a1a1e;
  --v-well-bg: #17171a;
  --v-face:
    radial-gradient(120% 110% at 50% 0%, rgba(255,255,255,0.08), transparent 68%),
    linear-gradient(180deg, #353539, #17171a);
  --v-face-accent:
    radial-gradient(120% 110% at 50% 0%, rgba(255,255,255,0.26), transparent 68%),
    linear-gradient(180deg, #423b2e, #160e00);
  --v-ink: #ffffff;
  --v-ink-strong: #ffffff;
  --v-ink-accent: #ffffff;
  --v-gold: #dcbe8e;
  --v-weight: 650;
  --v-text-shadow: 0 1px 1.5px rgba(0,0,0,0.5), 0 -1px 1.5px rgba(255,255,255,0.25);
  --v-icon-filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.65)) drop-shadow(0 -1px 1.5px rgba(255,255,255,0.33));
  --v-key: 0 -8px 32px rgba(255,255,255,0.11);
  --v-cast: 0 9px 53px rgba(0,0,0,0.73);
  --v-bevel:
    inset 0 1.5px 4.5px rgba(255,255,255,0.08),
    inset 0 -1.5px 4.5px rgba(255,255,255,0.08),
    inset 0 -3px 16px rgba(0,0,0,0.71);
  --v-raised: var(--v-key), var(--v-cast), var(--v-bevel);
  --v-raised-pressed: var(--v-key), var(--v-cast), var(--v-bevel); /* pressElev 1 */
  --v-pill: var(--v-cast), var(--v-bevel);
  --v-raised-2nd: 0 -2.8px 11.2px rgba(255,255,255,0.11), 0 3.15px 18.55px rgba(0,0,0,0.73), var(--v-bevel);
  --v-well-shadow:
    var(--v-key), var(--v-cast),
    inset 0 1.5px 3px rgba(255,255,255,0.1),
    inset 0 7.2px 18px rgba(0,0,0,0.91),
    inset 0 -9.6px 24px rgba(255,255,255,0.11);
  --v-pit:
    inset 0 5px 13px rgba(0,0,0,0.75),
    inset 0 -5px 14px rgba(255,255,255,0.09);
  --v-dent-btn: radial-gradient(65% 56% at 50% 40%, rgba(0,0,0,0.4), transparent 78%);
  --v-dent-tap: radial-gradient(65% 56% at 50% 40%, rgba(0,0,0,1), transparent 78%);
  --v-div-line: linear-gradient(180deg, rgba(0,0,0,1), rgba(0,0,0,1));
  --v-div-edge: 0 1px 2px rgba(255,255,255,0.17), 1px 0 2px rgba(255,255,255,0.17);
  --v-div-blur: 0.6px;
  --v-well-pad: 7px;  --v-pill-drop: 3px;  --v-face-up: 0px;
  --v-lift: 2.5px;  --v-dip: 1px;  --v-sink: 0px;
}

/*
   Velvet recipes: how existing Facet components wear the material.
   Scoped under the theme; recipes for the handoff's new components (tab
   bar, sheet, gauge...) arrive with those components. Press language
   everywhere: the dent is a fading ::after overlay, the face gradient
   never changes; the tuned press physics are final in every motion
   personality — only data-motion="off" and prefers-reduced-motion stop
   them.
*/

/* Inputs are carved into the material. */
[data-theme="velvet"] input:not(.slider),
[data-theme="velvet"] textarea,
[data-theme="velvet"] select {
  background-color: var(--v-well-bg);   /* -color: the select keeps its chevron */
  border-color: transparent;
  box-shadow: var(--v-pit);
}
[data-theme="velvet"] input:not(.slider):hover,
[data-theme="velvet"] textarea:hover,
[data-theme="velvet"] select:hover { border-color: transparent; }

/* Buttons: rounded voice, raised faces, cushion press. */
[data-theme="velvet"] .btn {
  position: relative;
  border: none;
  border-radius: var(--radius-pill);
  font-family: var(--v-font);
  font-weight: var(--v-weight);
  color: var(--v-ink);
  text-shadow: var(--v-text-shadow);
  background: var(--v-face);
  box-shadow: var(--v-raised-2nd);
  transition: transform var(--v-press-out), box-shadow var(--v-press-out);
}
[data-theme="velvet"] .btn::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-pill);
  background: var(--v-dent-btn); opacity: 0; pointer-events: none;
  transition: opacity var(--v-press-out);
}
[data-theme="velvet"] .btn:hover { background: var(--v-face); border: none; }
[data-theme="velvet"] .btn:active {
  transform: translateY(1px);
  transition-duration: 0.09s, 0.09s;
}
[data-theme="velvet"] .btn:active::after { opacity: 1; transition: opacity var(--v-press-in); }

/* The one raised face per screen: the primary CTA wears the gold. */
[data-theme="velvet"] .btn-primary {
  background: var(--v-face-accent);
  color: var(--v-ink-accent);
  box-shadow: var(--v-raised);
  transform: translateY(calc(-1 * var(--v-face-up)));
}
[data-theme="velvet"] .btn-primary:hover { background: var(--v-face-accent); }
[data-theme="velvet"] .btn-primary:active {
  background: var(--v-face-accent);
  box-shadow: var(--v-raised-pressed);
  transform: translateY(calc(var(--v-sink) - var(--v-face-up)));
}

/* Tertiary stays flat: label never lifts, 1px fixed press sink. */
[data-theme="velvet"] .btn-ghost {
  background: transparent;
  box-shadow: none;
}
[data-theme="velvet"] .btn-ghost:hover { background: transparent; border: none; }
[data-theme="velvet"] .btn-ghost:active { transform: translateY(1px); }

/* Sliders: carved groove, raised knob carrying the gold jewel; grabbing
   deepens the gold — the face itself never changes. */
[data-theme="velvet"] .slider::-webkit-slider-runnable-track {
  height: 10px; background: var(--v-well-bg); box-shadow: var(--v-pit);
}
[data-theme="velvet"] .slider::-moz-range-track {
  height: 10px; background: var(--v-well-bg); box-shadow: var(--v-pit);
}
[data-theme="velvet"] .slider::-moz-range-progress { background: transparent; }
[data-theme="velvet"] .slider::-webkit-slider-thumb {
  width: 32px; height: 32px; margin-top: -11px;
  background:
    radial-gradient(circle closest-side at 50% 42%,
      color-mix(in srgb, var(--v-gold) 78%, #fff 30%) 0,
      var(--v-gold) 55%,
      color-mix(in srgb, var(--v-gold) 72%, #000 28%) 88%,
      transparent 93%) no-repeat 50% 50% / 18px 18px,
    var(--v-face);
  border: none;
  box-shadow: var(--v-pill);
}
[data-theme="velvet"] .slider::-moz-range-thumb {
  width: 32px; height: 32px;
  background:
    radial-gradient(circle closest-side at 50% 42%,
      color-mix(in srgb, var(--v-gold) 78%, #fff 30%) 0,
      var(--v-gold) 55%,
      color-mix(in srgb, var(--v-gold) 72%, #000 28%) 88%,
      transparent 93%) no-repeat 50% 50% / 18px 18px,
    var(--v-face);
  border: none;
  box-shadow: var(--v-pill);
}
[data-theme="velvet"] .slider:active::-webkit-slider-thumb {
  background:
    radial-gradient(circle closest-side at 50% 42%,
      color-mix(in srgb, var(--v-gold) 88%, #000 8%) 0,
      color-mix(in srgb, var(--v-gold) 74%, #000 26%) 55%,
      color-mix(in srgb, var(--v-gold) 55%, #000 45%) 88%,
      transparent 93%) no-repeat 50% 50% / 18px 18px,
    var(--v-face);
}
[data-theme="velvet"] .slider:active::-moz-range-thumb {
  background:
    radial-gradient(circle closest-side at 50% 42%,
      color-mix(in srgb, var(--v-gold) 88%, #000 8%) 0,
      color-mix(in srgb, var(--v-gold) 74%, #000 26%) 55%,
      color-mix(in srgb, var(--v-gold) 55%, #000 45%) 88%,
      transparent 93%) no-repeat 50% 50% / 18px 18px,
    var(--v-face);
}

/* Result blocks are wells; the numbers that matter glow gold. */
[data-theme="velvet"] .result {
  background: var(--v-well-bg);
  border: none;
  border-radius: var(--radius-card);
  box-shadow: var(--v-pit);
}

/* Cards are raised faces lifted out of the fabric. */
[data-theme="velvet"] .card {
  background: var(--v-face);
  border: none;
  box-shadow: var(--v-raised-2nd);
}

/* The chart card is a well carved into the page; the gold line
   arrives through accent-1 with no chart-specific rule at all. */
[data-theme="velvet"] .chart-card {
  background: var(--v-well-bg);
  border: none;
  box-shadow: var(--v-pit);
}
[data-theme="velvet"] .result-number { color: var(--v-gold); }
[data-theme="velvet"] .number-words { color: var(--v-gold); }
[data-theme="velvet"] .field output { color: var(--v-gold); }

/* Micro labels: the rounded voice, wide-tracked uppercase. */
[data-theme="velvet"] .field-label {
  font-family: var(--v-font);
  font-size: var(--text-caption);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-muted);
}

/*
   Theme · Aero (light). <html data-theme="aero">.
   Frutiger Aero era: sky aqua, glass gloss, translucent plastic, pill
   buttons. Everything sits on a pale sky; controls are glass — a hard
   gloss line across the top half, a translucent white border, a soft
   blue glow under the one primary action. Accent-1 is glossy sky blue,
   accent-2 the translucent plastic fill, accent-3 the aero grass green
   that keeps AA as link text. The --a-* material tokens carry the
   glass itself; recipes below dress the components in it.
*/
[data-theme="aero"] {
  --background: #EAF6FD;      /* pale sky */
  --surface: #F7FCFF;         /* white glass panel */
  --text: #0C3049;            /* deep sea ink */
  --text-muted: #4E7189;      /* haze over water */
  --border: #BCDCEF;          /* sky hairline */

  --accent-1: #0B6FB8;        /* glossy sky blue, deep enough for white labels */
  --accent-1-hover: #0A64A6;
  --accent-1-pressed: #095996;
  --on-accent-1: #FFFFFF;

  --accent-2: #DCEFFA;        /* translucent plastic fill */
  --accent-2-hover: #CDE7F7;
  --accent-2-pressed: #BBDDF2;
  --on-accent-2: #0C3049;

  --accent-3: #0A7D5F;        /* aero grass green: AA as text on sky */
  --accent-3-hover: #086C52;
  --accent-3-pressed: #075C46;
  --on-accent-3: #FFFFFF;

  --success: #0D7042;
  --warning: #7F5D08;
  --error: #C03A2E;
  --info: #1273B8;

  /* Blue-tinted depth: aero shadows are sky, never soot. */
  --shadow-raised: 0 1px 2px rgb(12 48 73 / 0.10);
  --shadow-floating: 0 4px 14px rgb(12 48 73 / 0.14);
  --shadow-overlay: 0 12px 36px rgb(12 48 73 / 0.22);

  /* ----- The glass material ----- */
  /* The gloss is the era's signature: a bright top half ending in a
     hard line at 50%, a faint rise at the bottom edge. Layered as a
     background-image over any rank's fill. */
  --a-gloss: linear-gradient(180deg,
    rgb(255 255 255 / 0.80), rgb(255 255 255 / 0.28) 48%,
    rgb(255 255 255 / 0.00) 52%, rgb(255 255 255 / 0.16));
  --a-glass-border: rgb(255 255 255 / 0.65);
  --a-glass-bg: rgb(255 255 255 / 0.55);
  --a-glow: 0 2px 12px rgb(11 111 184 / 0.35);
}

/*
   Theme · Aero (dark). Composes: <html data-theme="aero" data-mode="dark">.
   Ocean glass: deep water ground, the same gloss dimmed to moonlight,
   sky blue lifted to stay the action color, grass green turned to
   sea glass.
*/
[data-theme="aero"][data-mode="dark"] {
  --background: #062A40;      /* deep water */
  --surface: #0B3A57;         /* submerged glass panel */
  --text: #E9F6FF;
  --text-muted: #8FB4CA;
  --border: #1D537A;

  --accent-1: #35B3F5;
  --accent-1-hover: #4FBEF8;
  --accent-1-pressed: #1FA0E4;
  --on-accent-1: #06283D;

  --accent-2: #0F476B;
  --accent-2-hover: #135380;
  --accent-2-pressed: #175E90;
  --on-accent-2: #E9F6FF;

  --accent-3: #43D6A9;        /* sea glass: AA as text on deep water */
  --accent-3-hover: #5DDFB6;
  --accent-3-pressed: #2FC497;
  --on-accent-3: #052E22;

  --success: #3FC98A;
  --warning: #E0B54D;
  --error: #F08A75;
  --info: #6FC2F2;

  --shadow-raised: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-floating: 0 4px 14px rgb(0 0 0 / 0.45);
  --shadow-overlay: 0 12px 36px rgb(0 0 0 / 0.55);

  --a-gloss: linear-gradient(180deg,
    rgb(255 255 255 / 0.30), rgb(255 255 255 / 0.10) 48%,
    rgb(255 255 255 / 0.00) 52%, rgb(255 255 255 / 0.06));
  --a-glass-border: rgb(255 255 255 / 0.28);
  --a-glass-bg: rgb(255 255 255 / 0.08);
  --a-glow: 0 2px 14px rgb(53 179 245 / 0.45);
}

/*
   Aero recipes: how components wear the glass. Pills everywhere, the
   gloss layered over each rank's own fill, a translucent white edge,
   and the primary action alone carries the blue glow. Tokens only —
   the ranks still come from the blocks above.
*/
[data-theme="aero"] .btn {
  border-radius: var(--radius-pill);
  border-color: var(--a-glass-border);
  background-image: var(--a-gloss);
  box-shadow: var(--shadow-raised), inset 0 1px 0 rgb(255 255 255 / 0.55);
}
[data-theme="aero"] .btn-primary {
  box-shadow: var(--a-glow), inset 0 1px 0 rgb(255 255 255 / 0.45);
  text-shadow: 0 1px 1px rgb(0 0 0 / 0.18);
}
[data-theme="aero"] .btn-ghost { background-image: none; }

/* Text inputs become glass capsules; the plastic look needs the frame. */
[data-theme="aero"] input:not(.slider),
[data-theme="aero"] select {
  border-radius: var(--radius-pill);
  border-color: var(--a-glass-border);
  background-color: var(--a-glass-bg);
  box-shadow: inset 0 1px 2px rgb(12 48 73 / 0.08);
}
[data-theme="aero"] textarea {
  border-color: var(--a-glass-border);
  background-color: var(--a-glass-bg);
}

/* Panels get the faint gloss too — chrome and cards read as plastic. */
[data-theme="aero"] .result,
[data-theme="aero"] .tab-bar,
[data-theme="aero"] .sheet,
[data-theme="aero"] .float-btn,
[data-theme="aero"] .card {
  background-image: var(--a-gloss);
  border-color: var(--a-glass-border);
}

/*
   Theme · Elegant (light). <html data-theme="elegant">.
   The old Gems signature carried forward: cream surfaces, gold
   hairlines, serif display type, carved elevation. Warm ivory ground,
   umber ink, antique gold as the only accent voice — accent-1 and
   accent-3 are golds, accent-2 the quiet cream fill. Headings switch
   to the serif stack through the --font-heading token alone; the
   --e-carve token presses panels gently INTO the cream instead of
   floating them above it.
*/
[data-theme="elegant"] {
  --background: #F7F2E9;      /* cream */
  --surface: #FDFAF3;         /* ivory panel */
  --text: #2B2417;            /* umber ink */
  --text-muted: #6F654E;      /* deepened for AA on cream */
  --border: #D6C08D;          /* the gold hairline */

  --accent-1: #8C6D1F;        /* antique gold: the one primary action */
  --accent-1-hover: #7A5F1B;
  --accent-1-pressed: #695217;
  --on-accent-1: #FFF8E7;

  --accent-2: #F1E9D6;        /* cream fill for every quiet action */
  --accent-2-hover: #EAE0C8;
  --accent-2-pressed: #DFD2B2;
  --on-accent-2: #2B2417;

  --accent-3: #7A5F1B;        /* deep gold: AA as text on cream */
  --accent-3-hover: #695217;
  --accent-3-pressed: #584512;
  --on-accent-3: #FFF8E7;

  --success: #516B31;         /* olive, AA on cream */
  --warning: #885D14;
  --error: #A44A35;           /* terracotta */
  --info: #5B6E8C;            /* slate */

  /* Warm depth, and shallow: elegance never casts a heavy shadow. */
  --shadow-raised: 0 1px 2px rgb(43 36 23 / 0.08);
  --shadow-floating: 0 4px 12px rgb(43 36 23 / 0.10);
  --shadow-overlay: 0 12px 32px rgb(43 36 23 / 0.18);

  /* Serif display voice — the token does all the work downstream.
     Stack ends in a generic family (font-list law). */
  --font-heading: "Iowan Old Style", "Palatino Linotype", Palatino,
                  Georgia, "Times New Roman", serif;

  /* ----- Carved elevation ----- */
  /* Panels are pressed into the cream: a soft interior top shadow and
     a bright lower lip, never a drop shadow. */
  --e-carve: inset 0 1px 3px rgb(43 36 23 / 0.12),
             inset 0 -1px 0 rgb(255 255 255 / 0.65);
}

/*
   Theme · Elegant (dark). Composes: <html data-theme="elegant"
   data-mode="dark">. The obsidian twin: near-black warm ground, lit
   gold for both accent voices, the hairlines dimmed to old brass.
*/
[data-theme="elegant"][data-mode="dark"] {
  --background: #14110B;      /* obsidian */
  --surface: #1E1910;
  --text: #F0E7D4;
  --text-muted: #A79A76;
  --border: #4A3E20;          /* old brass hairline */

  --accent-1: #D8B75B;        /* lit gold */
  --accent-1-hover: #E2C46E;
  --accent-1-pressed: #C9A748;
  --on-accent-1: #201806;

  --accent-2: #1E1910;
  --accent-2-hover: #272113;
  --accent-2-pressed: #322A17;
  --on-accent-2: #F0E7D4;

  --accent-3: #DCC077;        /* pale gold ink: AA on obsidian */
  --accent-3-hover: #E6CB88;
  --accent-3-pressed: #D0B364;
  --on-accent-3: #1A1204;

  --success: #8FAE5F;
  --warning: #CFA145;
  --error: #C97B62;
  --info: #8FA3C0;

  --shadow-raised: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-floating: 0 4px 12px rgb(0 0 0 / 0.45);
  --shadow-overlay: 0 12px 32px rgb(0 0 0 / 0.55);

  --e-carve: inset 0 1px 3px rgb(0 0 0 / 0.55),
             inset 0 -1px 0 rgb(255 255 255 / 0.07);
}

/*
   Elegant recipes: carved panels and hairline gold edges. The serif
   headings arrive through --font-heading with no rule at all.
*/
[data-theme="elegant"] .result,
[data-theme="elegant"] .sheet,
[data-theme="elegant"] .menu-item,
[data-theme="elegant"] .card {
  box-shadow: var(--e-carve);
}
[data-theme="elegant"] input:not(.slider),
[data-theme="elegant"] textarea,
[data-theme="elegant"] select {
  box-shadow: var(--e-carve);
}
/* Quiet actions carry the hairline instead of a filled edge. */
[data-theme="elegant"] .btn {
  border-color: var(--border);
}

/*
   The OS accent, as its own color (R2, reworked). --os-accent is a dormant,
   opt-in token: no component uses it, so nothing changes unless you write
   var(--os-accent) yourself. It defaults to the iOS system blue everywhere;
   where the browser exposes the real OS accent, it becomes that instead.

   Why it is opt-in and not the default accent-3: on iOS AccentColor is
   always the system blue, and on macOS almost nobody changes their accent,
   so wiring accent-3 to it made nearly every Apple user blue and every
   Chromium user ink — not the point of "wear the user's own accent". As a
   separate color, the effect is honest: blue by default for all, and a
   user's chosen accent only for the few on a supporting browser who set one.

   Support (probed, July 2026): AccentColor / AccentColorText resolve on
   Safari 16.4+ and Firefox 103+; Chromium does not support the keyword
   (CSS.supports returns false), so it stays on the iOS-blue fallback —
   which is exactly the intended default there. color-mix() over the keyword
   is safe in every engine that passes the gate.
*/
@supports (color: AccentColor) {
  :root {
    --os-accent: AccentColor;
    --os-accent-hover: color-mix(in srgb, AccentColor 86%, black);
    --os-accent-pressed: color-mix(in srgb, AccentColor 74%, black);
    --on-os-accent: AccentColorText;
  }
}

/*
   Reader adaptation (R3). The page bends to the reader, not the other
   way around — and it bends at the token layer, so every theme and
   every component adapts with zero extra code.
*/

/* Stronger contrast on request: muted text gives up its gray, borders
   climb toward the ink, the focus ring thickens. Formulas over the
   active theme's own tokens, so this holds in all five themes, light
   and dark. Placed after every theme block on purpose — same-or-higher
   specificity, later wins. */
@media (prefers-contrast: more) {
  :root,
  [data-mode],
  [data-theme],
  [data-theme][data-mode] {
    --text-muted: var(--text);
    --border: color-mix(in srgb, var(--text) 55%, var(--background));
    --focus-ring: calc(var(--border-focus) + 1px) solid var(--accent-3);
  }
}

/* Forced colors (Windows High Contrast): the OS repaints everything in
   its own palette. The audit fixes the pieces that vanish — elements
   whose shape was carried by a background alone get a real border, so
   they keep their outline in the system palette. */
@media (forced-colors: active) {
  .tab-indicator,
  .scroll-gauge,
  .scroll-gauge-thumb,
  .sheet,
  .nudge-card,
  .float-btn { border: 1px solid ButtonText; }
  .slider { border: 1px solid ButtonText; border-radius: var(--radius-pill); }
  [data-tip]::after { border: 1px solid CanvasText; }
}

/* Density: one attribute scales the whole spacing system at once. Small
   tightens, large opens up; medium is the default (no attribute). Works on
   html or any subtree — <html data-density="small">, a script-tag config
   key, or facet.set({density:"small"}). */
[data-density="small"] { --density-scale: 0.75; }
[data-density="large"] { --density-scale: 1.3; }

/* Type size: the same three-step control for type alone, independent of
   density. <html data-text-size="large">, or facet.set({textSize:"large"}). */
[data-text-size="small"] { --text-scale: 0.92; }
[data-text-size="large"] { --text-scale: 1.12; }

/* Screen-reader-only text: visually gone, fully read. The standard
   clip pattern; undo by removing the class, never by overriding. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Text roles: the type scale as classes, one per token, so any element
   can take a scale step without an inline style. Display and the
   heading steps carry the heading voice; the rest stay in the body
   voice. Same names as the tokens: .text-display, .text-h1 … .text-h4,
   .text-body, .text-quote, .text-footnote, .text-caption. */
.text-display { font-family: var(--font-heading); font-size: var(--text-display); font-weight: var(--weight-heading); line-height: 1.1; }
.text-h1      { font-family: var(--font-heading); font-size: var(--text-h1); font-weight: var(--weight-heading); line-height: var(--leading-heading); }
.text-h2      { font-family: var(--font-heading); font-size: var(--text-h2); font-weight: var(--weight-heading); line-height: var(--leading-heading); }
.text-h3      { font-family: var(--font-heading); font-size: var(--text-h3); font-weight: var(--weight-strong); line-height: var(--leading-heading); }
.text-h4      { font-family: var(--font-heading); font-size: var(--text-h4); font-weight: var(--weight-strong); line-height: var(--leading-heading); }
.text-body    { font-size: var(--text-body);    line-height: var(--leading-body); }
.text-quote   { font-size: var(--text-quote);   line-height: var(--leading-body); font-style: italic; }
.text-footnote   { font-size: var(--text-footnote);   line-height: 1.5; }
.text-caption { font-size: var(--text-caption); line-height: 1.4; color: var(--text-muted); }


/* ----- BACKGROUNDS: quiet patterns, one class, any theme -----
   What it is: whisper-faint page and section backgrounds drawn with
   CSS gradients only, colored by the tokens so every theme and mode
   gets them for free. What it is for: giving big quiet surfaces a
   material — a hero, a section divider zone, a whole page. When to
   use it: one class on a section or body; content sits straight on
   top. All patterns fade to nothing under prefers-contrast: more.
   The set: .bg-grid (technical drawing: plus marks at the grid
   intersections), .bg-dots (dot grid), .bg-ruled (writing lines),
   .bg-graph (graph paper, minor and major lines). */

.bg-grid,
.bg-dots,
.bg-ruled,
.bg-graph {
  --bg-ink: color-mix(in srgb, var(--text-muted) 28%, transparent);
  --bg-ink-faint: color-mix(in srgb, var(--text-muted) 14%, transparent);
  --bg-cell: 2.5rem;
}

/* Technical drawing: a plus mark at every grid intersection and
   nothing else. Built from four gradient layers — two full gridline
   layers underneath, two background-colored cover layers on top with
   a transparent window at each intersection, so only plus-length
   stubs of the lines show through. Gradients only, no images. */
.bg-grid {
  --bg-plus: 0.75rem;
  background-color: var(--background);
  background-image:
    /* cover with vertical windows (bounds the horizontal arms) */
    linear-gradient(90deg,
      var(--background) 0 calc(50% - var(--bg-plus) / 2),
      transparent 0 calc(50% + var(--bg-plus) / 2),
      var(--background) 0),
    /* cover with horizontal windows (bounds the vertical arms) */
    linear-gradient(180deg,
      var(--background) 0 calc(50% - var(--bg-plus) / 2),
      transparent 0 calc(50% + var(--bg-plus) / 2),
      var(--background) 0),
    /* the gridlines themselves, crossing at every tile center */
    linear-gradient(180deg,
      transparent 0 calc(50% - 0.5px), var(--bg-ink) 0 calc(50% + 0.5px), transparent 0),
    linear-gradient(90deg,
      transparent 0 calc(50% - 0.5px), var(--bg-ink) 0 calc(50% + 0.5px), transparent 0);
  background-size: var(--bg-cell) var(--bg-cell);
}

/* Dot grid: one soft dot per half cell. */
.bg-dots {
  background-color: var(--background);
  background-image: radial-gradient(circle, var(--bg-ink) 1px, transparent 1.5px);
  background-size: calc(var(--bg-cell) / 2) calc(var(--bg-cell) / 2);
}

/* Ruled: writing lines, generous leading. */
.bg-ruled {
  background-color: var(--background);
  background-image: linear-gradient(var(--bg-ink) 1px, transparent 1px);
  background-size: 100% 1.75rem;
}

/* Graph paper: faint minor squares, firmer major squares. */
.bg-graph {
  background-color: var(--background);
  background-image:
    linear-gradient(var(--bg-ink) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-ink) 1px, transparent 1px),
    linear-gradient(var(--bg-ink-faint) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-ink-faint) 1px, transparent 1px);
  background-size:
    var(--bg-cell) var(--bg-cell),
    var(--bg-cell) var(--bg-cell),
    calc(var(--bg-cell) / 5) calc(var(--bg-cell) / 5),
    calc(var(--bg-cell) / 5) calc(var(--bg-cell) / 5);
}

/* Contrast request: the whisper goes silent. */
@media (prefers-contrast: more) {
  .bg-grid, .bg-dots, .bg-ruled, .bg-graph { background-image: none; }
}


/*
   Motion personality · the spring, upgraded. Browsers that understand
   linear() get a real spring for --ease-spring: overshoot, then settle
   with momentum. Everyone else keeps the bouncy back-out fallback above.
*/
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(
      0, 0.06 2.5%, 0.235 6.3%, 0.575 12.6%, 0.819 17.6%, 1.003 22.6%,
      1.071 26.2%, 1.104 30%, 1.106 33.9%, 1.084 38.7%, 1.032 47.3%,
      1.003 54.5%, 0.995 62.5%, 1
    );
  }
}

/*
   Motion personality · Calm. <html data-motion="calm"> (or any subtree).
   Short, near-linear, slight — for products that should whisper. Same
   tokens, quieter values; no component changes a thing.
*/
[data-motion="calm"] {
  --duration-fast: 100ms;
  --duration-normal: 160ms;
  --duration-slow: 240ms;
  --ease-out: cubic-bezier(0.33, 0, 0.67, 1);
  --ease-in-out: cubic-bezier(0.33, 0, 0.67, 1);
  --ease-spring: cubic-bezier(0.33, 0, 0.67, 1);
}

/*
   Motion personality · Off. <html data-motion="off">. The explicit stop:
   everything snaps, nothing animates. prefers-reduced-motion (further
   down) does the same thing for visitors who asked the OS for it.
*/
[data-motion="off"],
[data-motion="off"] *,
[data-motion="off"] *::before,
[data-motion="off"] *::after {
  transition-duration: 0.01ms !important;
  animation-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

/*
   Seamless page transitions. <html data-transition="page">. Moving
   between pages of a Facet product stops looking like one page died and
   another loaded: the old page eases away, the new one springs in, and
   the URL changes normally — real multi-page navigation, no router.

   Built on cross-document view transitions. The at-rule below arms them
   for every Facet page; the animations only play on pages that opt in
   with the attribute (everyone else keeps an instant swap), only within
   the same origin, and they ride the motion personality tokens — Lively
   pages arrive with a settle, Calm pages slide quietly, data-motion="off"
   and prefers-reduced-motion snap. Browsers without support simply
   navigate: never broken, just instant.

   Per-link opt-out: <a data-no-transition> snaps the old page straight
   out (facet.js drops the attribute before leaving); the arrival is the
   destination page's own choice. facet.go(url) navigates with the same
   transition programmatically.
*/
@view-transition {
  navigation: auto;
}

/* Pages that did not opt in keep the instant swap they always had. */
:root:not([data-transition="page"])::view-transition-old(root),
:root:not([data-transition="page"])::view-transition-new(root) {
  animation-duration: 0s;
}

/* The opted-in move: old page eases up and away, new page springs in. */
:root[data-transition="page"]::view-transition-old(root) {
  animation: facet-page-out var(--duration-normal) var(--ease-in-out) both;
}

:root[data-transition="page"]::view-transition-new(root) {
  animation: facet-page-in var(--duration-slow) var(--ease-spring) both;
}

@keyframes facet-page-out {
  to { opacity: 0; translate: 0 calc(-1 * var(--space-stack)); }
}

@keyframes facet-page-in {
  from { opacity: 0; translate: 0 var(--space-card); }
}

/* Motion off — by attribute or by OS preference — means pages snap. */
:root[data-motion="off"]::view-transition-old(root),
:root[data-motion="off"]::view-transition-new(root) {
  animation-duration: 0s;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0s !important;
  }
}

/* The browser's own widgets (scrollbars, form controls) follow the mode. */
html[data-mode="dark"] { color-scheme: dark; }
html { color-scheme: light; }

/* A themed subtree (the attributes on any element below <html>) paints its
   own ground, so a dark panel on a light page reads as one. */
body [data-mode],
body [data-theme] {
  background: var(--background);
  color: var(--text);
}


/* ==========================================================================
   2. RESET
   Small on purpose: box sizing, margin zeroing, media defaults.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

/* Form elements inherit the page's typography instead of browser defaults. */
button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* Media never overflows its container. */
img,
video,
svg,
canvas {
  display: block;
  max-width: 100%;
  height: auto;
}

/* The hidden attribute always wins, even over components that set their
   own display (flex, grid). Without this, .result and .stack ignore it. */
[hidden] {
  display: none !important;
}


/* ==========================================================================
   3. BASE STYLES
   What every raw HTML tag looks like before any class is added.
   Write plain semantic HTML and it already looks like Facet.
   ========================================================================== */

/* ----- Page ----- */

html {
  background: var(--background);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: var(--weight-body);
  line-height: var(--leading-body);
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100dvh;
  font-size: var(--text-body);
}

/* ----- Headings ----- */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--weight-heading);
  line-height: var(--leading-heading);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-h3); }
h4, h5, h6 { font-size: var(--text-h4); }

/* ----- Text ----- */

p {
  max-width: 65ch;              /* comfortable reading measure by default */
  text-wrap: pretty;
}

small { font-size: var(--text-footnote); }

strong, b { font-weight: var(--weight-strong); }

a {
  color: var(--accent-3);       /* the quiet interactive rank, every theme */
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-color: var(--border);
  transition: text-decoration-color var(--duration-fast) var(--ease-out);
}

a:hover { text-decoration-color: currentColor; }

ul, ol {
  padding-left: var(--space-card);
}

li + li { margin-top: var(--space-tight); }

blockquote {
  padding-left: var(--space-stack);
  border-left: var(--border-highlight) solid var(--accent-3);
  color: var(--text-muted);
  font-style: italic;
}

hr {
  border: none;
  border-top: var(--border-hairline) solid var(--border);
}

/* ----- Code ----- */

code, kbd, samp, pre {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* Inline code gets a quiet chip; code inside pre stays bare. */
code {
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-control);
  padding: 0.1em 0.35em;
}

pre {
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  padding: var(--space-stack);
  overflow-x: auto;             /* wrapper law: this container exists to scroll */
}

pre code {
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
}

/* ----- Forms ----- */

label {
  display: block;
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
}

input,
textarea,
select {
  width: 100%;
  min-height: 44px;             /* touch target */
  padding: var(--space-control) var(--space-inline);
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-control);
  transition: border-color var(--duration-fast) var(--ease-out);
}

input:hover,
textarea:hover,
select:hover {
  border-color: var(--text-muted);
}

input:disabled,
textarea:disabled,
select:disabled,
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

::placeholder {
  color: var(--text-muted);
  opacity: 1;
}

textarea {
  min-height: calc(var(--space-hero) * 2);
  resize: vertical;
}

fieldset {
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  padding: var(--space-stack);
}

legend {
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
  padding-inline: var(--space-control);
}

/* Bare buttons pick up the secondary button look; see COMPONENTS for .btn. */
button {
  cursor: pointer;
}

/* ----- Tables ----- */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-footnote);
}

th, td {
  text-align: left;
  padding: var(--space-control) var(--space-inline);
  border-bottom: var(--border-hairline) solid var(--border);
}

th {
  font-weight: var(--weight-strong);
  color: var(--text-muted);
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

tbody tr:hover {
  background: var(--surface);
}

/* ----- Figures ----- */

figure img,
figure video {
  border-radius: var(--radius-panel);
}

figcaption {
  margin-top: var(--space-control);
  font-size: var(--text-caption);
  color: var(--text-muted);
}

/* ----- Focus rings ----- */
/* One visible ring everywhere, keyboard only. Never remove, only restyle. */

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-control);
}

/* ----- Text selection ----- */

::selection {
  background: var(--accent-3);
  color: var(--on-accent-3);
}

/* ----- Scrollbars ----- */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

/* ----- Skip-to-content link ----- */
/* First element inside <body> on every page:
   <a class="skip-link" href="#main">Skip to content</a> */

.skip-link {
  position: absolute;
  top: var(--space-control);
  left: var(--space-control);
  z-index: 100;
  padding: var(--space-control) var(--space-stack);
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-floating);
  transform: translateY(-200%);   /* parked offscreen until focused */
  transition: transform var(--duration-normal) var(--ease-spring);
}

.skip-link:focus {
  transform: none;
}

/* ----- Print base ----- */

@media print {
  html {
    background: white;
    color: black;
  }

  nav, .skip-link, [data-no-print] {
    display: none;
  }
}

/* ----- Reduced motion ----- */
/* Every transition and animation in the library collapses to nothing. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   4. COMPONENTS
   One section per component. Each section opens with the component's
   canonical description — the same text, word for word, as its entry on
   the library site and in llms.txt — then how to use it and its options.
   ========================================================================== */

/* --------------------------------------------------------------------------
   CONTAINER

   What it is: a page-width wrapper with themed padding. It centers content,
   caps its width, and keeps consistent side padding from the spacing tokens.
   What it is for: giving every page the same horizontal rhythm, driven by
   tokens, so a theme flip restyles the layout too, not just the components
   inside it.
   When to use it: put it directly on the top-level semantic tag of any
   ordinary page — main, header, footer, section. Use .container-narrow for
   prose, forms and calculators, .container for most pages, .container-wide
   for dashboards, .container-full for edge-to-edge strips that keep side
   padding. Never nest a container inside a container.

   Usage:  <main class="container"> ... </main>
   Options: .container-narrow  40rem   .container       64rem
            .container-wide    80rem   .container-full  edge to edge
   -------------------------------------------------------------------------- */

.container,
.container-narrow,
.container-wide,
.container-full {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--space-stack);
}

.container { max-width: var(--width-default); }
.container-narrow { max-width: var(--width-narrow); }
.container-wide { max-width: var(--width-wide); }

@media (min-width: 640px) {
  .container,
  .container-narrow,
  .container-wide,
  .container-full {
    padding-inline: var(--space-page);
  }
}

/* --------------------------------------------------------------------------
   STACK

   What it is: vertical flow with themed gaps. A stack says one thing: these
   sit above and below each other, this far apart.
   What it is for: spacing content without per-element margins. The gaps come
   from the spacing tokens, so a theme change restyles the rhythm of the
   whole page.
   When to use it: .stack is the default for content inside a section.
   Use .stack-tight for lines that belong together, like a label and its
   field. Use .stack-loose between whole sections of a page.

   Usage:  <section class="stack"> ... </section>
   Options: .stack-tight  --space-control    .stack  --space-stack
            .stack-loose  --space-section
   -------------------------------------------------------------------------- */

.stack,
.stack-tight,
.stack-loose {
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
}

.stack-tight { gap: var(--space-control); }
.stack-loose { gap: var(--space-section); }

/* --------------------------------------------------------------------------
   SNAP SECTION

   What it is: full-viewport scroll-snap areas. The .snap wrapper scrolls one
   full screen at a time and each .snap-section centers its content at a
   readable width.
   What it is for: the calculator page skeleton — intro screen, then inputs,
   then results — and any page that tells one story one screen at a time.
   When to use it: single-purpose pages with a clear screen-by-screen flow.
   Not for ordinary documents; normal pages just scroll. The .snap wrapper
   exists to enable scrolling, which is the one reason the wrapper law
   allows a container inside a container.

   Usage:  <main class="snap">
             <section class="snap-section"> intro </section>
             <section class="snap-section"> inputs </section>
             <section class="snap-section"> results </section>
           </main>
   -------------------------------------------------------------------------- */

.snap {
  height: 100dvh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
}

.snap-section {
  min-height: 100dvh;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-card);
  padding: var(--space-page) var(--space-stack);
}

/* Keep content inside a snap section at a readable width, like a container. */
.snap-section > * {
  width: 100%;
  max-width: var(--width-narrow);
  margin-inline: auto;
}

/* THE PAGER MODEL (.is-paged, added by facet.js on full-viewport snaps).
   The law, learned the hard way on iOS: CSS mandatory snap re-snaps any
   section taller than the viewport and wedges the next gesture;
   proximity doesn't snap; free-scrolling outers fight their sections.
   So with JS present the outer NEVER free-scrolls — facet.js springs
   its scrollTop between section tops — and every section is exactly one
   viewport tall, scrolling natively inside itself. Without JS the plain
   CSS snapping above still works. */
.snap.is-paged {
  scroll-snap-type: none;
  overflow-y: hidden;
  overflow-x: hidden;
  scroll-behavior: auto;        /* the pager sets scrollTop per frame */
  overscroll-behavior-y: contain;
  scrollbar-width: none;
}
.snap.is-paged::-webkit-scrollbar { display: none; }
.snap.is-paged,
.snap.is-paged .snap-section { touch-action: pan-y; }
.snap.is-paged .snap-section {
  height: 100dvh;
  min-height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  justify-content: flex-start;  /* centering via auto margins below: a
                                   centered flex column would clip the
                                   top of overflowing content */
}
.snap.is-paged .snap-section::-webkit-scrollbar { display: none; }
.snap.is-paged .snap-section > :first-child { margin-top: auto; }
.snap.is-paged .snap-section > :last-child { margin-bottom: auto; }

/* --------------------------------------------------------------------------
   BUTTON

   What it is: the action element, in three variants and three sizes, working
   identically on <button> and on <a>.
   What it is for: .btn-primary carries the screen's one main action in the
   accent color. .btn-secondary is every other action. .btn-ghost is for
   quiet actions inside busy areas, invisible until hovered.
   When to use it: any click that does something. One primary per screen at
   most. Navigation dressed as a button is an <a class="btn">. Put the
   button's key action name in a data-event attribute as the analytics hook.

   Usage:  <button class="btn btn-primary" data-event="calc-run"
                   data-tip="Runs the calculation">Calculate</button>
   Variants: .btn-primary  .btn-secondary (default for bare .btn)  .btn-ghost
   Sizes:    .btn-small  .btn (default)  .btn-large
   States:   hover, focus ring, pressed, :disabled — all built in.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-control);
  min-height: 44px;                       /* touch target */
  padding: var(--space-control) var(--space-card);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-control);
  background: var(--accent-2);
  color: var(--on-accent-2);
  font-weight: var(--weight-strong);
  text-decoration: none;                  /* .btn also works on <a> */
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    transform var(--duration-normal) var(--ease-spring);
}

/* Press with weight; release springs back with the personality. */
.btn:active { transform: translateY(1px) scale(0.97); }

/* Primary: filled with accent-1. One per screen. */
.btn-primary {
  background: var(--accent-1);
  border-color: var(--accent-1);
  color: var(--on-accent-1);
  box-shadow: var(--shadow-raised);
}

.btn-primary:hover { background: var(--accent-1-hover); border-color: var(--accent-1-hover); }
.btn-primary:active { background: var(--accent-1-pressed); border-color: var(--accent-1-pressed); }

/* Secondary: the accent-2 fill with a hairline. The default look. */
.btn-secondary:hover,
.btn:not(.btn-primary):not(.btn-ghost):hover {
  background: var(--accent-2-hover);
  border-color: var(--text-muted);
}

.btn-secondary:active,
.btn:not(.btn-primary):not(.btn-ghost):active {
  background: var(--accent-2-pressed);
}

/* Ghost: no fill, no border, until hovered. */
.btn-ghost {
  background: transparent;
  border-color: transparent;
}

.btn-ghost:hover {
  background: var(--accent-2);
  border-color: var(--border);
}

.btn-ghost:active {
  background: var(--accent-2-hover);
}

/* Sizes. Small keeps the 44px touch target via min-height on mobile. */
.btn-small {
  padding: var(--space-tight) var(--space-inline);
  font-size: var(--text-footnote);
  min-height: 36px;
}

.btn-large {
  padding: var(--space-inline) var(--space-page);
  font-size: var(--text-h4);
}

@media (pointer: coarse) {
  .btn-small { min-height: 44px; }        /* fingers get the full target */
}

/* --------------------------------------------------------------------------
   ICON BUTTON

   What it is: a square button holding a single icon.
   What it is for: compact actions whose meaning the icon plus its tooltip
   carries — the clear cross in a number field, a close button, a share
   arrow.
   When to use it: when space is tight and the action is secondary. The icon
   alone says nothing, so every icon button carries an aria-label for screen
   readers and a data-tip tooltip for everyone else. Combines with any
   button variant and size. Icons are inline SVG with stroke="currentColor"
   so they take the button's text color in every theme.

   Usage:  <button class="btn btn-icon" aria-label="Close"
                   data-tip="Close this panel"> <svg>...</svg> </button>
   -------------------------------------------------------------------------- */

.btn-icon {
  width: 44px;
  min-width: 44px;
  padding: 0;
}

.btn-icon.btn-small { width: 36px; min-width: 36px; }
.btn-icon.btn-large { width: 56px; min-width: 56px; }

/* --------------------------------------------------------------------------
   FIELD + NUMBER INPUT

   What it is: a labelled field wrapping the calculator-grade number input,
   with a clear button inside the field and a words helper under it.
   What it is for: money and other large numbers, entered the way the
   visitor says them. The input ships prefilled with an example value,
   clears fully on the first tap — and never again once the visitor has
   typed — groups digits for the locale as they are typed (25,00,000 where
   lakh and crore are the norm, 2,500,000 elsewhere), and reads the number
   back in words underneath, in the active language: 25 lakh, 2.5 million,
   25 लाख. The locale follows facet.location and the browser, overridable
   with data-numbers="indian|western"; the language with data-language.
   All behaviours come from facet.js the moment the input carries
   data-number.
   When to use it: every numeric entry in a calculator. Read the plain
   number in app code with facet.numberValue(input). For the error state,
   put .field-invalid on the field and swap the hint for a .field-error
   line.

   Usage:
     <label class="field">
       <span class="field-label">Monthly investment</span>
       <span class="number-input">
         <input type="text" inputmode="numeric" value="25000" data-number>
         <button type="button" class="btn btn-ghost btn-icon btn-small number-clear"
                 aria-label="Clear" data-tip="Clear this field">&times;</button>
       </span>
       <span class="number-words" aria-live="polite"></span>
       <span class="field-hint">What you invest every month</span>
     </label>

   The .number-input wrapper exists to overlay the clear button on the
   input — a real layout job, allowed by the wrapper law.
   -------------------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}

.field-label {
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
}

.field-hint {
  font-size: var(--text-caption);
  color: var(--text-muted);
  font-weight: var(--weight-body);
}

.field-error {
  font-size: var(--text-caption);
  color: var(--error);
  font-weight: var(--weight-body);
}

.field-invalid input,
.field-invalid textarea,
.field-invalid select {
  border-color: var(--error);
}

/* Select wears a token-colored chevron (two tiny gradients — no
   image files, ever) once the native arrow is dropped. */
select {
  appearance: none;
  padding-right: var(--space-section);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 1.15rem) 50%,
    calc(100% - 0.8rem) 50%;
  background-size: 0.35rem 0.35rem;
  background-repeat: no-repeat;
}

/* Search reads as search: a capsule, native clear button kept. */
input[type="search"] { border-radius: var(--radius-pill); }

/* File upload: the native input stays (drag-drop, keyboard, form
   value all free); only its button dresses like a secondary action
   and the filename text steps back. */
input[type="file"] {
  border: none;
  background: none;
  padding: var(--space-tight) 0;
  font-size: var(--text-footnote);
  color: var(--text-muted);
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  margin-right: var(--space-inline);
  padding: var(--space-control) var(--space-stack);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  background: var(--accent-2);
  color: var(--on-accent-2);
  font: inherit;
  font-weight: var(--weight-strong);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}
input[type="file"]:hover::file-selector-button { background: var(--accent-2-hover); }

/* Date and time: native pickers, themed — accent-color colors the
   picker highlight, color-scheme already flips it in dark mode. */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"],
input[type="week"] {
  accent-color: var(--accent-1);
}

/* The input + clear button overlay. */
.number-input {
  position: relative;
  display: block;
}

.number-input input {
  padding-right: var(--space-hero);          /* room for the clear button */
  font-size: var(--text-h4);              /* numbers deserve to be legible */
  font-variant-numeric: tabular-nums;     /* digits align while typing */
}

.number-input .number-clear {
  position: absolute;
  top: 50%;
  right: var(--space-tight);
  transform: translateY(-50%);
  color: var(--text-muted);
}

.number-input .number-clear:active { transform: translateY(-50%); }

/* Hide the clear button while the field is empty: nothing to clear. */
.number-input:has(input:placeholder-shown) .number-clear {
  display: none;
}

/* The words helper under the input: "25 lakh", "1.2 crore". */
.number-words {
  min-height: 1.2em;                      /* reserves the line: no layout jump */
  font-size: var(--text-caption);
  color: var(--accent-3);
  font-weight: var(--weight-strong);
}

/* --------------------------------------------------------------------------
   SLIDER

   What it is: a real range input wearing the theme — hairline track,
   accent-3 thumb — paired with a live value readout.
   What it is for: picking a number inside a known range where the feel of
   more-and-less matters more than exact digits: years, percentages, counts.
   When to use it: bounded inputs with sensible ends. When the exact number
   matters, pair it with or swap it for the number input. Put an <output>
   in the field label and facet.js keeps it showing the live value. Full
   keyboard support comes free with the native element.

   Usage:
     <label class="field">
       <span class="field-label">Years <output>10</output></span>
       <input type="range" class="slider" min="1" max="40" value="10"
              data-tip="Drag, or use the arrow keys">
     </label>
   -------------------------------------------------------------------------- */

.slider {
  appearance: none;
  -webkit-appearance: none;
  min-height: 44px;                       /* touch target around the track */
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
}

/* Track: a quiet hairline. */
.slider::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--border);
  border-radius: var(--radius-pill);
}

.slider::-moz-range-track {
  height: 4px;
  background: var(--border);
  border-radius: var(--radius-pill);
}

/* Filled part of the track, Firefox only (WebKit has no equivalent). */
.slider::-moz-range-progress {
  height: 4px;
  background: var(--accent-3);
  border-radius: var(--radius-pill);
}

/* Thumb: accent circle, grows slightly on hover. */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 24px;
  height: 24px;
  margin-top: -10px;                      /* centers on the 4px track */
  background: var(--accent-3);
  border: var(--border-highlight) solid var(--surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  transition: transform var(--duration-normal) var(--ease-spring);
}

.slider::-moz-range-thumb {
  width: 24px;
  height: 24px;
  background: var(--accent-3);
  border: var(--border-highlight) solid var(--surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  transition: transform var(--duration-normal) var(--ease-spring);
}

.slider:hover::-webkit-slider-thumb { transform: scale(1.15); }
.slider:hover::-moz-range-thumb { transform: scale(1.15); }
.slider:active::-webkit-slider-thumb { background: var(--accent-3-pressed); }
.slider:active::-moz-range-thumb { background: var(--accent-3-pressed); }

/* The live value readout inside the field label. */
.field output {
  float: right;                           /* sits at the label's right edge */
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   DETAILED SLIDER

   What it is: a slider for a wide range, paired with a precise entry. The
   track drags fast in coarse jumps (data-coarse) so you cross a big span in
   one swipe; the −/+ steppers and the number box reach the exact value
   between the jumps (data-fine). All three stay in sync.
   What it is for: values with many gradations where you want both speed and
   precision — a price, a duration in seconds, a token in pixels.
   When to use it: ranges too wide for a plain slider to land precisely. For
   a small bounded range, the plain .slider is enough.

   Usage:
     <div class="slider-detail" data-min="0" data-max="1000"
          data-coarse="20" data-fine="1">
       <input type="range" class="slider" min="0" max="1000" step="1"
              value="200" aria-label="Price">
       <div class="slider-detail-entry">
         <button type="button" class="btn btn-icon slider-detail-step"
                 data-step="-1" aria-label="Decrease">&minus;</button>
         <input type="number" class="slider-detail-number" min="0" max="1000"
                value="200" aria-label="Exact price">
         <button type="button" class="btn btn-icon slider-detail-step"
                 data-step="1" aria-label="Increase">+</button>
       </div>
     </div>
   facet.js wires it and fires a bubbling facet:slide {value} on any change.
   -------------------------------------------------------------------------- */
.slider-detail {
  display: flex;
  align-items: center;
  gap: var(--space-inline);
  flex-wrap: wrap;
}
.slider-detail .slider { flex: 1 1 8rem; min-width: 6rem; }
.slider-detail-entry {
  display: inline-flex;
  align-items: center;
  gap: var(--space-tight);
}
.slider-detail-number {
  width: 4.5rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  /* the −/+ buttons replace the native spinner */
  -moz-appearance: textfield;
}
.slider-detail-number::-webkit-inner-spin-button,
.slider-detail-number::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.slider-detail-step { font-size: var(--text-h4); line-height: 1; }

/* --------------------------------------------------------------------------
   RESULT BLOCK

   What it is: a verdict-first result card: one plain-language sentence a
   person can repeat at dinner, one big number, then the supporting figures.
   What it is for: the centrepiece of a results screen — readable at a
   glance, and announced by screen readers on every recalculation when the
   block carries aria-live="polite".
   When to use it: whenever a calculation produces one headline answer.
   Leave out the figures list when there is nothing to support the verdict.
   For the empty state before any input, show a muted verdict line alone.

   Usage:
     <section class="result" aria-live="polite">
       <p class="result-verdict">Your money halves in value every 10 years</p>
       <p class="result-number">&#8377;46,319</p>
       <dl class="result-figures">
         <div><dt>Total invested</dt><dd>&#8377;12,00,000</dd></div>
         <div><dt>Real return</dt><dd>4.1% a year</dd></div>
       </dl>
     </section>

   The <dl> is the semantic fit for label–value pairs; each <div> row is
   grouping the dl spec itself provides, not decoration.
   -------------------------------------------------------------------------- */

.result {
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-raised);
  padding: var(--space-page);
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
  text-align: center;
}

.result-verdict {
  font-size: var(--text-h3);
  font-weight: var(--weight-strong);
  margin-inline: auto;
  text-wrap: balance;
}

.result-number {
  font-size: var(--text-display);
  font-weight: var(--weight-heading);
  line-height: var(--leading-heading);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.result-figures {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-control) var(--space-page);
  border-top: var(--border-hairline) solid var(--border);
  padding-top: var(--space-stack);
}

.result-figures dt {
  font-size: var(--text-caption);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.result-figures dd {
  margin: 0;
  font-size: var(--text-h4);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   DESCRIPTION TOOLTIP

   What it is: a one-attribute explanation bubble: put data-tip on any
   element and the text appears above it on hover and on keyboard focus.
   Pure CSS, no JS involved.
   What it is for: explaining every interactive element in place — one of
   the library's core principles. The description doubles as documentation
   anyone can read by pointing at the thing.
   When to use it: every interactive element ships with one. Add
   data-tip-below on elements near the top of the viewport so the bubble
   drops under them instead of clipping off the screen.

   Usage:  <button class="btn" data-tip="Runs the calculation">Calculate</button>

   Limits (fine for v1): single line, clipped by overflow:hidden ancestors.
   TODO: JS-positioned tooltips once a real page hits the clipping limit.
   -------------------------------------------------------------------------- */

[data-tip] {
  position: relative;
}

[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + var(--space-control));
  left: 50%;
  translate: -50% 2px;
  z-index: 10;
  width: max-content;
  max-width: 16rem;
  padding: var(--space-tight) var(--space-inline);
  background: var(--text);
  color: var(--background);
  font-size: var(--text-caption);
  font-weight: var(--weight-body);
  text-align: center;
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-floating);
  /* Hidden bubbles leave layout entirely — an opacity-0 box hanging
     off a right-edge control would widen the whole page on phones. */
  display: none;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    translate var(--duration-normal) var(--ease-spring);
}

[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  display: block;
  opacity: 1;
  translate: -50% 0;
}
/* The frame the bubble transitions FROM on the display flip. */
@starting-style {
  [data-tip]:hover::after,
  [data-tip]:focus-visible::after {
    opacity: 0;
    translate: -50% 2px;
  }
}

/* Elements near the top of the viewport add data-tip-below, so the tip
   drops under them instead of clipping off the screen. */
[data-tip][data-tip-below]::after {
  bottom: auto;
  top: calc(100% + var(--space-control));
}

/* The tail: a small triangle under the bubble, flipping with
   data-tip-below. */
[data-tip]::before {
  content: "";
  position: absolute;
  bottom: calc(100% + var(--space-control) - 5px);
  left: 50%;
  translate: -50% 2px;
  z-index: 10;
  border: 5px solid transparent;
  border-top-color: var(--text);
  display: none;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    translate var(--duration-fast) var(--ease-out);
}
[data-tip][data-tip-below]::before {
  bottom: auto;
  top: calc(100% + var(--space-control) - 5px);
  border-top-color: transparent;
  border-bottom-color: var(--text);
}
[data-tip]:hover::before,
[data-tip]:focus-visible::before { display: block; opacity: 1; translate: -50% 0; }
@starting-style {
  [data-tip]:hover::before,
  [data-tip]:focus-visible::before { opacity: 0; translate: -50% 2px; }
}

/* Touch: iOS applies a STICKY :hover on tap that never clears, so on
   touch devices the hover bubble is forced off and facet.js peeks the
   tip via .tip-on for a few seconds instead (initTouchPolish). */
@media (hover: none) {
  [data-tip]:hover::after,
  [data-tip]:hover::before { display: none; opacity: 0; }
}
[data-tip].tip-on::after,
[data-tip].tip-on::before { display: block; opacity: 1; translate: -50% 0; }

/* ----- CHOICE CONTROLS: checkbox, radio, switch -----
   What it is: the yes/no and pick-one controls — native checkbox and
   radio wearing the theme through accent-color, and a switch that is
   a checkbox upgraded with one class. What it is for: settings,
   consents, filters — checkbox for independent yes/nos, radio for
   one-of-few with all options visible, switch for a setting that
   takes effect immediately. When to use it: wrap control and text in
   a label.check-row so the whole line is the hit target. The switch
   keeps checkbox semantics — form value, keyboard, screen readers —
   the class only changes the clothes. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent-1);
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}

/* The whole line is the target: 44px tall, gap from the scale. */
.check-row {
  display: flex;
  align-items: center;
  gap: var(--space-inline);
  min-height: 2.75rem;
  cursor: pointer;
}
.check-row:has(input:disabled) { cursor: not-allowed; color: var(--text-muted); }

/* The switch: a checkbox in track-and-thumb clothes. */
input[type="checkbox"].switch {
  appearance: none;
  position: relative;
  width: 2.75rem;
  height: 1.625rem;
  border-radius: var(--radius-pill);
  background: var(--border);
  transition: background var(--duration-fast) var(--ease-out);
  touch-action: manipulation;
  cursor: pointer;
}
input[type="checkbox"].switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(1.625rem - 4px);
  height: calc(1.625rem - 4px);
  border-radius: var(--radius-pill);
  background: var(--background);
  box-shadow: var(--shadow-raised);
  transition: translate var(--duration-base) var(--ease-spring);
}
input[type="checkbox"].switch:checked { background: var(--accent-1); }
input[type="checkbox"].switch:checked::before { translate: 1.125rem 0; }
input[type="checkbox"].switch:disabled { opacity: 0.5; cursor: not-allowed; }

/* ----- MODAL, DRAWER, POPOVER: the native top layer, dressed -----
   What it is: overlays built on the platform's own machinery — modal
   and drawer are <dialog> (focus trap, Esc, inert page for free),
   popover is the popover attribute (top layer, light dismiss, no JS).
   What it is for: modal for a decision that blocks the page, drawer
   for a side panel of details or filters, popover for small floating
   bits — hints, mini menus. When to use it: open a dialog from any
   button whose aria-controls names it (facet.js calls showModal);
   close with a <form method="dialog"> button inside, Esc, or a
   backdrop click. Popovers need only popovertarget on the button.
   The app-kit .sheet stays the settings panel for app pages; the
   drawer is the document-world sibling. */
/* A closed dialog must stay hidden even when a layout class on it (e.g.
   .stack, which sets display:flex) would otherwise beat the UA's
   dialog:not([open]) { display:none }. Without this, a <dialog class="modal
   stack"> renders in the flow while closed and its backdrop stacks up. */
dialog:not([open]) { display: none; }

dialog.modal,
dialog.drawer {
  border: none;
  padding: var(--space-page);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-overlay);
}
dialog.modal {
  width: min(92vw, 34rem);
  border-radius: var(--radius-card);
  margin: auto;                   /* centre it — the * { margin: 0 } reset
                                     wipes the UA's centring margin, so a
                                     modal without this pins to the corner */
}
dialog.drawer {
  margin: 0 0 0 auto;             /* pinned to the right edge */
  width: min(92vw, 24rem);
  height: 100dvh;
  max-height: 100dvh;
  border-radius: var(--radius-card) 0 0 var(--radius-card);
}
dialog.modal::backdrop,
dialog.drawer::backdrop {
  background: rgb(0 0 0 / 0.5);
  backdrop-filter: blur(3px);
}
/* Arrive with the motion personality; leave instantly. */
dialog.modal[open] { animation: facet-modal-in var(--duration-base) var(--ease-spring); }
dialog.drawer[open] { animation: facet-drawer-in var(--duration-base) var(--ease-spring); }
@keyframes facet-modal-in { from { opacity: 0; translate: 0 14px; } }
@keyframes facet-drawer-in { from { translate: 100% 0; } }

[popover] {
  margin: auto;                   /* centered fallback where no anchor */
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  padding: var(--space-stack);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-floating);
}
[popover]:popover-open { animation: facet-modal-in var(--duration-fast) var(--ease-out); }
/* Layout utilities (display: flex/grid) must never beat the UA's
   closed-popover hiding — re-assert it at author origin. */
[popover]:not(:popover-open) { display: none; }

/* ----- BLOCK HELPERS: the few styles the block layer adds -----
   Blocks are assemblies of existing components — cards in grids,
   fields in forms, links in navs — documented as copyable patterns
   on the homepage. Most need no CSS at all; these are the handful of
   real jobs the assemblies add: a horizontal card scroller, the
   collapsing top menu, hero and CTA voices, stat cards, and the two
   editorial pieces (pull quote, media figure). */

/* Horizontal scrolling card row: side-scrolls with momentum, cards
   keep a readable width, gentle snap per card (proximity is safe
   here — cards never outgrow the viewport; the pager law is about
   full-page sections). */
.card-row {
  display: flex;
  gap: var(--space-stack);
  overflow-x: auto;
  padding-bottom: var(--space-control);   /* room for the scrollbar */
  scroll-snap-type: x proximity;
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
}
.card-row > * {
  flex: 0 0 min(16rem, 78vw);
  scroll-snap-align: start;
}

/* Top menu: logo, links, actions; on phones the links fold behind a
   native details disclosure — no JS, no hamburger framework. */
.top-menu {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  flex-wrap: wrap;
  padding-block: var(--space-inline);
  border-bottom: var(--border-hairline) solid var(--border);
}
.top-menu .wordmark-slot {
  font-weight: var(--weight-heading);
  font-size: var(--text-h4);
  margin-right: auto;
}
.top-menu-links { display: flex; gap: var(--space-tight); flex-wrap: wrap; }
.top-menu-fold { display: none; }
@media (max-width: 40rem) {
  .top-menu-links { display: none; }
  .top-menu-fold { display: block; width: 100%; }
  .top-menu-fold summary { list-style: none; cursor: pointer; }
  .top-menu-fold summary::-webkit-details-marker { display: none; }
  .top-menu-fold[open] .top-menu-links,
  .top-menu-fold .top-menu-links { display: flex; flex-direction: column; padding-top: var(--space-control); }
}

/* Hero: the opening statement — display type, one line under it,
   one primary action. */
.hero {
  display: grid;
  gap: var(--space-stack);
  justify-items: start;
  padding-block: var(--space-section);
}
.hero h1, .hero .text-display { margin: 0; }

/* CTA band: the closing ask — an ink-filled strip that flips the
   tokens so everything inside just works. */
.cta-band {
  display: grid;
  gap: var(--space-inline);
  justify-items: center;
  text-align: center;
  padding: var(--space-section) var(--space-card);
  border-radius: var(--radius-card);
  background: var(--accent-1);
  color: var(--on-accent-1);
}

/* Stat cards: one number that matters, its label under it. */
.stat {
  display: grid;
  gap: var(--space-tight);
  padding: var(--space-stack) var(--space-card);
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-card);
}
.stat strong {
  font-size: var(--text-h2);
  font-weight: var(--weight-heading);
  font-variant-numeric: tabular-nums;
}
.stat span { font-size: var(--text-caption); color: var(--text-muted); }

/* Pull quote: the sentence worth stealing, set large. */
.pullquote {
  border-left: var(--border-highlight) solid var(--accent-3);
  padding-left: var(--space-card);
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  font-weight: var(--weight-heading);
  font-style: normal;
  color: var(--text);
}
.pullquote footer {
  margin-top: var(--space-control);
  font-size: var(--text-footnote);
  font-weight: var(--weight-body);
  color: var(--text-muted);
}

/* Media figure: an image that explains, with its caption attached. */
.media-figure { margin: 0; }
.media-figure img,
.media-figure video {
  width: 100%;
  border-radius: var(--radius-panel);
  border: var(--border-element) solid var(--border);
}
.media-figure figcaption {
  margin-top: var(--space-control);
  font-size: var(--text-caption);
  color: var(--text-muted);
}

/* ----- MOTION PACK: parallax depths and idle life -----
   What it is: alive by default. data-parallax="depth" lets an
   element drift up to that many pixels behind the pointer (desktop),
   the scroll's momentum (everywhere) or the device's tilt (where no
   permission gate blocks it) — facet.motion is the engine. The
   idle-* classes give key elements a resting pulse: a slow float, a
   faint sway, a soft breathing scale. What it is for: heroes,
   illustrations, decorative cards — life at the edges, never on the
   reading path. When to use it: sparingly; one or two moving things
   per screen. Idle classes animate the translate/rotate/scale
   properties, so they compose with the engine's transform instead of
   colliding (the parallax exclusion, solved structurally). Calm
   personality stills idle life; off and prefers-reduced-motion still
   everything. */
.idle-float { animation: facet-idle-float 5s ease-in-out infinite alternate; }
.idle-sway  { animation: facet-idle-sway 7s ease-in-out infinite alternate; }
.idle-pulse { animation: facet-idle-pulse 4s ease-in-out infinite alternate; }
@keyframes facet-idle-float { from { translate: 0 2px; } to { translate: 0 -4px; } }
@keyframes facet-idle-sway  { from { rotate: -1.2deg; } to { rotate: 1.2deg; } }
@keyframes facet-idle-pulse { from { scale: 1; } to { scale: 1.02; } }
[data-motion="calm"] .idle-float,
[data-motion="calm"] .idle-sway,
[data-motion="calm"] .idle-pulse { animation: none; }

/* ----- ICONS: forty thin line glyphs on one attribute -----
   What it is: the icon set — forty-eight 24x24 glyphs at a 1.5px
   stroke with round caps, carried as markup in facet.js (no image files, no
   sprite fetch). What it is for: every small symbol a product needs:
   navigation, actions, states. When to use it: write
   <svg data-icon="search"></svg> and facet.js fills it; icons ride
   currentColor and scale with the text around them. Decorative by
   default (aria-hidden); give the svg an aria-label to make it
   meaningful. Extend the set in one line: facet.icons.rocket =
   '<path d="..."/>' then facet.icon(). */
svg[data-icon] {
  width: 1.25em;
  height: 1.25em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.25em;
  flex: none;
}

/* ----- FLAGSHIP LINK: the signature way to another page -----
   What it is: the big confident link — heading voice, a strong
   accent underline, and an arrow that leans into the journey on
   hover. What it is for: the one onward step at the end of a
   section: "See the full report", "Start a project". When to use
   it: at most one or two per screen; ordinary references stay plain
   underlined links. It is always an <a> to a real URL — never a
   button in costume. */
.flagship-link {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-control);
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  font-weight: var(--weight-heading);
  color: var(--text);
  text-decoration: none;
  border-bottom: var(--border-highlight) solid var(--accent-3);
  padding-bottom: 2px;
  transition: color var(--duration-fast) var(--ease-out);
}
.flagship-link::after {
  content: "\27F6";               /* the long arrow */
  color: var(--accent-3);
  transition: translate var(--duration-base) var(--ease-spring);
}
.flagship-link:hover { color: var(--accent-3); }
.flagship-link:hover::after { translate: 0.35em 0; }

/* ----- SKELETON, SPINNER, EMPTY STATE: the waiting family -----
   What it is: the three honest ways to show absence. The skeleton is
   a shimmering placeholder shaped like the content it stands in for
   (put .skeleton on sized blocks). The spinner is a small accent
   ring for work with no known shape or length. The empty state is a
   dashed quiet zone that says what would be here and offers the one
   action that fills it. What it is for: loading lists (skeleton),
   indeterminate waits (spinner), and genuinely nothing-yet screens
   (empty). When to use it: skeletons over spinners whenever the
   shape is known; always pair a spinner with words — under reduced
   motion the ring stands still and the words do the talking. */
.skeleton {
  display: block;
  min-height: 1em;
  border-radius: var(--radius-control);
  background: linear-gradient(90deg,
    var(--accent-2-pressed) 25%, var(--accent-2) 42%, var(--accent-2-pressed) 60%);
  background-size: 200% 100%;
  animation: facet-shimmer 1.4s linear infinite;
  color: transparent;
  user-select: none;
}
@keyframes facet-shimmer {
  from { background-position: 120% 0; }
  to { background-position: -80% 0; }
}

.spinner {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  border: 3px solid var(--accent-2-pressed);
  border-top-color: var(--accent-1);
  border-radius: var(--radius-pill);
  animation: facet-spin 0.8s linear infinite;
  flex: none;
}
@keyframes facet-spin { to { rotate: 360deg; } }

.empty {
  display: grid;
  place-items: center;
  gap: var(--space-control);
  padding: var(--space-section) var(--space-card);
  border: var(--border-element) dashed var(--border);
  border-radius: var(--radius-card);
  text-align: center;
  color: var(--text-muted);
}
.empty strong { color: var(--text); }

/* ----- BADGE, CHIP, AVATAR, PROGRESS: the small talkers -----
   What it is: four tiny signals. The badge is a status word in a
   tinted pill (kinds success / warning / error / info via a class).
   The chip is a small toggleable pill button — aria-pressed carries
   its state, the quiet accent fills it when on (promoted from this
   site's own filter chips: growth by extraction). The avatar is a
   round face: an image, or initials on the quiet fill. Progress is
   the native <progress> element themed — accent-1 fill on a border
   track. What it is for: statuses on rows and cards; filter picks;
   people; completeness. When to use it: badges state, they never
   act; chips toggle (give real buttons aria-pressed); an avatar
   always carries alt text or an aria-label with the person's name;
   determinate progress needs value and max, and pair it with words
   for readers. */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 1px var(--space-control);
  border-radius: var(--radius-pill);
  font-size: var(--text-caption);
  font-weight: var(--weight-strong);
  background: var(--accent-2);
  color: var(--on-accent-2);
  border: var(--border-element) solid var(--border);
}
.badge-success { background: color-mix(in srgb, var(--success) 14%, var(--background)); color: var(--success); border-color: color-mix(in srgb, var(--success) 38%, var(--background)); }
.badge-warning { background: color-mix(in srgb, var(--warning) 14%, var(--background)); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 38%, var(--background)); }
.badge-error   { background: color-mix(in srgb, var(--error) 14%, var(--background));   color: var(--error);   border-color: color-mix(in srgb, var(--error) 38%, var(--background)); }
.badge-info    { background: color-mix(in srgb, var(--info) 14%, var(--background));    color: var(--info);    border-color: color-mix(in srgb, var(--info) 38%, var(--background)); }

.chips { display: flex; flex-wrap: wrap; gap: var(--space-tight); max-width: none; }
.chip {
  min-height: 36px;
  padding: var(--space-tight) var(--space-inline);
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-pill);
  font: inherit;
  font-size: var(--text-footnote);
  color: var(--text);
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    transform var(--duration-base) var(--ease-spring);
}
.chip:hover { border-color: var(--text-muted); }
.chip:active { transform: scale(0.92); }
.chip[aria-pressed="true"] {
  background: var(--accent-3);
  border-color: var(--accent-3);
  color: var(--on-accent-3);
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-pill);
  background: var(--accent-2-pressed);
  color: var(--on-accent-2);
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
  overflow: hidden;
  flex: none;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-large { width: 3.5rem; height: 3.5rem; font-size: var(--text-body); }

progress {
  appearance: none;
  width: 100%;
  height: 0.5rem;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--accent-2-pressed);
  overflow: hidden;
}
progress::-webkit-progress-bar { background: var(--accent-2-pressed); border-radius: var(--radius-pill); }
progress::-webkit-progress-value { background: var(--accent-1); border-radius: var(--radius-pill); transition: width var(--duration-base) var(--ease-out); }
progress::-moz-progress-bar { background: var(--accent-1); border-radius: var(--radius-pill); }

/* ----- CHART: the themed SVG line chart -----
   What it is: a line chart drawn by facet.chart(el, {points}) — data
   line in accent-1 (2.5px, dashed for projected spans), dots ringed
   in the page background, grid on the border token, axis text muted,
   event markers as faint dashed verticals labelled in a reserved
   band ABOVE the plot, and a crosshair you drag to read values,
   dropped on pointer lift. What it is for: one series over time:
   prices, balances, rates. When to use it: whenever the shape of the
   story matters more than the table of numbers — and keep the table
   nearby for the numbers. The SVG draws at ~1 unit per rendered
   pixel so type reads true; colors are all tokens, so every theme
   (velvet's gold included) gets its chart for free. The surface is
   touch-action: none — dragging reads values, never scrolls (the
   gesture law). */
.chart-card {
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--space-stack);
}
.chart svg { width: 100%; height: auto; touch-action: none; display: block; }
.chart .grid { stroke: var(--border); stroke-width: 1; }
.chart .line {
  stroke: var(--accent-1);
  stroke-width: 2.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.chart .line.proj { stroke-dasharray: 7 7; opacity: 0.85; }
.chart .dot { fill: var(--accent-1); stroke: var(--background); stroke-width: 2; }
.chart .lbl { fill: var(--text); font-weight: 600; }
.chart .lbl-m { fill: var(--text-muted); }
.chart .xhair { stroke: var(--text-muted); stroke-dasharray: 3 3; }
.chart .evt { stroke: var(--border); stroke-dasharray: 2 5; }
.chart .lbl-e { fill: var(--text-muted); }

/* ----- TABLE: header, zebra rows, row hover -----
   What it is: the data table — muted header row, zebra striping,
   a hover that names the row you are reading, right-aligned tabular
   numbers via .num. What it is for: real records: transactions,
   users, line items. When to use it: data with columns; lists of
   things with one field go in list markup instead. Wrap wide tables
   in .table-scroll — a wrapper the wrapper law allows because it
   enables horizontal scrolling on phones. */
.table-scroll { overflow-x: auto; }
table.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-footnote);
}
.table th {
  text-align: left;
  padding: var(--space-control) var(--space-inline);
  color: var(--text-muted);
  font-weight: var(--weight-strong);
  border-bottom: var(--border-highlight) solid var(--border);
  white-space: nowrap;
}
.table td {
  padding: var(--space-control) var(--space-inline);
  border-bottom: var(--border-hairline) solid var(--border);
}
.table tbody tr:nth-child(even) { background: var(--surface); }
.table tbody tr:hover { background: var(--accent-2-hover); }
.table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ----- NAV LINK: the three states of site navigation -----
   What it is: the navigation link with its three states designed —
   quiet by default, named on hover, plainly marked when it is the
   page you are on (aria-current="page"). What it is for: headers,
   sidebars, footers — anywhere a set of destinations lives. When to
   use it: every navigational link in chrome. In content, plain <a>
   underlined links stay the rule; .nav-link is for wayfinding
   surfaces where underlines would pile up. */
.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: var(--space-tight) var(--space-inline);
  border-radius: var(--radius-control);
  color: var(--text-muted);
  text-decoration: none;
  transition:
    color var(--duration-fast) var(--ease-out),
    background var(--duration-fast) var(--ease-out);
}
.nav-link:hover {
  color: var(--text);
  background: var(--accent-2-hover);
}
.nav-link[aria-current="page"] {
  color: var(--text);
  font-weight: var(--weight-strong);
  background: var(--accent-2-pressed);
}

/* ----- TABS: in-page views under an underline -----
   What it is: content tabs — real ARIA tabs (role=tablist/tab/
   tabpanel) with an accent underline on the active one; facet.js
   wires clicks, arrow keys and panel visibility. What it is for:
   two-to-five views of the same subject inside a page: Overview /
   Activity / Settings. When to use it: in-page view switching. Not
   app navigation (the tab bar) and not a form value (the segmented
   control). Point each tab's aria-controls at its panel's id; mark
   one aria-selected="true". */
.tabs {
  display: flex;
  gap: var(--space-tight);
  border-bottom: var(--border-hairline) solid var(--border);
}
.tabs [role="tab"] {
  border: none;
  background: none;
  padding: var(--space-control) var(--space-stack);
  min-height: 2.75rem;
  font: inherit;
  color: var(--text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: calc(-1 * var(--border-element));
  transition: color var(--duration-fast) var(--ease-out);
}
.tabs [role="tab"]:hover { color: var(--text); }
.tabs [role="tab"][aria-selected="true"] {
  color: var(--text);
  font-weight: var(--weight-strong);
  border-bottom-color: var(--accent-3);
}

/* ----- BREADCRUMB: where you are, as links -----
   What it is: the path to here — an ordered list of links with quiet
   separators, the current page unlinked and marked aria-current.
   What it is for: deep hierarchies: catalogues, docs, file trees.
   When to use it: three levels or more; two levels just need a back
   link. */
.breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: 0;
  padding: 0;
}
.breadcrumb li {
  display: flex;
  align-items: center;
  font-size: var(--text-footnote);
}
.breadcrumb li + li::before {
  content: "/";
  color: var(--text-muted);
  margin-inline: var(--space-control);
}
.breadcrumb a {
  color: var(--text-muted);
  text-decoration: none;
}
.breadcrumb a:hover { text-decoration: underline; color: var(--text); }
.breadcrumb [aria-current="page"] {
  color: var(--text);
  font-weight: var(--weight-strong);
}

/* ----- PAGINATION: numbered pages as real links -----
   What it is: page links in 44px squares — previous, numbers, next —
   with the current page filled in accent-1 and marked aria-current.
   What it is for: long lists split across URLs, so every page is
   linkable and crawlable. When to use it: server-side or URL-driven
   paging. For infinite content, prefer a "load more" button — honest
   and keyboard-friendly. Disable an edge arrow with
   aria-disabled="true" on a span, never a dead link. */
.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-tight);
}
.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding-inline: var(--space-control);
  border-radius: var(--radius-panel);
  color: var(--text);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out);
}
.pagination a:hover { background: var(--accent-2-hover); }
.pagination [aria-current="page"] {
  background: var(--accent-1);
  color: var(--on-accent-1);
  font-weight: var(--weight-strong);
}
.pagination [aria-disabled="true"] { color: var(--text-muted); }

/* ----- ACCORDION: native details, one open at a time if named -----
   What it is: expandable rows on <details class="accordion"> — the
   platform's own disclosure, dressed: hairline rows, a rotating
   chevron, roomy 44px summaries. What it is for: FAQs, advanced
   settings, anything long that most readers skip. When to use it:
   give sibling accordions the same name attribute and the browser
   keeps exactly one open — no JS anywhere. */
details.accordion {
  border-bottom: var(--border-hairline) solid var(--border);
}
.accordion summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inline);
  min-height: 2.75rem;
  padding-block: var(--space-control);
  font-weight: var(--weight-strong);
  cursor: pointer;
  list-style: none;
}
.accordion summary::-webkit-details-marker { display: none; }
.accordion summary::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  rotate: 45deg;
  flex: none;
  transition: rotate var(--duration-base) var(--ease-spring);
}
.accordion[open] summary::after { rotate: 225deg; }
.accordion > :not(summary) { padding-bottom: var(--space-stack); }

/* ----- TOAST: a transient notice that names itself -----
   What it is: a small pill that appears near the bottom, says one
   thing, and leaves on its own — created by facet.toast(message,
   kind). What it is for: confirmations and quiet failures: saved,
   copied, offline. When to use it: outcomes that need no reply.
   Never for questions — those are dialogs. The rack is aria-live
   polite, so readers hear the message without losing their place.
   Kinds color the leading dot: success, warning, error, info. */
.toast-rack {
  position: fixed;
  bottom: calc(var(--space-card) + env(safe-area-inset-bottom, 0px));
  left: 50%;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-control);
  z-index: 60;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--space-control);
  max-width: min(92vw, 28rem);
  padding: var(--space-control) var(--space-card);
  border-radius: var(--radius-pill);
  background: var(--text);
  color: var(--background);
  font-size: var(--text-footnote);
  box-shadow: var(--shadow-overlay);
  pointer-events: auto;
  animation: facet-toast-in var(--duration-base) var(--ease-spring);
  transition: opacity var(--duration-base) var(--ease-out), translate var(--duration-base) var(--ease-out);
}
@keyframes facet-toast-in { from { opacity: 0; translate: 0 12px; } }
.toast-out { opacity: 0; translate: 0 8px; }
.toast[data-kind]::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  flex: none;
}
.toast[data-kind="success"]::before { background: var(--success); }
.toast[data-kind="warning"]::before { background: var(--warning); }
.toast[data-kind="error"]::before { background: var(--error); }
.toast[data-kind="info"]::before { background: var(--info); }

/* ----- DROPDOWN MENU: a details that opens under its button -----
   What it is: a small action menu on <details class="dropdown"> — the
   summary wears button clothes, the .dropdown-menu panel drops in
   under it, outside clicks and Esc close it (facet.js), and every
   item is a real button or link. What it is for: two-to-seven
   secondary actions behind one trigger: export, duplicate, delete.
   When to use it: actions, never navigation between pages (links
   belong in nav) and never a form value (that is the select). */
details.dropdown {
  position: relative;
  display: inline-block;
}
.dropdown summary { list-style: none; }
.dropdown summary::-webkit-details-marker { display: none; }
.dropdown-menu {
  position: absolute;
  top: calc(100% + var(--space-tight));
  left: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  padding: var(--space-tight);
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-floating);
  z-index: 30;
  animation: facet-toast-in var(--duration-fast) var(--ease-out);
}
.dropdown-menu button,
.dropdown-menu a {
  display: block;
  width: 100%;
  padding: var(--space-control) var(--space-inline);
  border: none;
  border-radius: var(--radius-control);
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  min-height: 2.75rem;
  display: flex;
  align-items: center;
}
.dropdown-menu button:hover,
.dropdown-menu a:hover { background: var(--accent-2-hover); }
.dropdown-menu .dropdown-danger { color: var(--error); }

/* ----- CARD: default, outlined, clickable -----
   What it is: the grouped-surface primitive — a padded, rounded panel
   a breath above the page. What it is for: one thing per card: a
   product, a stat, a setting group, a summary. When to use it: .card
   for most grouping; .card-outlined when many cards sit close and
   shadows would pile up; a clickable card is an <a class="card
   card-clickable"> — the whole surface is the link, it lifts on
   hover and presses on click. Never put more than one primary action
   inside a card that is itself clickable. */
.card,
.card-outlined {
  display: block;
  background: var(--surface);
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--space-card);
  box-shadow: var(--shadow-raised);
  color: inherit;
}
.card-outlined {
  background: var(--background);
  box-shadow: none;
}
.card-clickable {
  text-decoration: none;
  cursor: pointer;
  transition:
    translate var(--duration-base) var(--ease-spring),
    box-shadow var(--duration-base) var(--ease-out);
}
.card-clickable:hover {
  translate: 0 -2px;
  box-shadow: var(--shadow-floating);
}
.card-clickable:active {
  translate: 0 0;
  box-shadow: var(--shadow-raised);
}

/* ----- STEPPER: minus, value, plus -----
   What it is: a quantity control — a real number input between two
   step buttons, in one pill. What it is for: counts and small
   adjustments where typing is overkill: guests, quantity, years.
   When to use it: ranges of a few dozen at most; beyond that use the
   number input. The input stays typeable; the buttons call the
   input's own stepUp/stepDown so min, max and step attributes rule.
   facet.js wires .stepper-down/.stepper-up and fires input+change. */
.stepper {
  display: inline-flex;
  align-items: center;
  padding: 2px;
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
}
.stepper button {
  width: 2.5rem;
  height: 2.5rem;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text);
  font-size: var(--text-h4);
  cursor: pointer;
  touch-action: manipulation;
  transition: background var(--duration-fast) var(--ease-out);
}
.stepper button:hover { background: var(--accent-2-hover); }
.stepper button:active { background: var(--accent-2-pressed); }
.stepper input {
  width: 3.5rem;
  border: none;
  background: transparent;
  text-align: center;
  font: inherit;
  font-weight: var(--weight-strong);
  color: var(--text);
  -moz-appearance: textfield;
  appearance: textfield;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { appearance: none; }

/* ----- SEGMENTED CONTROL: one pick, all options visible -----
   What it is: a pill of options where exactly one is pressed in —
   radios underneath, so the form value, keyboard arrows and screen
   reader behavior are all native. What it is for: switching between
   2–5 views or ranges of the same thing: 1Y/5Y/Max, list/grid. When
   to use it: choices that change what is shown, not settings that
   act (that is the switch) and not navigation (that is the tab bar).
   Markup: label > hidden radio + span per option. */
.segmented {
  display: inline-flex;
  margin: 0;                     /* it is often a fieldset */
  padding: 2px;
  gap: 2px;
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
}
.segmented label { position: relative; display: inline-flex; }
.segmented input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.segmented span {
  display: inline-flex;
  align-items: center;
  padding: var(--space-control) var(--space-stack);
  border-radius: var(--radius-pill);
  font-size: var(--text-footnote);
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}
.segmented input:checked + span {
  background: var(--background);
  color: var(--text);
  font-weight: var(--weight-strong);
  box-shadow: var(--shadow-raised);
}
.segmented input:focus-visible + span {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ----- ROW: horizontal flow with wrap and themed gaps -----
   What it is: things side by side that wrap when they run out of
   room, with the gap owned by the row. What it is for: button rows,
   chip sets, meta lines, any run of small pieces. When to use it:
   whenever things sit beside each other — the row owns the gap, so
   children stay margin-free, exactly like the stack owns vertical
   rhythm. Variants: .row-tight for close runs, .row-loose for
   breathing room. */
.row,
.row-tight,
.row-loose {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-stack);
}
.row-tight { gap: var(--space-control); }
.row-loose { gap: var(--space-page); }

/* ----- GRID: responsive columns with themed gaps -----
   What it is: an auto-fitting column grid — as many columns as fit,
   each at least a card wide, collapsing to one on phones with no
   breakpoints written. What it is for: card grids, feature grids,
   galleries, dashboards. When to use it: any set of same-shaped
   pieces; the grid decides the count, the content decides the size.
   Variants: .grid-wide for bigger cards, .grid-tight and .grid-loose
   for gap. */
.grid,
.grid-wide,
.grid-tight,
.grid-loose {
  display: grid;
  gap: var(--space-stack);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}
.grid-wide { grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }
.grid-tight { gap: var(--space-control); }
.grid-loose { gap: var(--space-page); }

/* --------------------------------------------------------------------------
   APP COMPONENTS — ingested verbatim from the shipped inflation app.
   Base recipes, tokens only; their velvet skins live in the velvet theme
   block. Every piece has its wall entry on the homepage; the copy button
   under each demo gives the exact markup contract.
     TAB BAR   .tab-bar > .tab-seg / .tab-divider / .tab-settings
     SHEET     .sheet-scrim + .sheet[data-open] > .sheet-scroll/.sheet-foot
     MENU LIST .menu-item / .menu-label / .menu-icon-row / .menu-icon-btn
     GAUGE     .scroll-gauge / .scroll-gauge-thumb / .scroll-gauge-page
   -------------------------------------------------------------------------- */


/* ----- TAB BAR: iOS-style bottom pill of segments + a trailing icon -----
   Embossed frosted glass: translucent over a backdrop blur, pressed INTO
   the page via inset shadows (dark from the top, faint from the bottom).
   The active highlight is one .tab-indicator pill (injected by
   facetTabIndicator) that glides between segments like iOS. */
.tab-bar {
  position: fixed;
  bottom: calc(var(--space-inline) + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  display: flex;
  align-items: center;
  padding: var(--space-tight);
  /* Light blur only: content should visibly move behind the glass. */
  background: color-mix(in srgb, var(--surface) 32%, transparent);
  -webkit-backdrop-filter: blur(5px) saturate(1.4);
  backdrop-filter: blur(5px) saturate(1.4);
  border: var(--border-element) solid color-mix(in srgb, var(--border) 65%, transparent);
  border-radius: var(--radius-pill);
  box-shadow:
    inset 0 2px 5px color-mix(in srgb, var(--text) 22%, transparent),
    inset 0 -1px 3px color-mix(in srgb, var(--text) 9%, transparent),
    0 1px 0 color-mix(in srgb, var(--surface) 55%, transparent);
  white-space: nowrap;
}
.tab-indicator {
  position: absolute;
  top: var(--space-tight);
  left: 0;
  height: calc(100% - var(--space-tight) * 2);
  width: 0;
  border-radius: var(--radius-pill);
  /* raised pill gliding inside the inset bar; facetTabIndicator
     animates transform/width itself (squash-and-stretch), so no
     CSS transition here */
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  box-shadow:
    0 2px 5px color-mix(in srgb, var(--text) 25%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--background) 85%, transparent);
  pointer-events: none;
}
.tab-seg {
  position: relative;
  z-index: 1;
  min-height: 40px;
  padding: 0 var(--space-stack);
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
  /* letterpress: inactive labels sit pressed into the inset bar */
  text-shadow: 0 1px 0 color-mix(in srgb, var(--background) 65%, transparent);
  transition: color var(--duration-fast) var(--ease-out);
}
.tab-seg[aria-current="true"] {
  color: var(--accent-pressed);
  /* the active label rides the raised pill: crisper, lighter press */
  text-shadow: 0 1px 0 color-mix(in srgb, var(--background) 40%, transparent);
}
.tab-divider {
  width: var(--border-element);
  height: var(--space-card);
  background: var(--border);
  margin-inline: var(--space-tight);
}
.tab-settings {
  position: relative;
  z-index: 1;
  width: 40px;
  min-width: 40px;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  transition: color var(--duration-fast) var(--ease-out);
}
/* The gear turns a quarter with the panel: in on open, back on close. */
.tab-settings svg {
  transition: transform var(--duration-slow) var(--ease-in-out);
}
.tab-settings[aria-expanded="true"] {
  color: var(--accent-pressed);
}
.tab-settings[aria-expanded="true"] svg {
  transform: rotate(90deg);
}

/* ----- SHEET: right-edge slide-in panel over a darkening scrim -----
   Vertically centred; caps at 60% of the viewport height and 80% of
   its width on phones. Content taller than the cap scrolls INSIDE
   .sheet-body. Toggle via data-open on both elements. */
.sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgb(0 0 0 / 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-slow) var(--ease-in-out);
}
.sheet-scrim[data-open="true"] {
  opacity: 1;
  pointer-events: auto;
}
.sheet {
  position: fixed;
  top: 50%;
  right: var(--space-inline);
  z-index: 41;
  width: min(80vw, 20rem);
  height: 80dvh;
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--surface) 40%, transparent);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  backdrop-filter: blur(24px) saturate(1.5);
  border: var(--border-element) solid color-mix(in srgb, var(--border) 65%, transparent);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-overlay);
  /* facetSheet drives entry/exit with the tab pill's per-edge springs
     (frame stretches, right edge never crosses its resting line) —
     no CSS transform transition here, JS owns transform + width. */
  transform: translate(calc(100% + var(--space-stack)), -50%);
  visibility: hidden;
}
/* Nav + actions scroll in the top region; the icon row lives in a foot
   pinned below it. The scroll region keeps the panel's RESTING width and
   anchors to the right edge, so while the frame's left edge overshoots
   and breathes, the text inside lands linearly and then stays still —
   the tab bar's still-labels/moving-pill split, applied to the panel.
   The foot spans the frame itself, so its buttons stretch and snap back
   with the overshoot. */
.sheet-scroll {
  flex: 1;
  min-height: 0;
  width: min(80vw, 20rem);
  margin-left: auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-card) var(--space-inline) var(--space-control);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;        /* groups stack close, no spread */
  gap: var(--space-stack);
}
/* the foot keeps the RESTING width too: its buttons stay a fixed size
   while the frame overshoots around them */
.sheet-foot {
  width: min(80vw, 20rem);
  margin-left: auto;
  padding: var(--space-control) var(--space-inline) var(--space-inline);
}
.sheet-group {
  display: flex;
  flex-direction: column;
}
.sheet-scroll .sheet-group { gap: var(--space-tight); }
/* SCROLL GAUGE: a pill thumb inset in an embossed groove, top right —
   thumb height shows how much of the content is visible, position
   shows where you are. Spans only the top quarter; its left edge
   lines up with where the panel's corner radius ends. Momentum can
   push the thumb past the groove, where it clips away. Injected by
   facetScrollGauge; the native scrollbar hides in its favour. */
.sheet-scroll { scrollbar-width: none; }
.sheet-scroll::-webkit-scrollbar { display: none; }
.scroll-gauge {
  position: absolute;
  top: var(--space-stack);          /* clears the corner curve */
  right: var(--space-control);
  height: 25%;
  width: 8px;                   /* left edge lands at the radius line */
  padding: 2px;                 /* breathing room around the pill */
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  box-shadow:
    inset 0 1px 2px color-mix(in srgb, var(--text) 20%, transparent),
    inset 0 -1px 1px color-mix(in srgb, var(--text) 8%, transparent),
    0 1px 0 color-mix(in srgb, var(--surface) 60%, transparent);
  overflow: hidden;             /* out-of-bounds momentum disappears */
  pointer-events: none;
}
.scroll-gauge-thumb {
  width: 100%;
  border-radius: var(--radius-pill);
  /* a light raised pill sitting in the inset groove */
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--text) 30%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--background) 85%, transparent);
  transition: height var(--duration-fast) var(--ease-out);
}
/* Page-level variant: a fixed lane floating just off the right edge,
   half the viewport tall, vertically centred. The thumb is the
   viewport's true share of the page, so snaps and smooth glides
   read 1:1 on it. */
.scroll-gauge-page {
  position: fixed;
  top: 50%;
  right: var(--space-control);
  transform: translateY(-50%);
  height: 50dvh;
  width: 10px;
  z-index: 25;
  pointer-events: auto;
  cursor: grab;
  touch-action: none;         /* the gauge drags the page, never scrolls */
  transition: width 0.16s ease, right 0.16s ease;
}
/* grab: the lane widens and the pill lights up in the theme colour */
.scroll-gauge-page.grabbing {
  width: 18px;
  right: calc(var(--space-control) - 4px);
  cursor: grabbing;
}
.scroll-gauge-page .scroll-gauge-thumb { transition: background 0.16s ease; }
.scroll-gauge-page.grabbing .scroll-gauge-thumb {
  background: var(--v-gold);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.4);
}

/* ----- THEMED MAP: a Google Map wearing the active theme -----
   What it is: a Google Map wearing the active theme — ground, water,
   parks and roads derived from the base family, labels from the text
   family, boundaries from the quiet accent, recomputed live when the
   theme or mode switches. What it is for: maps that belong to the
   product instead of arriving in Google's own colors. When to use it:
   any embedded map. Bring your own key: put data-map on a div, the key
   in data-map-key (or data-maps-key on the facet.js script tag),
   optional data-lat / data-lng / data-zoom. Without a key, or offline,
   the div is this quiet themed placeholder — a token grid and one
   line of type, never a broken embed. */
[data-map] {
  aspect-ratio: 16 / 10;
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  overflow: hidden;              /* the live canvas honours the radius */
  background:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px),
    var(--surface);
  background-size: 2rem 2rem, 2rem 2rem, auto;
  display: grid;
  place-items: center;
  padding: var(--space-stack);
  color: var(--text-muted);
  font-size: var(--text-footnote);
  text-align: center;
}
[data-map="live"] {
  display: block;                /* hand layout back to the Maps canvas */
  padding: 0;
  background: var(--surface);
}

/* Apple-style continuous corners wherever a rectangle has a radius.
   Progressive: browsers without corner-shape keep plain rounding.
   Pills (tab bar, gauge) stay true capsules on purpose. */
@supports (corner-shape: squircle) {
  .sheet,
  .menu-item,
  .menu-icon-btn,
  .stat,
  .chart-card,
  .seg,
  .btn,
  input,
  .result {
    corner-shape: squircle;
  }
}

/* ----- MENU LIST: rows for a sheet or dropdown ----- */
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-inline);
  width: 100%;
  min-height: 40px;
  padding: var(--space-control) var(--space-inline);
  border: none;
  border-radius: var(--radius-panel);
  background: transparent;
  color: var(--text);
  font-size: var(--text-footnote);
  font-weight: var(--weight-strong);
  text-align: left;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out);
}
.menu-item:hover { background: color-mix(in srgb, var(--background) 55%, transparent); }
.menu-item svg { flex: none; color: var(--text-muted); }
/* Trailing state readout: "Auto", "On", "Tilt"... */
.menu-value {
  margin-left: auto;
  color: var(--accent-pressed);
  font-weight: var(--weight-strong);
}
.menu-value[data-off="true"] { color: var(--text-muted); }
.menu-label {
  padding: var(--space-control) var(--space-inline) 0;
  margin-bottom: var(--space-control);
  font-size: var(--text-caption);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.menu-divider {
  border: none;
  border-top: var(--border-hairline) solid var(--border);
  margin: var(--space-tight) var(--space-control);
}
/* Inset divider: a short centred hairline, not edge to edge. */
.menu-divider-inset {
  width: 55%;
  align-self: flex-start;
  margin-left: var(--space-inline);
  margin-right: auto;
}
/* A horizontal row of icon controls: equal widths, proper button faces,
   each with a tiny state caption. */
.menu-icon-row {
  flex-direction: row;
  align-items: stretch;
  gap: var(--space-control);
}
.menu-icon-btn {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-tight);
  min-height: 56px;
  padding: var(--space-control) var(--space-tight);
  border: var(--border-element) solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: var(--radius-panel);
  /* ON reads raised: light face, soft drop shadow, lit top lip */
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  box-shadow:
    0 2px 4px color-mix(in srgb, var(--text) 16%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--background) 85%, transparent);
  color: var(--text);
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}
.menu-icon-btn:hover {
  background: color-mix(in srgb, var(--surface) 95%, transparent);
  border-color: var(--text-muted);
}
.menu-icon-btn:active { transform: translateY(1px); }
/* OFF reads pressed into the glass: darker well, inset shadows. Keyed on
   a .menu-value[data-off], or on the button itself when it has no value. */
.menu-icon-btn:has(.menu-value[data-off="true"]),
.menu-icon-btn[data-off="true"] {
  background: color-mix(in srgb, var(--text) 7%, transparent);
  border-color: color-mix(in srgb, var(--border) 50%, transparent);
  box-shadow:
    inset 0 2px 4px color-mix(in srgb, var(--text) 20%, transparent),
    inset 0 -1px 2px color-mix(in srgb, var(--text) 8%, transparent),
    0 1px 0 color-mix(in srgb, var(--surface) 60%, transparent);
  color: var(--text-muted);
}
.menu-icon-btn .menu-value {
  margin-left: 0;
  font-size: var(--text-caption);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ================== end FACET-CANDIDATE COMPONENTS ================== */


/* --------------------------------------------------------------------------
   FLOAT BUTTON

   What it is: a lone floating round button pinned to the bottom-left of
   the page — the tab bar's trailing icon without the bar. What it is
   for: pages that need the settings sheet (or any panel) but not
   section navigation: give it aria-controls pointing at a .sheet and
   facet.js wires the open/close, the aria-expanded state and the press
   feedback exactly like the tab bar's gear. When to use it: whenever a
   page has one floating action; .float-btn-right pins it bottom-right
   instead. Always an aria-label and a data-tip: the icon alone says
   nothing.

   Usage:
     <button class="float-btn" aria-controls="settings-sheet"
             aria-expanded="false" aria-label="Open settings"
             data-tip="Settings" data-event="open-settings">
       <svg>...gear...</svg>
     </button>
     <div class="sheet-scrim" data-open="false"></div>
     <aside class="sheet" id="settings-sheet" data-open="false"
            aria-hidden="true" aria-label="Settings"> ... </aside>
   -------------------------------------------------------------------------- */

.float-btn {
  position: fixed;
  left: calc(var(--space-stack) + env(safe-area-inset-left, 0px));
  bottom: calc(var(--space-stack) + env(safe-area-inset-bottom, 0px));
  z-index: 30;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  padding: 0;
  border: var(--border-element) solid color-mix(in srgb, var(--border) 65%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface) 32%, transparent);
  -webkit-backdrop-filter: blur(5px) saturate(1.4);
  backdrop-filter: blur(5px) saturate(1.4);
  box-shadow:
    inset 0 2px 5px color-mix(in srgb, var(--text) 22%, transparent),
    inset 0 -1px 3px color-mix(in srgb, var(--text) 9%, transparent),
    0 1px 0 color-mix(in srgb, var(--surface) 55%, transparent);
  color: var(--text);
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out),
    transform var(--duration-normal) var(--ease-spring);
}
.float-btn:active { transform: translateY(1px) scale(0.96); }
.float-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--surface) 92%, transparent); }
.float-btn-right {
  left: auto;
  right: calc(var(--space-stack) + env(safe-area-inset-right, 0px));
}

/* Velvet: the floating trigger is a raised face; open, it wears the
   pill like the tab bar's gear. Cushion press, face never changes. */
[data-theme="velvet"] .float-btn {
  position: fixed;                 /* the .btn recipe must not unpin it */
  border: none;
  background: var(--v-face);
  box-shadow: var(--v-raised-2nd);
  color: var(--v-ink);
  transition: transform var(--v-press-out), box-shadow var(--v-press-out);
}
[data-theme="velvet"] .float-btn::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-pill);
  background: var(--v-dent-tap); opacity: 0; pointer-events: none;
  transition: opacity var(--v-press-out);
}
[data-theme="velvet"] .float-btn:active { transform: translateY(1px); transition-duration: 0.09s, 0.09s; }
[data-theme="velvet"] .float-btn:active::after { opacity: 1; transition: opacity var(--v-press-in); }
[data-theme="velvet"] .float-btn[aria-expanded="true"] {
  background: var(--v-face);
  box-shadow: var(--v-pill);
  color: var(--v-ink-strong);
}
[data-theme="velvet"] .float-btn svg { filter: var(--v-icon-filter); }

/* --------------------------------------------------------------------------
   CHOICE GRID

   What it is: a grid of small square choice buttons — a symbol above a
   tiny caption, five per row on phones. What it is for: one exclusive
   pick from up to about ten options where the choices are compact and
   symbolic — currencies, units, categories; aria-pressed carries the
   state and facet.js keeps the pick exclusive. When to use it: any
   single pick too wide for a segmented control; shipped as the
   inflation app's ten-currency picker. State changes ease — selection
   must never jump. Velvet: unselected options sit flat in carved
   wells, the selected one stands proud with its symbol in gold.

   Usage:
     <div class="choice-grid" role="group" aria-labelledby="cur-label">
       <button type="button" class="choice-btn" aria-pressed="true"
               data-tip="Indian rupee" data-event="cur-inr">
         <span class="choice-sym" aria-hidden="true">&#8377;</span>
         <span class="choice-cap">INR</span>
       </button>
       ...
     </div>
   -------------------------------------------------------------------------- */

.choice-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--space-control);
}
.choice-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: var(--space-control) 0;
  border: var(--border-element) solid var(--border);
  border-radius: var(--radius-panel);
  background: var(--surface);
  color: var(--text-muted);
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-normal) var(--ease-spring);
}
.choice-btn:active { transform: translateY(1px) scale(0.97); }
.choice-btn .choice-sym { font-size: var(--text-h4); font-weight: 700; }
.choice-btn .choice-cap { font-size: var(--text-caption); letter-spacing: 0.06em; }
.choice-btn[aria-pressed="true"] {
  background: var(--accent-2-hover);
  border-color: var(--text-muted);
  color: var(--text);
}
.choice-btn[aria-pressed="true"] .choice-sym { color: var(--accent-3); }

/* Velvet: wells for the field, a raised face for the pick, gold symbol. */
[data-theme="velvet"] .choice-btn {
  border: none;
  border-radius: var(--radius-panel);
  background: var(--v-well-bg);
  box-shadow: var(--v-pit);
  color: var(--text-muted);
  font-family: var(--v-font);
  transition:
    background 0.25s ease,
    box-shadow 0.25s ease,
    color 0.25s ease,
    transform var(--v-press-out);
}
[data-theme="velvet"] .choice-btn:active { transform: translateY(1px); }
[data-theme="velvet"] .choice-btn[aria-pressed="true"] {
  background: var(--v-face);
  box-shadow: var(--v-raised-2nd);
  color: var(--v-ink-strong);
}
[data-theme="velvet"] .choice-btn[aria-pressed="true"] .choice-sym { color: var(--v-gold); }

/* --------------------------------------------------------------------------
   SLIDER TICK SCALE

   What it is: a tick scale hugging a slider's track — minor ticks,
   sparse labels, and an optional highlighted "now" tick marking a
   meaningful value (the current year where a projection begins). What
   it is for: giving a long range slider landmarks so positions read as
   values. When to use it: wrap slider and scale in a .slider-wrap; the
   scale insets by half the knob so tick positions stay true; facet.js
   builds the ticks from the slider's min/max (data-minor, data-label,
   data-now configure the steps) and rebuilds on facet.sliderScale(wrap)
   when the range changes.

   Usage:
     <div class="slider-wrap">
       <input type="range" class="slider" min="1960" max="2056" value="2000">
       <div class="slider-scale" data-minor="10" data-label="40"
            data-now="2026" aria-hidden="true"></div>
     </div>
   -------------------------------------------------------------------------- */

.slider-wrap {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.slider-wrap .slider { width: 100%; }
.slider-scale {
  position: relative;
  height: 18px;
  margin: -3px 16px 0;                /* hugs the track; half-knob inset */
}
.slider-scale .tick {
  position: absolute;
  top: 0;
  width: 1px;
  height: 5px;
  background: var(--border);
}
.slider-scale .tick-label {
  position: absolute;
  top: 7px;
  transform: translateX(-50%);
  font-size: var(--text-caption);
  color: var(--text-muted);
  opacity: 0.8;
}
.slider-scale .tick-now {
  width: 2px;
  height: 8px;
  background: var(--accent-3);        /* velvet: accent-3 IS the gold */
}
.slider-scale .tick-now-label { color: var(--accent-3); opacity: 1; }

/* --------------------------------------------------------------------------
   INSTALL NUDGE CARD

   What it is: a card over a dim blurred scrim, sitting just above the
   bottom chrome: eyebrow, title, one-line pitch, a primary "Add now", a
   dimmed "Add it later", fine-print "Don't ask again". What it is for:
   the gentle add-to-home-screen ask, driven by facet.installNudge —
   shown once per session after a delay, never when standalone,
   installed or opted out. When to use it: installable products; wire
   "Add now" to installNudge.addNow() and show the instruction overlay
   when it returns "guide" (iOS has no native prompt).

   Usage (markup contract, from the shipped app):
     <div class="nudge-scrim" id="a2hs-nudge" hidden>
       <div class="nudge-card" role="dialog" aria-modal="true"
            aria-labelledby="nudge-title">
         <span class="eyebrow">One tap away</span>
         <h2 id="nudge-title">Add this to your Home Screen</h2>
         <p>Quick access — full screen, works offline.</p>
         <button type="button" class="btn btn-primary nudge-add" data-event="nudge-add">Add now</button>
         <button type="button" class="btn nudge-later" data-event="nudge-later">Add it later</button>
         <button type="button" class="nudge-never" data-event="nudge-never">Don't ask again</button>
       </div>
     </div>
   -------------------------------------------------------------------------- */

.eyebrow {
  font-size: var(--text-caption);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--accent-3);
  display: block;
}
.nudge-scrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--space-card);
  padding-bottom: calc(var(--space-inline) + 48px + env(safe-area-inset-bottom, 0px) + var(--space-page));
  background: rgb(0 0 0 / 0.5);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.nudge-card {
  width: min(100%, 22rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-inline);
  padding: var(--space-page) var(--space-card) var(--space-stack);
  border-radius: calc(var(--radius-card) + 12px);
  background: var(--background);
  box-shadow: var(--shadow-overlay);
  text-align: center;
}
.nudge-card h2 { font-size: var(--text-h3); }
.nudge-card p { color: var(--text-muted); font-size: var(--text-footnote); max-width: 30ch; }
.nudge-add { width: 100%; min-height: 52px; margin-top: var(--space-control); }
.nudge-later {
  width: 100%;
  border: none;
  background: transparent;
  box-shadow: none;
  color: var(--text-muted);
  opacity: 0.7;
}
.nudge-never {
  border: none;
  background: transparent;
  padding: var(--space-control);
  font-size: var(--text-caption);
  color: var(--text-muted);
  opacity: 0.6;
  text-decoration: underline;
  text-underline-offset: 2px;
}
[data-theme="velvet"] .nudge-card {
  background: var(--v-bg);
  box-shadow:
    0 30px 70px rgb(0 0 0 / 0.5),
    0 10px 28px rgb(0 0 0 / 0.32),
    var(--v-bevel);
}

/* --------------------------------------------------------------------------
   INSTRUCTION OVERLAY

   What it is: the whole screen goes near-black and the direction sits
   up top where the eye lands — eyebrow, huge title with the key phrase
   highlighted, muted body lines, a faint tap-anywhere-to-close. What it
   is for: full-screen how-tos; shipped as the iOS Add-to-Home-Screen
   walk-through. When to use it: any instruction that deserves the whole
   screen. The palette is deliberately fixed — the screen goes dark in
   every theme — with the highlight in the pale gold that reads on black.

   Usage:
     <div class="overlay-guide" hidden>
       <span class="eyebrow">Install this app</span>
       <p class="overlay-title">Select <strong>"Add to Home Screen"</strong></p>
       <p class="overlay-sub">Tap Safari's Share button, then choose it.</p>
       <p class="overlay-sub" style="opacity:.55">Tap anywhere to close</p>
     </div>
   -------------------------------------------------------------------------- */

.overlay-guide {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgb(0 0 0 / 0.94);      /* deliberately fixed: dark in every theme */
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-stack);
  padding: calc(env(safe-area-inset-top, 0px) + var(--space-hero)) var(--space-card) var(--space-hero);
}
.overlay-guide .eyebrow { color: #dcbe8e; }   /* pale gold on black, fixed */
.overlay-title {
  font-size: clamp(2rem, 9vw, 2.8rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: #ffffff;
  max-width: 20ch;
}
.overlay-title strong { color: #dcbe8e; }
.overlay-sub { color: rgb(255 255 255 / 0.6); font-size: var(--text-h4); }

/* Velvet capsule number input: the amount reads as one soft capsule. */
[data-theme="velvet"] .number-input input {
  border-radius: var(--radius-pill);
  padding-inline: var(--space-card);
  min-height: 54px;
}
